We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← The Expert Edge: Idiom, Review and Capstones step 3 of 14
A macro-generated FFI binding layer
Use macro_rules! to generate C declarations and Rust wrappers. The macro
removes repeated code; you still need to decide which calls are safe and
establish the foreign functions' preconditions.
Your task
Complete bind_c! with safe and raw arms, remove the ffi! convenience
macro, and replace the hand-written bindings with:
bind_c! {
safe sqrt(x: c_double) -> c_double => c_sqrt;
raw abs(x: c_int) -> c_int => c_abs;
raw strlen(s: *const c_char) -> usize => c_strlen;
raw memcmp(a: *const c_void, b: *const c_void, n: usize) -> c_int => c_memcmp;
}
Each entry must generate an unsafe extern "C" declaration with the given
name and types, plus a wrapper with the name after =>.
safegenerates apub fnthat calls the foreign function inside an explicitunsafeblock.rawgenerates apub unsafe fn, also with an explicit unsafe call in its body. Its documentation must include a# Safetysection explaining the caller's obligations.
Keep the supplied safe helper functions and the run entry point. The
harness also invokes the macro for a raw labs binding, so both macro arms
must work. The incomplete starter cannot satisfy that harness.
Results to preserve
pub fn run(xs: Vec<i32>, s: String) -> (i64, f64, usize, bool)
| Result | Meaning |
|---|---|
sum |
Sum of absolute values in xs, accumulated as i64. |
root |
C sqrt(sum as f64). |
len |
Number of bytes before the first NUL byte in s, or its full byte length if there is none. |
same |
Whether all bytes of s equal their reversal, including any interior NUL bytes. |
For xs = [-3, 4, -5] and s = "aba", the result is approximately
(12, 3.4641016151377544, 3, true). For s = "ab\0ba", the C-string
length is 2, but the palindrome comparison uses all five bytes. An empty
string has length zero and is a byte palindrome. A Unicode character
palindrome need not be a byte palindrome. The driver accepts either s or
a valid UTF-8 s_bytes array, which lets fixtures represent interior NULs.
A scalar argument can still need a safety check
C's abs cannot represent the absolute value of its most-negative int.
The helper handles c_int::MIN after widening to i64, without calling C
for that input. labs has the corresponding restriction for long; the
harness uses a raw wrapper and calls it with a known representable value.
The safe keyword is a claim made by the binding author, not a proof checked
by the macro. For this thin wrapper, callers must not need extra safety
conditions beyond supplying valid Rust arguments. Correct foreign signatures,
linking and any global-state requirements still need review. A floating-point
domain error, such as a negative square root, is not by itself undefined
behavior; it is distinct from the integer overflow restriction above.
Keep pointer checks in the safe helpers
strlen_of copies the bytes into a buffer, appends a NUL and keeps the buffer
alive through strlen. An interior NUL is allowed here because the requested
result is the length of the first C-string prefix.
bytes_equal checks lengths, returns early for empty slices, and then passes
valid readable ranges to memcmp. It compares bytes, not Rust strings, so
the reversed buffer does not need to be valid UTF-8.
These helpers establish the concrete preconditions that a general binding macro cannot infer from argument types alone.
Generate items and document the contract
Match one entry followed by $($rest:tt)*, emit its declaration and wrapper,
then process the remaining entries. An empty arm () => {}; handles the end
of the list. Identifiers capture names; ty fragments capture types.
#[doc = concat!("Wraps C ", stringify!($name), ".")] can name the
function in generated documentation. A # Safety heading satisfies the
documentation structure, but its text must still explain the actual contract.
For example, strlen needs a readable NUL-terminated sequence, whereas
memcmp needs readable ranges of the supplied length.
The starter's ffi!($call) places arbitrary caller expressions inside an
unsafe block. Clippy rejects this pattern and also rejects public unsafe
functions without safety documentation. Generate a specific foreign call
from the captured declaration instead of suppressing those lints. Passing
Clippy does not establish that a binding is sound.
Match the target C ABI exactly. The supplied types use std::ffi aliases,
pointers, and usize for size_t on the supported targets. *const c_void
is an opaque pointer type; c_void is not a general by-value C argument.
String and &str are not substitutes for C strings. FFI-compatible structs
can use repr(C), but their fields must also have suitable representations.
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.