We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Macros, FFI and Type-Driven Design step 24 of 28
Export Rust functions with C-compatible names and signatures
Fix two exported functions and call them through foreign declarations in the same Rust program. This practices C-compatible function signatures and symbol names without requiring a separate C build.
pub fn run(xs: Vec<i32>) -> (i64, i32)
Return the sum of all elements as an i64, followed by the first element
clamped to -10..=10. For an empty vector, return (0, 0). Preserve the supplied
function signatures and use the foreign declarations for the calls in run.
Inputs are limited to those whose running sum fits in i64.
| Input | Sum | Clamped first element |
|---|---|---|
[] |
0 |
0 |
[1, 2, 3] |
6 |
1 |
[42] |
42 |
10 |
[-50, -50] |
-100 |
-10 |
The harness reports the tuple as JSON fields sum and clamped. Output tests
check these values; they do not prove that a separate C program can link to your
code or that you used the requested declarations. Submissions must also compile
and pass Clippy.
Calling convention and symbol name are separate
extern "C" selects the target platform's C calling convention. It does not
choose the function's linker name. #[unsafe(no_mangle)] exports the function
using its Rust identifier instead of a Rust-mangled name.
#[unsafe(export_name = "cracked_clamp")] chooses a specific exported name.
Here, Rust code defines rust_clamp, while the foreign declaration refers to
cracked_clamp. Object-file tools may display platform-specific decorations
around exported names.
These declarations use the C ABI, but both implementations are still Rust. The optimizer may inline or simplify calls, so this example does not guarantee a visible call instruction or a round trip through separately compiled C code.
Fix the unsafe attribute
Edition 2024 requires #[unsafe(no_mangle)] rather than bare #[no_mangle].
The same wrapper is required for export_name and link_section.
Exported names can collide with names from other linked code. Depending on the platform and linking setup, a collision may cause a linker error or resolve to an unintended implementation. The compiler cannot establish that your name is unique across the whole program. Use distinct names and document that assumption. The attribute wrapper records this responsibility; it does not verify it.
cargo fix --edition can update the syntax, but it cannot establish safety.
Also keep extern "C": suppressing name mangling alone does not make a Rust-ABI
function callable from C. Export concrete functions rather than relying on
no_mangle to provide one symbol for a generic function.
Fix the duplicate Rust name
The starter defines rust_sum and declares another item named rust_sum in
the same module. Rust reports E0428 before the linker can resolve anything.
Move the foreign declarations into a nested c_side module. Calls such as
c_side::rust_sum(...) then refer to the declaration, while the definition
remains in the parent module. The separate Rust paths still refer to the same
exported symbol. Import c_int inside the nested module as needed.
rust_clamp and its exported name cracked_clamp already differ, so those
two names do not create the same Rust-level collision.
Respect the pointer contract
The sum function receives an element pointer and a length, not a Rust slice.
For non-null, nonempty input, from_raw_parts requires aligned, initialized
elements in a single allocation. The memory must remain readable and unmodified
while the borrowed slice is used. Its byte size must fit in isize::MAX, and
the range must not wrap around the address space.
The supplied function returns zero before creating a slice when the pointer
is null or the length is zero. That branch matters: from_raw_parts itself
requires a non-null, aligned pointer even for an empty slice. In run, the
vector owns the nonempty input and stays alive throughout the call.
The foreign signatures use C-compatible scalar and pointer types. Do not pass
Vec, Rust tuples or &str directly across this boundary. The tuple returned
by run is fine because run is an ordinary Rust function, not an export.
Keep panics inside Rust
A Rust panic that would unwind across an extern "C" boundary aborts the
process. With panic=abort, a panic aborts immediately. Avoid known panic paths:
the supplied clamp function returns the original value when lo > hi, rather
than calling clamp with an invalid range.
An API can use catch_unwind inside its boundary and translate an unwinding
panic into an error result, but it cannot catch aborting panics or repair an
invalid pointer. That is not needed for the supported inputs in this exercise.
Check your understanding Why does an unsafe function still contain an unsafe block? Show answer Hide answer
The function declaration gives callers a safety contract to uphold. The block marks an operation whose preconditions the implementation must justify.
In edition 2024, unsafe_op_in_unsafe_fn warns by default when such an operation
has no explicit block. This site's -D warnings check makes that warning fail
grading. It is not a deny-by-default language rule. Keep the block around
from_raw_parts and explain why its pointer requirements hold.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.