We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Macros, FFI and Type-Driven Design step 22 of 28
Calling C functions through safe Rust wrappers
Call C's toupper, abs and strlen through safe Rust functions.
Fix the unsafe call in shout, then implement the other two wrappers.
pub fn shout(s: &str) -> String
pub fn abs_sum(xs: Vec<i32>) -> i64
pub fn c_strlen_of(bytes: Vec<u8>) -> usize
Required behavior
shoutuppercases ASCII lowercase letters using C'stoupper. Preserve other ASCII bytes and all non-ASCII UTF-8 bytes. For example,"héllo"becomes"HéLLO", not"HÉLLO". Empty text stays empty, and embedded zero bytes are preserved because this function processes individual bytes.abs_sumsums the absolute values into ani64. Call C'sabsfor every value excepti32::MIN, whose magnitude must be computed in Rust usingi64. Empty input returns zero; duplicates contribute separately. Inputs are chosen so the total fits ini64.c_strlen_ofappends a zero byte and calls C'sstrlen. Return the number of bytes before the first zero, whether that zero was already present or was appended. Empty input returns zero. Bytes need not be valid UTF-8.
Keep all three wrapper functions safe. Establish each C function's
preconditions inside the wrapper and explain them in a // SAFETY: comment.
The output tests check results; they do not prove that an implementation
actually called C or that every unsafe operation is sound.
Platform assumptions
This exercise targets hosted systems with a C runtime, eight-bit bytes and
a 32-bit C int. It also assumes the initial "C" character locale remains
unchanged. The three symbols used here are available through the C runtime
in ordinary desktop Rust builds, so the starter needs no additional crate
or build script on those targets.
This is not a guarantee for every Rust target or every C function. Embedded
environments may have no C runtime, and other libraries may need explicit
linking. In larger projects, generated bindings or the libc crate help
supply platform-specific declarations instead of writing them by hand.
What an extern declaration promises
The starter declares the functions in an unsafe extern "C" block. The
"C" ABI selects the target's C calling convention. Edition 2024 requires
the block to be marked unsafe, acknowledging that Rust cannot verify
these declarations against the foreign implementation.
Use c_int and c_char for the corresponding C types. These aliases follow
the target; the signedness of C char, for example, varies by platform.
The strlen declaration uses usize for C's size_t on the targets of
this exercise.
A declaration must match the foreign function's ABI and argument/return types. The compiler does not automatically consult C headers. It may catch some inconsistencies, but accepting a declaration does not prove that a later call is valid.
Functions in an extern block are unsafe to call unless explicitly declared
safe. The starter's call therefore produces E0133. This is not fixed by
syntax alone: check the input contract, then use an unsafe block around
the call. Unsafe foreign calls needed an unsafe context before edition
2024 too; the required unsafe block declaration is the edition change.
toupper: valid input is not the same as Unicode support
C's toupper accepts EOF or a value representable as unsigned char.
A non-ASCII byte promoted from u8 to c_int is within that domain on
these targets. The dangerous conversion is sign-extending a negative
char value that is not EOF.
We restrict conversion to ASCII here to preserve the bytes of multibyte
UTF-8 characters. toupper is locale-sensitive, not a Unicode uppercase
operation; the unchanged "C" locale gives the ASCII behavior required
above. Do not change the locale for this exercise.
This complete example safely converts one ASCII byte:
use std::ffi::c_int;
unsafe extern "C" {
fn toupper(c: c_int) -> c_int;
}
fn main() {
// SAFETY: an ASCII byte promoted to c_int is in unsigned char's range.
let upper = unsafe { toupper(c_int::from(b'q')) };
assert_eq!(upper, c_int::from(b'Q'));
}
abs: widen before handling the minimum
For a 32-bit C int, abs(-2147483648) has undefined behavior because
2147483648 cannot be represented in the return type. Converting the result
to i64 after the C call would be too late.
Detect i32::MIN first and calculate its magnitude in i64. For the other
inputs, C's abs has a representable result, which you can widen before
adding it to the total. For example, [-2147483648, 1] sums to 2147483649.
strlen: keep a terminator within the live buffer
A Rust byte vector knows its length; C's strlen instead reads until the
first zero. Appending zero ensures that even an empty or unterminated input
has a terminator within its allocation. Keep that vector alive and unchanged
throughout the call.
For [97, 98, 0, 99, 100], the answer is 2. The later bytes still exist
but are outside the C string. Cast the buffer pointer to *const c_char
when passing it to the supplied declaration.
Because the wrappers receive valid Rust strings, integers or owned byte vectors, they can establish these preconditions themselves. Their callers should not need an unsafe block. Marking a wrapper unsafe would also break the harness's safe calls; adding safety documentation alone would not fix it.
Submissions must compile, pass the tests and pass clippy -D warnings.
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.