Dart ResourceHandle
last modified October 18, 2026
The ResourceHandle interface in Dart wraps an operating system
resource handle so that it can be passed to another process through a socket.
It is part of the dart:io library and is used together with
SocketControlMessage and RawSocket.sendMessage.
It is not a general-purpose RAII wrapper: it has no dispose
method and does not close resources automatically when it goes out of scope.
Basic Definition
ResourceHandle is an abstract interface class with no generic type
parameter. Create one with a factory such as
ResourceHandle.fromFile, fromSocket,
fromStdin, fromStdout, fromReadPipe, or
fromWritePipe.
A handle represents one OS resource. Extract it with a matching
toFile, toSocket, toReadPipe, or
toWritePipe method. After extraction, no other to...
method may be called on that handle.
Basic ResourceHandle Usage
This example wraps an open file, sends the handle over a Unix domain socket, and reads the file through the received handle. The small helper functions create a connected socket pair and wait for one control message.
import 'dart:async';
import 'dart:io';
Future<(RawServerSocket, RawSocket, RawSocket)> unixPair(String path) async {
var server = await RawServerSocket.bind(
InternetAddress(path, type: InternetAddressType.unix),
0,
);
var clientFuture = RawSocket.connect(
InternetAddress(path, type: InternetAddressType.unix),
0,
);
var serverSide = await server.first;
var clientSide = await clientFuture;
return (server, serverSide, clientSide);
}
Future<SocketMessage> receive(RawSocket socket) async {
var completer = Completer<SocketMessage>();
late StreamSubscription<RawSocketEvent> subscription;
subscription = socket.listen((event) {
if (event == RawSocketEvent.read) {
var message = socket.readMessage();
if (message != null && !completer.isCompleted) {
completer.complete(message);
subscription.cancel();
}
}
});
return completer.future;
}
Future<void> main() async {
var dir = Directory.systemTemp.createTempSync('resource_handle');
var path = '${dir.path}/socket';
var (server, serverSide, clientSide) = await unixPair(path);
var file = File('${dir.path}/data.txt');
await file.writeAsString('Hello, ResourceHandle!');
var raf = await file.open();
var handle = ResourceHandle.fromFile(raf);
clientSide.sendMessage([SocketControlMessage.fromHandles([handle])], [1]);
var message = await receive(serverSide);
var received = message.controlMessages
.expand((control) => control.extractHandles())
.first;
var fileHandle = received.toFile();
var bytes = await fileHandle.read(100);
print('File content: ${String.fromCharCodes(bytes)}');
await fileHandle.close();
clientSide.close();
await serverSide.close();
await server.close();
await file.delete();
dir.deleteSync(recursive: true);
}
ResourceHandle.fromFile wraps the open RandomAccessFile.
The receiver extracts the handle with toFile and reads it. The
extracted handle owns a descriptor received over the socket, so it can be
closed independently of the sender's file object.
$ dart main.dart File content: Hello, ResourceHandle!
Manual Resource Release
This example demonstrates that releasing a resource is still an explicit operation. The wrapped file is closed through its original object.
import 'dart:io';
Future<void> main() async {
var file = File('resource.txt');
await file.writeAsString('temporary data');
var raf = await file.open();
ResourceHandle.fromFile(raf);
print('Wrapped file handle');
await raf.close();
await file.delete();
print('Resource released');
}
ResourceHandle has no dispose method. Closing the
underlying RandomAccessFile releases the OS resource, after which
the file can be deleted safely.
$ dart main.dart Wrapped file handle Resource released
Error Handling with Resources
This example shows explicit cleanup of a wrapped resource when an error occurs.
import 'dart:io';
Future<void> main() async {
var file = File('database.tmp');
await file.writeAsString('temporary database');
var raf = await file.open();
ResourceHandle.fromFile(raf);
try {
throw Exception('Database operation failed');
} catch (e) {
print('Error occurred: $e');
} finally {
await raf.close();
await file.delete();
print('Resource released');
}
}
Because the handle performs no automatic cleanup, the file must be closed and
deleted in a finally block. This keeps the error path from leaking
the open file descriptor.
$ dart main.dart Error occurred: Exception: Database operation failed Resource released
Multiple Resources Management
This example manages several file handles in a collection and releases each one.
import 'dart:io';
Future<void> main() async {
var files = [File('log1.txt'), File('log2.txt')];
await files[0].writeAsString('Log entry 1');
await files[1].writeAsString('Log entry 2');
var handles = <ResourceHandle>[];
var openFiles = <RandomAccessFile>[];
for (var file in files) {
var raf = await file.open();
openFiles.add(raf);
handles.add(ResourceHandle.fromFile(raf));
}
print('Prepared ${handles.length} resource handles');
for (var raf in openFiles) {
await raf.close();
}
for (var file in files) {
await file.delete();
}
print('All resources released');
}
There is no ResourceHandle.group helper. A plain list is enough
when several handles need to be prepared together, and the original objects
still control when the resources are closed.
$ dart main.dart Prepared 2 resource handles All resources released
Passing a Handle Over a Socket
This example transfers the read end of a pipe over a Unix domain socket and uses the received handle in the same process.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
var dir = Directory.systemTemp.createTempSync('resource_handle');
var path = '${dir.path}/socket';
var server = await RawServerSocket.bind(
InternetAddress(path, type: InternetAddressType.unix),
0,
);
var clientFuture = RawSocket.connect(
InternetAddress(path, type: InternetAddressType.unix),
0,
);
var serverSide = await server.first;
var clientSide = await clientFuture;
var pipe = await Pipe.create();
var handle = ResourceHandle.fromReadPipe(pipe.read);
var control = SocketControlMessage.fromHandles([handle]);
var sent = clientSide.sendMessage([control], [1, 2, 3]);
print('sendMessage bytes: $sent');
var message = Completer<SocketMessage>();
var subscription = serverSide.listen((event) {
if (event == RawSocketEvent.read) {
var received = serverSide.readMessage();
if (received != null && !message.isCompleted) {
message.complete(received);
}
}
});
var receivedMessage = await message.future;
print('data: ${receivedMessage.data}');
var handles = receivedMessage.controlMessages
.expand((control) => control.extractHandles())
.toList();
print('handles: ${handles.length}');
var readPipe = handles.first.toReadPipe();
var result = readPipe.transform(utf8.decoder).join();
pipe.write.write('Hello over pipe!');
await pipe.write.close();
print(await result);
await subscription.cancel();
clientSide.close();
await serverSide.close();
await server.close();
dir.deleteSync(recursive: true);
}
SocketControlMessage.fromHandles attaches file descriptors to a
message sent with RawSocket.sendMessage. The receiver calls
readMessage, extracts the control messages, and converts the
received handle with toReadPipe. Handle passing over sockets is
supported on macOS and Linux (excluding Android), but not on Windows, Android,
or Fuchsia.
$ dart main.dart sendMessage bytes: 3 data: [1, 2, 3] handles: 1 Hello over pipe!
Best Practices
- Close the underlying resource:
ResourceHandledoes not dispose resources automatically - Extract once: A handle represents one OS resource; call only one
to...method - Use try/finally: Release resources even when an error occurs
- Transfer deliberately: Use socket control messages only when passing handles between processes
- Check platform support: Handle passing is not available on every operating system
Source
Dart ResourceHandle Documentation
This tutorial covered Dart's ResourceHandle interface with
practical examples showing file handles, explicit release, error cleanup,
multiple resources, and transferring a pipe handle over a socket.
Author
List all Dart tutorials.