Expand description
What is safer_ffi
?
safer_ffi
is a framework that helps you write foreign function interfaces (FFI) without polluting your Rust code with unsafe { ... }
code blocks while making functions far easier to read and maintain.
Prerequisites
Minimum Supported Rust Version: 1.66.1
Quickstart
Click to hide
Small self-contained demo
You may try working with the examples/point
example embedded in the repo:
git clone https://github.com/getditto/safer_ffi && cd safer_ffi
(cd examples/point && make)
Otherwise, to start using ::safer_ffi
, follow the following steps:
Crate layout
Step 1: Cargo.toml
Edit your Cargo.toml
like so:
[package]
name = "crate_name"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = [
"staticlib", # Ensure it gets compiled as a (static) C library
# "cdylib", # If you want a shared/dynamic C library (advanced)
"lib", # For `generate-headers` and other downstream rust dependents
# such as integration `tests/`, doctests, and `examples/`
]
[[bin]]
name = "generate-headers"
required-features = ["headers"] # Do not build unless generating headers.
[dependencies]
# Use `cargo add` or `cargo search` to find the latest values of x.y.z.
# For instance:
# cargo add safer-ffi
safer-ffi.version = "x.y.z"
safer-ffi.features = [] # you may add some later on.
[features]
# If you want to generate the headers, use a feature-gate
# to opt into doing so:
headers = ["safer-ffi/headers"]
-
Where
"x.y.z"
ought to be replaced by the last released version, which you can find by runningcargo search safer-ffi
. -
See the dedicated chapter on
Cargo.toml
for more info.
Step 2: src/lib.rs
Then, to export a Rust function to FFI, add the
#[derive_ReprC]
and #[ffi_export]
attributes
like so:
use ::safer_ffi::prelude::*;
/// A `struct` usable from both Rust and C
#[derive_ReprC]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Point {
x: f64,
y: f64,
}
/* Export a Rust function to the C world. */
/// Returns the middle point of `[a, b]`.
#[ffi_export]
fn mid_point(a: &Point, b: &Point) -> Point {
Point {
x: (a.x + b.x) / 2.,
y: (a.y + b.y) / 2.,
}
}
/// Pretty-prints a point using Rust's formatting logic.
#[ffi_export]
fn print_point(point: &Point) {
println!("{:?}", point);
}
// The following function is only necessary for the header generation.
#[cfg(feature = "headers")] // c.f. the `Cargo.toml` section
pub fn generate_headers() -> ::std::io::Result<()> {
::safer_ffi::headers::builder()
.to_file("rust_points.h")?
.generate()
}
- See the dedicated chapter on
src/lib.rs
for more info.
Step 3: src/bin/generate-headers.rs
fn main() -> ::std::io::Result<()> {
::crate_name::generate_headers()
}
Compilation & header generation
# Compile the C library (in `target/{debug,release}/libcrate_name.ext`)
cargo build # --release
# Generate the C header
cargo run --features headers --bin generate-headers
- See the dedicated chapter on header generation for more info.
Generated C header (rust_points.h
)
/*! \file */
/*******************************************
* *
* File auto-generated by `::safer_ffi`. *
* *
* Do not manually edit this file. *
* *
*******************************************/
#ifndef __RUST_CRATE_NAME__
#define __RUST_CRATE_NAME__
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
/** \brief
* A `struct` usable from both Rust and C
*/
typedef struct Point {
/** <No documentation available> */
double x;
/** <No documentation available> */
double y;
} Point_t;
/** \brief
* Returns the middle point of `[a, b]`.
*/
Point_t
mid_point (
Point_t const * a,
Point_t const * b);
/** \brief
* Pretty-prints a point using Rust's formatting logic.
*/
void
print_point (
Point_t const * point);
#ifdef __cplusplus
} /* extern \"C\" */
#endif
#endif /* __RUST_CRATE_NAME__ */
Testing it from C
Here is a basic example to showcase FFI calling into our exported Rust functions:
main.c
#include <stdlib.h>
#include "rust_points.h"
int
main (int argc, char const * const argv[])
{
Point_t a = { .x = 84, .y = 45 };
Point_t b = { .x = 0, .y = 39 };
Point_t m = mid_point(&a, &b);
print_point(&m);
return EXIT_SUCCESS;
}
Compilation command
cc -o main{,.c} -L target/debug -l crate_name -l{pthread,dl,m}
# Now feel free to run the compiled binary
./main
Note regarding the extra
-l…
flags.Those vary based on the version of the Rust standard library being used, and the system being used to compile it. In order to reliably know which ones to use,
rustc
itself ought to be queried for it.Simple command:
rustc --crate-type=staticlib --print=native-static-libs -</dev/null
this yields, to the stderr, output along the lines of:
note: Link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms. note: native-static-libs: -lSystem -lresolv -lc -lm -liconv
Using something like
sed -nE 's/^note: native-static-libs: (.*)/\1/p'
is thus a convenient way to extract these flags:rustc --crate-type=staticlib --print=native-static-libs -</dev/null \ 2>&1 | sed -nE 's/^note: native-static-libs: (.*)/\1/p'
Ideally, you would not query for this information in a vacuum (e.g.,
/dev/null
file being used as input Rust code just above), and rather, would apply it for your actual code being compiled:cargo rustc -q -- --print=native-static-libs \ 2>&1 | sed -nE 's/^note: native-static-libs: (.*)/\1/p'
And if you really wanted to polish things further, you could use the JSON-formatted compiler output (this, for instance, avoids having to redirect
stderr
). But then you’d have to use a JSON parser, such asjq
:RUST_STDLIB_DEPS=$(set -eo pipefail && \ cargo rustc \ --message-format=json \ -- --print=native-static-libs \ | jq -r ' select (.reason == "compiler-message") | .message.message ' | sed -nE 's/^native-static-libs: (.*)/\1/p' \ )
and then use:
cc -o main{,.c} -L target/debug -l crate_name ${RUST_STDLIB_DEPS}
which does output:
Point { x: 42.0, y: 42.0 }
🚀🚀
Development
Tests
safer-ffi includes three different tests suites that can be run.
# In the project root:
cargo test
# FFI tests
make -C ffi_tests
# JavaScript tests
make -C js_tests
# Running the JS tests also gives you instructions for running browser tests.
# Run this command in the `js_tests` directory, open a browser and navigate to
# http://localhost:13337/
wasm-pack build --target web && python3 -m http.server 13337
Re-exports
Modules
- boxed
alloc
#[repr(C)]
Box
ed types. char *
-compatible strings (slim pointers), for easier use from within C. They thus do not support inner nulls, nor string appending.- Closures with a
#[repr(C)]
layout (inlined vtable), up to 9 function arguments. - dyn_traits
dyn-traits
- headers
headers
C headers generation. - Trait abstractions describing the semantics of “being
#[repr(C)]
” - On certain platforms,
::libc
has no definitions for pervasive types such assize_t
. - Wrappers around
NonNull
to better express the semantics of such pointer. - Logic common to all fat pointers.
Rust
string types with a defined#[repr(C)]
layout, albeit notchar *
compatible (fat pointers).- Tuple types with a guaranteed
#[repr(C)]
layout. - vec
alloc
Macros
- Transitioning helper macro: still uses the old
ReprC!
syntax, but just to forward to the new#[derive_ReprC2($(js)?)]
one. - Creates a compile-time checked
char_p::Ref
<'static>
out of a string literal. - c_vec
alloc
- ffi_export_future_helpers
futures
anddyn-traits
Structs
- String
alloc
Same asString
, but with guaranteed#[repr(C)]
layout - Vec
alloc
Same asVec<T>
, but with guaranteed#[repr(C)]
layout - A
ReprC
standalone type with the same layout and ABI as::libc::c_char
. - A
ReprC
standalone type with the same layout and ABI as::libc::c_int
.
Attribute Macros
- Identity macro when
feature = "headers"
is enabled, otherwise this macro outputs nothing. - Export a function to be callable by C.