Architecture Overview
NextStd is not a traditional C library. It is a hybrid architecture designed
to seamlessly bridge the gap between two radically different languages.
To a developer using the library, NextStd looks and feels exactly like modern
C. They include a header, call a macro, and their code compiles cleanly. Under
the hood, however, there is almost no C execution happening. The C code acts
entirely as a lightweight routing layer that safely passes data across a Foreign
Function Interface (FFI) boundary into a highly optimized, memory-safe Rust
engine.
The Three Pillars of NextStd
Understanding how data flows through the library requires looking at its three distinct layers.
1. The C Front-End (include/nextstd/)
This is the only part of the library the end-user ever sees. It consists entirely of header files containing:
- Memory-Layout Structs: C structs (like
ns_stringorns_vec) that define the exact byte layout of the data so the C compiler knows how much memory to reserve on the stack. _GenericMacros: The secret weapon ofNextStd. Because C does not support function overloading, we use C11_Genericmacros to inspect the type of a variable at compile time and automatically route it to the correct underlying function (e.g., routing aninttons_print_intand anns_stringtons_print_ns_string).- Error Handling Macros: The
NS_TRYandNS_EXCEPTmacros that evaluate thens_error_tenums returned by the backend.
2. The FFI Boundary
The Foreign Function Interface (FFI) is the bridge between C and Rust. Because Rust and C manage memory differently, crossing this bridge requires strict rules:
- No C++ Magic: Everything must use standard C ABI (Application Binary Interface).
- Pointer Passing: When C needs Rust to modify a struct (like allocating
heap memory for a new string), C passes a raw pointer (
ns_string*) across the boundary. - Universal Error Currency: Rust functions never crash or “panic” across the
boundary. Every fallible operation returns an
ns_error_tenum back to C.
3. The Rust Back-End (crates/)
This is where the actual logic lives. The Rust backend exposes functions marked
with #[unsafe(no_mangle)] pub extern "C".
- The “Unsafe” Gateway: When Rust receives a raw pointer from C, it must
enter an
unsafe {}block to dereference it. - The Safe Execution: Once the raw C data is converted into safe Rust types
(like turning a
*const c_charinto aCStr), the rest of the execution happens in 100% safe Rust. Rust handles the dynamic memory allocation, the string formatting, and the bounds checking. - The Clean Return: Rust updates the memory at the C pointer address,
converts any internal Rust errors into an
ns_error_t, and cleanly returns control to C.
The Lifecycle of a Function Call
To picture how this all comes together, let’s trace exactly what happens when a
user calls ns_read(&my_text):
- Compile Time (C Front-End): The C preprocessor evaluates
ns_read(&my_text). It sees that&my_textis anns_string*type. Using the_Genericmacro, it replaces the code with a call tons_read_ns_string(&my_text). - Execution (Crossing the FFI): The compiled C program jumps to the memory
address of
ns_read_ns_string. - Rust Takes Over (Back-End): The Rust function
pub extern "C" fn ns_read_ns_string(ptr: *mut NsString)wakes up. It safely reads dynamic input from the terminal using Rust’sstd::io. - Memory Allocation: Rust calculates the exact size of the input, allocates
the necessary heap memory, and carefully writes the pointer addresses and
capacity sizes directly into the
ptrstruct. - Return: Rust returns
NsError::Success(which maps perfectly toNS_ERROR_SUCCESSin C). Control is handed back to the C program.
By keeping the C layer incredibly thin and offloading all the complex logic to
Rust, NextStd guarantees memory safety without sacrificing the ease of use of
a C library.