chapter eight

8 Rust-C Integration and Migration

 

This chapter covers

  • What an Application Binary Interface (ABI) promises, and how the AAPCS shapes every Rust-C call on Cortex-M
  • Calling C from Rust: type mapping, #[repr] discipline, safe wrappers, and linking
  • Calling Rust from C: exported symbols, no_std static libraries, and error codes that cross the boundary
  • The hard cases: buffers, strings, and callbacks with context
  • Automating both directions with bindgen and cbindgen, aimed at the right target
  • Incrementally migrating an existing C firmware codebase to Rust

Nearly every embedded Rust project eventually runs into someone else’s C: a vendor’s HAL, a protocol stack nobody has time to reimplement, an RTOS kernel that already runs on the board. Rust’s usual promises, memory safety, a strict compiler, don’t extend across a language boundary automatically; the moment C and Rust need to share a function call or a piece of memory, you’re responsible for making the two sides agree by hand, using the tools this chapter covers.

8.1 Understanding Rust FFI & ABI

8.1.1 Setting up the bridgeheads

8.1.2 The ABI and the procedure call standard

8.2 Calling C from Rust

8.2.1 Naming conventions and the ffi module

8.2.2 Scalar type compatibility

8.2.3 Struct layout and alignment

8.2.4 Enums, transparent wrappers, and layout lints

8.2.5 Encapsulating unsafe behind safe interfaces

8.2.6 Linking C code: direct and indirect

8.3 Calling Rust from C

8.3.1 Exporting functions and data

8.3.2 A minimal no_std static library

8.3.3 Consuming the library from C

8.3.4 Error propagation with integer codes

8.4 Advanced FFI topics

8.4.1 Arrays and C strings

8.4.2 Callbacks from C into Rust

8.4.3 Callbacks with context

8.5 Automating the interface: bindgen and cbindgen

8.5.1 bindgen: from C headers to Rust