Getting Started
This chapter wires ezffi into a fresh crate end to end — your first exported function and the C program that calls it. By the end you'll have a working Counter library callable from C.
Dependencies
ezffi ships the proc macro and some Rust types ready to be export-compatible; cbindgen reads the macro-generated extern "C" items and produces the C header. If you wanna use any other header generator feel free, but cbindgen is the one I'm using in my tests until I write my own solution if I see it's needed.
[package]
name = "counter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["lib", "staticlib"] # or `cdylib`, it depends on your use case
[dependencies]
ezffi = "0.1"
[build-dependencies]
cbindgen = "0.29"
The library
src/lib.rs:
#![allow(unused)] fn main() { #[ezffi::export] pub struct Counter { value: u64 } #[ezffi::export] impl Counter { pub fn new() -> Self { Self { value: 0 } } pub fn increment(&mut self) { self.value += 1; } pub fn value(&self) -> u64 { self.value } } }
Wiring cbindgen
The header generation is a really important step. ezffi generates its headers in target/<profile>/include/ezffi so it's really important to generate yours in the same include dir, so we end up with the following structure:
include
├── ezffi
│ ├── ezffi.h
│ ├── slice.h
│ ├── ...
│ └── string.h
└── counter
└── counter.h
The recommended build.rs using cbindgen is the following:
build.rs:
use std::env; use std::path::Path; fn main() { // cbindgen cannot expand macros in stable builds // unless you set this env var unsafe { std::env::set_var("RUSTC_BOOTSTRAP", "counter") }; let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let out_dir = env::var("OUT_DIR").unwrap(); let pkg_name = env::var("CARGO_PKG_NAME").unwrap(); println!("cargo:rerun-if-changed=src/"); println!("cargo:rerun-if-changed=cbindgen.toml"); // `OUT_DIR` is `target/<profile>/build/<crate>-<hash>/out` let target_dir = Path::new(&out_dir) .ancestors() .nth(3) .expect("Failed to find target dir"); let include_dir = target_dir.join("include").join(&pkg_name); std::fs::create_dir_all(&include_dir).unwrap(); let config = cbindgen::Config::from_file(Path::new(&crate_dir) .join("cbindgen.toml")) .expect("Failed to read cbindgen.toml"); cbindgen::Builder::new() .with_crate(&crate_dir) .with_config(config) .generate() .expect("Unable to generate bindings") .write_to_file(include_dir.join(format!("{pkg_name}.h"))); }
cbindgen.toml:
language = "C"
# Add here the relative .h path of any dependency you are
# using from `ezffi` or from other lib crates that expose
# an `ezffi` interface (e.g.: ezffi/string.h).
# In this case we don't use any of the `ezffi` exposed types
# so it is empty.
include = []
[parse.expand]
crates = ["counter"]
The generated header
After cargo build, target/debug/include/counter/counter.h looks roughly like:
#include <stdint.h>
typedef struct CounterCounter { uint64_t value; } CounterCounter;
CounterCounter counter_counter_new(void);
void counter_counter_increment(CounterCounter *this_);
uint64_t counter_counter_value(const CounterCounter *this_);
I talk more about why this is the header output, how it works internally and the rules I apply when creating C-compatible wrappers (and teach you what to expect from your code) in chapter #[ezffi::export].
The C side
main.c:
#include "counter/counter.h"
#include <stdio.h>
int main(void) {
CounterCounter c = counter_counter_new();
counter_counter_increment(&c);
counter_counter_increment(&c);
counter_counter_increment(&c);
printf("%llu\n", (unsigned long long)counter_counter_value(&c));
return 0;
}
Compile and run:
cargo build --release
gcc main.c \
-I target/release/include \
-L target/release \
-l counter \
-o counter_demo
./counter_demo
# 3
Depending on your binary you may also need other libraries to be linked like -lpthread -ldl -lm, otherwise you'll get a compile-time error warning you of missing symbols (e.g. pow if you don't link m but some dependency uses it).
And that's it — you got your first C-compatible library and a bin consumer without having to worry about any implementation details.