![safer-ffi-banner](
https://github.com/getditto/safer_ffi/blob/banner/guide/assets/safer_ffi.jpg?raw=true)
[![CI](
https://github.com/getditto/safer_ffi/workflows/CI/badge.svg?branch=master)](
https://github.com/getditto/safer_ffi/actions)
[![guide](https://img.shields.io/badge/guide-mdbook-blue)](
https://getditto.github.io/safer_ffi)
[![docs-rs](https://docs.rs/safer-ffi/badge.svg)](
https://getditto.github.io/safer_ffi/rustdoc/safer_ffi)
[![crates-io](https://img.shields.io/crates/v/safer-ffi.svg)](
https://crates.io/crates/safer-ffi)
[![repository](https://img.shields.io/badge/repository-GitHub-brightgreen.svg)](
https://github.com/getditto/safer_ffi)
# 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.
> [š Read The User Guide š][user guide]
[user guide]: https://getditto.github.io/safer_ffi
## 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:
```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:
### Crate layout
#### Step 1: `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/`
]
[[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 running `cargo search safer-ffi`.
- See the [dedicated chapter on `Cargo.toml`][cargo-toml] for more info.
#### Step 2: `src/lib.rs`
Then, to export a Rust function to FFI, add the
[`#[derive_ReprC]`][derive_ReprC] and [`#[ffi_export]`][ffi_export] attributes
like so:
```rust ,no_run
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`][lib-rs] for more info.
#### Step 3: `src/bin/generate-headers.rs`
```rust ,ignore
fn main() -> ::std::io::Result<()> {
::crate_name::generate_headers()
}
```
### Compilation & header generation
```bash
# 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][header-generation] for
more info.
Generated C header (rust_points.h
)
```C
/*! \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
#include
/** \brief
* A `struct` usable from both Rust and C
*/
typedef struct Point {
/** */
double x;
/** */
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`
```C
#include
#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
```bash
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:
```bash
rustc --crate-type=staticlib --print=native-static-libs -&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 }
```
šš
[callbacks]: https://getditto.github.io/safer_ffi/callbacks/_.html
[cargo-toml]: https://getditto.github.io/safer_ffi/usage/cargo-toml.html
[ffi_export]: https://getditto.github.io/safer_ffi/ffi-export/_.html
[header-generation]: https://getditto.github.io/safer_ffi/usage/lib-rs.html#header-generation
[derive_ReprC]: https://getditto.github.io/safer_ffi/derive-reprc/_.html
[lib-rs]: https://getditto.github.io/safer_ffi/usage/lib-rs.html
## Development
### Tests
safer-ffi includes three different tests suites that can be run.
```bash
# 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
```