Course contentsShow
← Rust
Lesson 1627 of 1,91014. System Integration and EcosystemFree lesson

Structuring Complex Workspaces

You will manage multi-crate projects to isolate FFI logic from core business domain code.

In this lesson, you will learn how to architect a multi-crate workspace to cleanly decouple volatile FFI integration logic from your pure Rust business domain.

The Problem of Monolithic FFI

When integrating with C libraries, the temptation is to mix low-level pointer arithmetic and raw extern "C" declarations directly into your application code. This creates a "leaky abstraction" where safety, error handling, and domain logic become tangled. In a complex workspace, this makes unit testing business logic difficult because you are forced to mock or initialize heavy C dependencies just to run a test.

Decoupling via Workspace Crates

To solve this, we structure our workspace into three logical tiers:

  1. sys Crate: Contains only the auto-generated bindgen code and minimal link metadata. It exposes raw pointers and unsafe primitives.
  2. adapter Crate: Wraps the sys crate. It transforms raw C types into idiomatic Rust types, manages lifetimes, and enforces safety wrappers. Crucially, this is the only crate that knows the C library exists.
  3. domain Crate: Contains your core logic. It consumes traits defined by the adapter crate. By using dependency inversion, the domain logic operates on interfaces rather than concrete C-based implementations.

Example Workspace Structure

# Cargo.toml (root)
[workspace]
members = ["crates/lib-sys", "crates/lib-adapter", "crates/core-logic"]

Inside your core-logic, you should never see a *mut c_void. Instead, you define a trait:

// In core-logic/src/lib.rs
pub trait DataProcessor {
    fn process(&self, data: &[u8]) -> Result<Vec<u8>, DomainError>;
}

The adapter crate then implements this trait by calling the FFI functions defined in the sys crate. Because the adapter acts as a facade, the core-logic remains "FFI-agnostic." You can swap the implementation or mock the trait in tests without ever invoking a C linker.

This separation prevents FFI-induced instability from bleeding into your architectural core. By isolating the unsafe surface area into a dedicated crate, you ensure that compiler-enforced safety reaches as deep into the system as possible, keeping your domain logic clean and portable.

Key Takeaway: Isolate FFI code into dedicated "sys" and "adapter" crates to decouple raw pointers from your business logic, enabling testability and architectural clarity.