fiffi 0.1.0

Rust bindings for libffi, alternative to libffi-rs
# fiffi

[![Apache 2.0 licensed][apache-badge]][apache-url]
[![MIT licensed][mit-badge]][mit-url]

`fiffi` (*\[fi\`f:i]*) provides Rust bindings for [`libffi`]. [`libffi-rs`] already provides Rust
bindings for `libffi`, but `fiffi` aims to provide more ergonomic bindings that makes it easier to
avoid safety issues when using functionality provided by `libffi`.

The crate uses [`libffi-sys-rs`] to build and link the underlying libffi library. `fiffi` is
`no_std` but requires `alloc`.

## Use Cases

`fiffi` is built around three main use cases:

- Calling foreign functions whose signatures are not fully known at compile time with `Function`.
- Creating function pointers to Rust closures with `Closure`.
- Creating function pointers with arbitrary signatures chosen at run time backed by Rust callbacks
  with `DynamicClosure`.

## Examples

Add `fiffi` to your `Cargo.toml`:

```toml
[dependencies]
fiffi = "0.1"
```

### Calling an FFI function with `Function`

```rust
use fiffi::function::{Function, arg, ret};
use fiffi::types::Type;

// Typically when using fiffi, the function's signature is not known at compile-time. If you know
// the function signature at compile-time, it is probably preferable to call the function pointer
// directly instead of using an external library for it.
extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

let function = Function::new(
    fiffi::fn_ptrize!(add),
    &[Type::I32, Type::I32],
    Some(&Type::I32),
);

let a = 10i32;
let b = 32i32;
let mut result = 0i32;

// SAFETY: `function` was built from `add` with matching argument and return
// types, and `result` is valid storage for the return value.
unsafe {
    function.call([arg(&a), arg(&b)], ret(&mut result));
}

assert_eq!(result, 42);
```

### Converting a Rust closure to a function pointer with `Closure`

```rust
use fiffi::closure::Closure;

let offset = 5i32;
let add_offset = Closure::new(move |value: i32| value + offset);

// `add_offset.as_fn_ptr()` can now be used to retrieve an `extern "C"` function pointer to call the
// closure.

// SAFETY: `add_offset` accepts one `i32` argument, returns `i32`, and remains alive while the
// function pointer is used.
let add_offset_fn = unsafe { add_offset.as_fn_ptr().into_fn::<extern "C" fn(i32) -> i32>() };

assert_eq!(add_offset_fn(1), 6);
```

### Creating a function pointer with a function signature defined at run time with `DynamicClosure`

```rust
use core::mem::MaybeUninit;

use fiffi::closure::dynamic::DynamicClosureCall;
use fiffi::closure::DynamicClosure;
use fiffi::types::Type;

fn add_offset_callback(mut call: DynamicClosureCall<i32>) {
    let arg = call
        .args()
        .get(0)
        .expect("This callback must be used with exactly one argument.");

    if arg.ty() != &Type::I32 {
        // **NOTE** This will abort instead of unwinding regardless of the crate's setting
        // because Rust cannot unwind past libffi's `extern "C"` functions.
        panic!("This callback can only be used with an `i32` argument.");
    }

    let mut value = MaybeUninit::<i32>::uninit();

    // SAFETY: The argument's type is `i32`. The `copy_to` fully initializes `value`.
    let result = unsafe {
        arg.copy_to(&mut value);
        value.assume_init()
    } + call.context();

    let ret = call
        .ret()
        .expect("This callback expects a return value.");

    if ret.ty() != &Type::I32 {
        // **NOTE** This will abort instead of unwinding regardless of the crate's setting
        // because Rust cannot unwind past libffi's `extern "C"` functions.
        panic!("This callback can only be used with an `i32` return value.");
    }

    // SAFETY: The return value's type is `i32`.
    unsafe {
        ret.write(result);
    }
}

let add_offset = DynamicClosure::new(
    add_offset_callback,
    &[Type::I32],
    Some(&Type::I32),
    5,
);

// SAFETY: `add_offset` represents an `extern "C"` function that accepts one `i32` and returns an
// `i32`. `add_offset` remains alive while it is called.
let add_offset_fn = unsafe {
    add_offset
        .as_fn_ptr()
        .into_fn::<extern "C" fn(i32) -> i32>()
};

assert_eq!(add_offset_fn(37), 42);
```

## Features

- `closure`: includes `Closure` and `DynamicClosure` function-pointer support. This feature is
  enabled by default.
- `check_only`: forwards `libffi-sys`'s `check_only` feature to speed up `cargo check` by not
  compiling libffi.
- `system`: forwards `libffi-sys`'s `system` feature to dynamically link to a system libffi instead
  of compiling a static library. Note that some functionality may require libffi 3.5.0.

## `libffi-rs` or `fiffi`?

See [`libffi-rs-2-fiffi.md`] for a comparison of how two simple code snippets using `libffi-rs`
would be written using `fiffi`.

### Reasons to prefer `libffi-rs`

* Mature project with more than 1 maintainer
* Used by large projects such as [`miri`] and [`deno`]
* Prioritizes stability and support for older Rust versions
* Several abstraction levels (`low`, `middle`, `high`) for different needs

### Reasons to prefer `fiffi`

* Smaller API with one way to do something rather than different abstraction levels
* Designed to not allow invalid function signatures or ABIs, which may cause `libffi-rs` to return an error or panic

## Supported targets

The targets below are tested in `fiffi`'s CI tests. Additional targets may work, but are not tested
in CI; run the full test suite before using `fiffi` on another platform to ensure that everything
works as intended.

Development currently occurs mainly on x86_64 Linux and Windows.

* `aarch64-apple-darwin`
* `aarch64-pc-windows-gnullvm`
* `aarch64-pc-windows-msvc`
* `aarch64-unknown-linux-gnu`
* `armv7-unknown-linux-gnueabihf`
* `i686-pc-windows-gnu`
* `i686-pc-windows-msvc`
* `i686-unknown-linux-gnu`
* `loongarch64-unknown-linux-gnu`
* `powerpc-unknown-linux-gnu`
* `powerpc64-unknown-linux-gnu`
* `powerpc64le-unknown-linux-gnu`
* `riscv64gc-unknown-linux-gnu`
* `s390x-unknown-linux-gnu`
* `sparc64-unknown-linux-gnu`
* `x86_64-apple-darwin`
* `x86_64-pc-windows-gnu`
* `x86_64-pc-windows-gnullvm`
* `x86_64-pc-windows-msvc`
* `x86_64-unknown-linux-gnu`
* `x86_64-unknown-linux-musl`

[`libffi`]: https://sourceware.org/libffi/
[`libffi-rs`]: https://github.com/libffi-rs/libffi-rs/
[`libffi-sys-rs`]: https://crates.io/crates/libffi-sys/
[`miri`]: https://github.com/rust-lang/miri/
[`deno`]: https://github.com/denoland/deno/
[`libffi-rs-2-fiffi.md`]: ./libffi-rs-2-fiffi.md
[`Function`]: https://docs.rs/fiffi/latest/fiffi/function/struct.Function.html
[`Closure`]: https://docs.rs/fiffi/latest/fiffi/closure/struct.Closure.html
[`DynamicClosure`]: https://docs.rs/fiffi/latest/fiffi/closure/struct.DynamicClosure.html
[apache-badge]: https://img.shields.io/badge/license-Apache%20License%202.0-blue
[apache-url]: ./LICENSE-APACHE
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: ./LICENSE-MIT