FFI

Efficiency!

(This is Germany after all)

» efficient C bindings «

Application Binary Interface

(Like an API, but for machine code calling machine code)

Rust ABI is not stable.

Rust also supports the platform-ABI(s).

(Windows has two…​)

CPUs have registers, and they have a pointer to the stack (in RAM)

Where does this function find its arguments? Where does the return value go?

fn hello(param1: i32, param2: f64) -> SomeStruct { ... }

Your Rust code might want to interact with shared/static libraries.

Or be one.

» efficient C bindings «

There are no conversion costs

Using Rust from C

We have this amazing Rust library, we want to use in our existing C project.

struct MagicAdder {
	amount: u32
}

impl MagicAdder {
	fn new(amount: u32) -> MagicAdder {
		MagicAdder {
			amount
		}
	}

	fn process_value(&self, value: u32) -> u32 {
		self.amount + value
	}
}

Things TODO

  • Tell C these functions exist

  • Tell Rust to use C-compatible types and functions

  • Link the external code as a library

  • Provide some C types that match the Rust types

  • Call our Rust functions

C-flavoured Rust Code

#[repr(C)]
struct MagicAdder {
	amount: u32
}

impl MagicAdder { /* Snip... */ }

#[no_mangle]
extern "C" fn magicadder_new(amount: u32) -> MagicAdder {
	MagicAdder::new(amount)
}

#[no_mangle]
extern "C" fn magicadder_process_value(adder: *const MagicAdder, value: u32) -> u32 {
	if let Some(ma) = unsafe { adder.as_ref() } {
		ma.process_value(value)
	} else {
		0
	}
}

Matching C header

/// Designed to have the exact same shape as the Rust version
typedef struct magic_adder_t {
	uint32_t amount;
} magic_adder_t;

/// Wraps MagicAdder::new
magic_adder_t magicadder_new(uint32_t amount);

/// Wraps MagicAdder::process_value
uint32_t magicadder_process_value(magic_adder_t* self, uint32_t value);

Making a library

You can tell rustc to make:

  • binaries (bin)

  • libraries (lib)

    • rlib

    • dylib

    • staticlib

    • cdylib

Cargo.toml

[package]
name = "magic_adder"
version = "1.0.0"
edition = "2021"

[lib]
crate-type = ["lib", "staticlib", "cdylib"]

Using C from Rust

We have this amazing C library, we want to use as-is in our Rust project.

#include <stdint.h>

/** Do some amazing maths */
uint32_t cool_library_function(uint32_t x, uint32_t y);
#include <stdio.h>
#include "hello.h"

uint32_t cool_library_function(uint32_t x, uint32_t y)
{
    // I know, right? Amazing.
    return x + y;
}

Things TODO

  • Tell Rust these functions exist

  • Link the external code as a library

  • Call those with unsafe { …​ }

  • Transmute data for C functions

Naming things is hard

#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)]

Disables some Rust naming lints

Binding functions

#include <stdint.h>

/** Do some amazing maths */
uint32_t cool_library_function(uint32_t x, uint32_t y);
use std::os::raw::{c_uint, c_char};

extern "C" {
    // We state that this function exists, but there's no definition.
    // The linker looks for this 'symbol name' in the other objects
    fn cool_library_function(x: c_uint, y: c_uint) -> c_uint;
}

Primitive types

Some type conversions can be infered by the compiler.

  • c_uintu32

  • c_inti32

  • c_charu8 (not char!)

  • c_void()

  • …​etc…​

Calling this

use std::os::raw::c_uint;

extern "C" {
    fn cool_library_function(x: c_uint, y: c_uint) -> c_uint;
}

fn main() {
    let result: u32 = unsafe {
        cool_library_function(6, 7)
    };
    println!("{} should be 13", result);
}

Some more specific details…​

Cargo (build-system) support

  • Build native code via build-dependency crates

  • build.rs can give linker extra arguments

Opaque types

When not knowing (or caring) about internal layout, opaque structs can be used.

/// This is like a 'struct FoobarContext;' in C
#[repr(C)]
pub struct FoobarContext { _priv: [i32; 0] }

extern "C" {
	fn foobar_init() -> *mut FoobarContext;
	fn foobar_do(ctx: *mut FoobarContext, foo: i32);
	fn foobar_destroy(ctx: *mut FoobarContext);
}

/// Use this in your Rust code
pub struct FoobarHandle(*mut FoobarContext);

Callbacks

extern "C" applies to function pointers given to extern functions too.

use std::os::raw::c_void;

pub type FooCallback = extern "C" fn(state: *mut c_void);

extern "C" {
    pub fn libfoo_register_callback(state: *mut c_void, cb: FooCallback);
}

extern "C" fn my_callback(_state: *mut c_void) {
    // Do stuff here
}

fn main() {
    unsafe { libfoo_register_callback(core::ptr::null_mut(), my_callback); }
}

But this is a lot of manual work?

There’s a better way!

Making C headers from Rust

Making Rust source from C headers

Loading auto-generated Rust source

#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
pub mod bindings {
    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

Calling these tools:

  • On the command line

  • Executing a command in build.rs

  • Calling a library function in build.rs

sys crates

xxxx-sys is a Rust crate that provides a thin wrapper around some C library xxxx.

You normally have a higher-level xxxx crate that provides a Rust interface