blit 0.2.1
blit: ^0.2.1 copied to clipboard
Zero-copy Bytes/BytesMut and high-performance random-access file I/O for Dart and Flutter, with async file I/O, native FFI mmap, and Web storage.
blit #
Zero-copy Bytes / BytesMut, async file I/O, and Web storage for Dart and
Flutter. Native I/O is pure dart:ffi; Web I/O uses OPFS, IndexedDB, and Blob.
No C compiler, native build hooks, or native assets.
Why blit #
- Zero-copy
Bytes- a(buffer, offset, length)view;slice/sublistare O(1). BytesMut- growable buffer;freeze()/splitTo()/splitOff()return shared views with copy-on-write on later mutation.- Exact
Uint64- two 32-bit limbs; works on VM, dart2js, and dartwasm. - AsyncFile - one random-access API across IOCP, io_uring, mmap, isolate pools, OPFS, IndexedDB, and Blob.
- AsyncIO -
ReadBuf,AsyncRead/AsyncWrite, buffered readers/writers,readExact,readToEnd,copy, and typed-data adapters. - Native zero-copy -
NativeBufferand pointer read/write on the native mmap backend. - Simple API -
blit.read/blit.write/blit.openfor default use.
Runtime dependencies are package:ffi and the pure-Dart package:io_uring
(used only by the Linux ring backend).
Install #
dart pub add blit
Requires Dart ^3.13.5.
Simple API #
import 'package:blit/blit.dart';
await blit.write('data.bin', bytes);
final data = await blit.read('data.bin');
await blit.append('data.bin', tail);
await blit.copy('data.bin', 'backup.bin');
final file = await blit.open('data.bin');
final head = await file.readAt(0, 4096);
await file.writeAt(0, head);
await file.close();
blit.open defaults to read-write and creates a missing file. Use
FileOptions for explicit mode:
final file = await blit.open(
'data.bin',
options: const FileOptions(
mode: FileMode.read,
create: false,
durability: Durability.full,
profile: PerformanceProfile.throughput,
),
);
Core bytes #
import 'dart:typed_data';
import 'package:blit/blit.dart';
final backing = Uint8List.fromList([1, 2, 3, 4]);
final bytes = Bytes.fromUint8List(backing);
final slice = bytes.slice(1, 3); // O(1), shares backing
backing[1] = 99;
print(slice[0]); // 99
Typed-data interop is zero-copy:
final bytes = Bytes.fromTypedData(byteData);
final typed = bytes.asUint8List();
Other byte libraries can implement BytesLike:
abstract interface class BytesLike {
int get length;
Uint8List asUint8List();
}
For direct writer tails:
final buf = BytesMut(1 << 16);
final tail = buf.spareView(4096);
// write through tail[offset]
buf.advance(4096);
AsyncFile #
import 'package:blit/blit_io.dart';
final file = await AsyncFile.open(
'data.bin',
options: const FileOptions(mode: FileMode.readWrite),
);
final dst = Uint8List(1 << 20);
final n = await file.readAt(offset, ReadBuf(dst));
await file.writeAt(offset, IoSlice(dst, end: n));
await file.flush();
await file.close();
Batch requests:
await file.readMany([
ReadRequest(0, ReadBuf(a)),
ReadRequest(1 << 20, ReadBuf(b)),
]);
await file.writeMany([
WriteRequest(0, IoSlice(a)),
WriteRequest(1 << 20, IoSlice(b)),
]);
Errors use one hierarchy:
BlitException
- BlitIOException
- EndOfFileException
- ClosedException
- PermissionException
- QuotaExceededException
- UnsupportedException
AsyncIO #
import 'package:blit/blit_io.dart';
final file = await AsyncFile.open(
'data.bin',
options: const FileOptions(mode: FileMode.read),
);
final reader = BufReader(file, capacity: 64 * 1024);
final line = await readLine(reader);
final dst = BytesMutAsyncWriter();
await reader.copyTo(dst);
Available:
ReadBuf,IoSliceAsyncRead,AsyncWrite,AsyncBufReadreadExact,readToEnd,readU8..U64,writeAll,writeUint*,copyBufReader,BufWriter,readLine,readerStreamUint8ListReader,BytesAsyncReader,Uint8ListWriter,BytesMutAsyncWriter
Unsafe external memory #
Unsafe APIs live in package:blit/unsafe.dart.
UnsafeBytes is a fixed-length allocation handle outside the Dart GC. The
length is not stored in the handle; callers pass it to every length-bearing
operation. free releases through the allocator header.
final bytes = UnsafeBytes.alloc(64);
final view = bytes.view(64);
bytes.setInt32(0, 42);
bytes.setFloat64(8, 1.5);
bytes.free();
Native uses malloc/free; Web uses a WebAssembly.Memory arena. The handle
is an int extension type, so allocation does not add a Dart object. There are
no bounds, lifetime, alignment, double-free, or use-after-free checks. This is
not a C ABI layer and does not define struct layouts.
AsyncFile.readAtUnsafe and writeAtUnsafe accept an UnsafeBytes plus an
explicit length.
Raw int operations are in package:blit/unsafe_raw.dart:
final address = unsafeAlloc(8);
unsafeWriteInt32(address, 0, 42);
unsafeFree(address);
Native zero-copy #
import 'package:blit/blit_native.dart';
final native = await NativeMmapFile.open('data.bin', create: false);
final buffer = NativeBuffer.allocate(1 << 20);
try {
native.readIntoNative(offset, buffer);
native.writeFromNative(offset, buffer);
} finally {
buffer.dispose();
await native.close();
}
NativeBufferPool reuses exact-size buffers. Pointer paths are native-only and
bypass the typed-data copy.
Web #
import 'package:blit/blit_web.dart';
await blit.write('data.bin', bytes);
final data = await blit.read('data.bin');
final blobFile = await openBlob(blob);
final all = await blobFile.readAll();
await blobFile.close();
OPFS is the default file backend. IndexedDB and Blob APIs are available from
package:blit/blit_web.dart.
Platform support #
| Capability | Windows | Linux | macOS | Android | iOS | Web |
|---|---|---|---|---|---|---|
Bytes / BytesMut / Uint64 |
yes | yes | yes | partial | partial | yes |
AsyncFile |
yes | yes | yes | partial | partial | yes |
| AsyncIO utilities | yes | yes | yes | partial | partial | yes |
| Native zero-copy | yes | yes | yes | partial | partial | no |
| OPFS / IndexedDB / Blob | no | no | no | no | no | yes |
partial means the pure-Dart core is portable, but Android/iOS native file
paths are not built or validated by CI.
Performance #
The chart at the top is generated from CI benchmark results. Native engines:
- Windows: IOCP, mmap, isolate pools
- Linux: io_uring, mmap, isolate pools
- POSIX:
pread/pwrite, mmap - Web: OPFS worker pool, IndexedDB transactions, Blob slices
Use PerformanceProfile and FileOptions to select a policy instead of
referencing engine classes directly.
Development #
dart analyze
dart test
dart test -p chrome test/web
CI runs Dart tests on Ubuntu/macOS/Windows, Chrome Web tests, native/Rust benchmarks, and renders the benchmark SVG.
Limitations #
- Ordinary Dart
Uint8Listcannot be pinned; true zero-copy requiresNativeBufferor another native-backed view. FileOptions.directrequires aligned offsets and lengths.- Overlapping writes are serialized; do not assume a specific completion order.
- iOS/Android native paths are not validated by CI.