- Notifications
You must be signed in to change notification settings - Fork43
Write safer FFI code in Rust without polluting it with unsafe code
License
getditto/safer_ffi
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
safer_ffi
is a framework that helps you write foreign function interfaces (FFI) without polluting your Rust code withunsafe { ... }
code blocks while making functions far easier to read and maintain.
Minimum Supported Rust Version:1.66.1
Click to hide
You may try working with theexamples/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:
Edit yourCargo.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-ffisafer-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 youcan find by runningcargo search safer-ffi
.See thededicated chapter on
Cargo.toml
for more info.
Then, to export a Rust function to FFI, add the#[derive_ReprC]
and#[ffi_export]
attributeslike so:
use::safer_ffi::prelude::*;/// A `struct` usable from both Rust and C#[derive_ReprC]#[repr(C)]#[derive(Debug,Clone,Copy)]pubstructPoint{x:f64,y:f64,}/* Export a Rust function to the C world. *//// Returns the middle point of `[a, b]`.#[ffi_export]fnmid_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]fnprint_point(point:&Point){println!("{:?}", point);}// The following function is only necessary for the header generation.#[cfg(feature ="headers")]// c.f. the `Cargo.toml` sectionpubfngenerate_headers() ->::std::io::Result<()>{::safer_ffi::headers::builder().to_file("rust_points.h")?.generate()}
- Seethe dedicated chapter on
src/lib.rs
for more info.
fnmain() ->::std::io::Result<()>{::crate_name::generate_headers()}
# Compile the C library (in `target/{debug,release}/libcrate_name.ext`)cargo build# --release# Generate the C headercargo run --features headers --bin generate-headers
- Seethe dedicated chapter on header generation formore 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__cplusplusextern"C" {#endif#include<stddef.h>#include<stdint.h>/** \brief * A `struct` usable from both Rust and C */typedefstructPoint {/** <No documentation available> */doublex;/** <No documentation available> */doubley;}Point_t;/** \brief * Returns the middle point of `[a, b]`. */Point_tmid_point (Point_tconst*a,Point_tconst*b);/** \brief * Pretty-prints a point using Rust's formatting logic. */voidprint_point (Point_tconst*point);#ifdef__cplusplus}/* extern \"C\" */#endif#endif/* __RUST_CRATE_NAME__ */
Here is a basic example to showcase FFI calling into our exported Rustfunctions:
#include<stdlib.h>#include"rust_points.h"intmain (intargc,charconst*constargv[]){Point_ta= { .x=84, .y=45 };Point_tb= { .x=0, .y=39 };Point_tm=mid_point(&a,&b);print_point(&m);returnEXIT_SUCCESS;}
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, andthe system being used to compile it. In order to reliably know which ones touse,
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'
isthus 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 informationin 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 theJSON-formatted compiler output (this, for instance, avoids having toredirect
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 }
🚀🚀
safer-ffi includes three different tests suites that can be run.
# In the project root:cargotest# FFI testsmake -C ffi_tests# JavaScript testsmake -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
About
Write safer FFI code in Rust without polluting it with unsafe code