ABI

4 Posts

Static Libraries, Dynamic Libraries, and ABI

6 minute

The previous FFI articles focused on function-level boundaries: extern "C", strings, memory ownership, callbacks, and void *ctx.

When delivering Rust to a C project, there is another layer:

What file does the C project link?
What symbols are declared in the header?
Can those symbols keep a stable ABI?
After Rust internals change, does C need to change?

C developers know .a, .so, .h, and ABI. Rust delivered to C should return to those concepts instead of exposing Cargo internals.

Read More

Structs and Memory Layout

6 minute

C developers often read struct as a memory layout:

#include <stdint.h>

struct Header {
    uint8_t version;
    uint32_t length;
    uint16_t flags;
};

Fields are laid out in declaration order, padding may be inserted, and the whole struct has an alignment requirement. That mental model matters for protocols, file formats, register maps, and FFI.

Rust also has struct, but the first split is this:

normal Rust struct: describes an internal data model
#[repr(C)] struct: describes a layout that must match the C ABI

The keyword is familiar, but the default layout contract is not the same. In ordinary Rust code, treat a struct as named fields. Bring memory layout into the type only when an ABI or binary boundary needs it.

Read More

FFI: Letting Rust Call C

6 minute

Calling C from Rust can look like just writing an extern "C" declaration.

The real issue is different: a C function does not carry Rust’s borrowing, lifetime, Result, or destructor rules. Rust can call it, but the compiler cannot prove that the call satisfies the C API contract.

A better model is:

safe Rust API
-> validate and prepare arguments
-> call C inside a small unsafe block
-> convert error codes, output parameters, and raw pointers back to Rust types

The C header is the entry point

Suppose an existing C library exposes this header:

Read More

FFI: Letting C Call Rust

5 minute

Letting C call Rust does not mean exposing ordinary Rust functions directly to C.

C understands C ABI: symbol names, calling conventions, integers, pointers, struct layout, memory ownership, release functions, and error returns. Rust internals such as String, Vec, Result, traits, and panic should not cross the C boundary directly.

A stable model is:

C call
-> extern "C" wrapper
-> validate pointer and length
-> convert to safe Rust types
-> call internal Rust logic
-> convert result to C-compatible return values

This article covers C calling Rust. Rust calling C comes next.

Read More