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.
Minimum Supported Rust Version: 1.66.1
Click to hide
You may try working with the examples/point
example embedded in the repo:
bash
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:
Cargo.toml
Edit your Cargo.toml
like so:
```toml [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/
]
[dependencies]
cargo add
or cargo search
to find the latest values of x.y.z.safer-ffi.version = "x.y.z" safer-ffi.features = [] # you may add some later on.
[features]
headers = ["safer-ffi/headers"] ```
Where "x.y.z"
ought to be replaced by the last released version, which you
can find by running cargo search safer-ffi
.
See the dedicated chapter on Cargo.toml
for more info.
src/lib.rs
Then, to export a Rust function to FFI, add the
#[derive_ReprC]
and #[ffi_export]
attributes
like so:
```rust ,norun use ::saferffi::prelude::*;
/// A struct
usable from both Rust and C
pub struct Point { x: f64, y: f64, }
/* Export a Rust function to the C world. */
/// Returns the middle point of [a, b]
.
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.
fn print_point(point: &Point) { println!("{:?}", point); }
// The following function is only necessary for the header generation.
Cargo.toml
sectionpub fn generateheaders() -> ::std::io::Result<()> { ::saferffi::headers::builder() .tofile("rustpoints.h")? .generate() } ```
src/lib.rs
for more info.src/bin/generate-headers.rs
rust ,ignore
fn main() -> ::std::io::Result<()> {
::crate_name::generate_headers()
}
```bash
target/{debug,release}/libcrate_name.ext
)cargo build # --release
cargo run --features headers --bin generate-headers ```
Generated C header (
rust_points.h
)
``C
/*! \file */
/*******************************************
* *
* File auto-generated by
::safer_ffi`. *
* *
* Do not manually edit this file. *
* *
*************/
extern "C" {
/* \brief
* A struct
usable from both Rust and C
*/
typedef struct Point {
/*
/** <No documentation available> */
double y;
} Point_t;
/** \brief
* Returns the middle point of [a, b]
.
*/
Pointt
midpoint (
Pointt const * a,
Pointt const * b);
/** \brief * Pretty-prints a point using Rust's formatting logic. */ void printpoint ( Pointt const * point);
} /* extern \"C\" */
```
Here is a basic example to showcase FFI calling into our exported Rust functions:
main.c
```C
int main (int argc, char const * const argv[]) { Pointt a = { .x = 84, .y = 45 }; Pointt b = { .x = 0, .y = 39 }; Pointt m = midpoint(&a, &b); printpoint(&m); return EXITSUCCESS; } ```
```bash cc -o main{,.c} -L target/debug -l crate_name -l{pthread,dl,m}
./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:
bash
rustc --crate-type=staticlib --print=native-static-libs -</dev/null
this yields, to the stderr, output along the lines of:
```text 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:
bash
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:
bash
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 as jq
:
bash
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:
bash
cc -o main{,.c} -L target/debug -l crate_name ${RUST_STDLIB_DEPS}
which does output:
text
Point { x: 42.0, y: 42.0 }
šš
safer-ffi includes three different tests suites that can be run.
```bash
cargo test
make -C ffi_tests
make -C js_tests
js_tests
directory, open a browser and navigate towasm-pack build --target web && python3 -m http.server 13337
```