4 releases (2 breaking)
| 0.4.0 | Feb 20, 2026 |
|---|---|
| 0.3.0 | Jan 14, 2026 |
| 0.2.1 | Jan 11, 2026 |
| 0.2.0 | Jan 11, 2026 |
#957 in Concurrency
47KB
682 lines
enough-ffi
FFI helpers for the enough cooperative cancellation trait.
This crate provides C-compatible functions and types for bridging cancellation across language boundaries. Use it to integrate Rust libraries with C#/.NET, Python, Node.js, and other languages that can call C APIs.
Safety Model
This crate uses Arc-based reference counting internally to prevent use-after-free:
- Sources and tokens share state through
Arc - Destroying a source while tokens exist is safe - tokens remain valid
- Tokens that outlive their source will never become cancelled (no one can call cancel)
- Each token must be explicitly destroyed when no longer needed
Quick Start
C FFI Functions
// Source management
void* enough_cancellation_create(void);
void enough_cancellation_cancel(void* source);
bool enough_cancellation_is_cancelled(void* source);
void enough_cancellation_destroy(void* source);
// Token management
void* enough_token_create(void* source);
void* enough_token_create_never(void);
bool enough_token_is_cancelled(void* token);
void enough_token_destroy(void* token);
Rust FFI Functions
When writing Rust FFI functions that receive a token pointer:
use enough_ffi::FfiCancellationToken;
use enough::Stop;
#[no_mangle]
pub extern "C" fn my_operation(
data: *const u8,
len: usize,
token: *const FfiCancellationToken,
) -> i32 {
// Create a non-owning view from the pointer
let stop = unsafe { FfiCancellationToken::from_ptr(token) };
// Use with any library that accepts impl Stop
for i in 0..len {
if i % 100 == 0 && stop.should_stop() {
return -1; // Cancelled
}
// do work...
}
0
}
C# Integration
public class CancellationHandle : IDisposable
{
[DllImport("mylib")] static extern IntPtr enough_cancellation_create();
[DllImport("mylib")] static extern void enough_cancellation_cancel(IntPtr source);
[DllImport("mylib")] static extern void enough_cancellation_destroy(IntPtr source);
[DllImport("mylib")] static extern IntPtr enough_token_create(IntPtr source);
[DllImport("mylib")] static extern void enough_token_destroy(IntPtr token);
private IntPtr _source, _token;
private CancellationTokenRegistration _registration;
public CancellationHandle(CancellationToken ct)
{
_source = enough_cancellation_create();
_token = enough_token_create(_source);
_registration = ct.Register(() => enough_cancellation_cancel(_source));
}
public IntPtr TokenHandle => _token;
public void Dispose()
{
_registration.Dispose();
enough_token_destroy(_token);
enough_cancellation_destroy(_source);
}
}
Node.js Integration
import ffi from 'ffi-napi';
const lib = ffi.Library('mylib', {
'enough_cancellation_create': ['pointer', []],
'enough_cancellation_cancel': ['void', ['pointer']],
'enough_cancellation_destroy': ['void', ['pointer']],
'enough_token_create': ['pointer', ['pointer']],
'enough_token_destroy': ['void', ['pointer']],
});
function withCancellation(signal, operation) {
const source = lib.enough_cancellation_create();
const token = lib.enough_token_create(source);
const onAbort = () => lib.enough_cancellation_cancel(source);
signal?.addEventListener('abort', onAbort);
try {
return operation(token);
} finally {
signal?.removeEventListener('abort', onAbort);
lib.enough_token_destroy(token);
lib.enough_cancellation_destroy(source);
}
}
Types
| Type | Description |
|---|---|
FfiCancellationSource |
Owns cancellation state, can trigger cancellation |
FfiCancellationToken |
Holds reference to state, can check cancellation |
FfiCancellationTokenView |
Non-owning view for Rust FFI functions |
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.