enough-ffi

Crates.ioenough-ffi
lib.rsenough-ffi
version0.3.0
created_at2026-01-11 08:49:35.922573+00
updated_at2026-01-14 00:24:47.843156+00
descriptionC FFI helpers for the enough cooperative cancellation trait
homepage
repositoryhttps://github.com/imazen/enough
max_upload_size
id2035432
size31,761
Lilith River (lilith)

documentation

README

enough-ffi

FFI helpers for the enough cooperative cancellation trait.

Crates.io Documentation License

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.

Commit count: 34

cargo fmt