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 from Rust: libc is already linked
Call three C standard library functions from Rust. No crates, no build script, no
linker flags, no Cargo.toml.
pub fn shout(s: &str) -> String
pub fn abs_sum(xs: Vec<i32>) -> i64
pub fn c_strlen_of(bytes: Vec<u8>) -> usize
The thing nobody tells beginners
FFI is usually presented as a chapter you read and do not practise, because
“you’d need the libc crate and a build script”. That is false for the C
standard library.
The Rust standard library itself links the platform C runtime — it has to, since
std is built on top of the OS’s C interfaces. So the C standard library is
already in your binary, and a plain rustc build can declare and call
strlen, abs, toupper, sqrt, memcmp, qsort, malloc, snprintf with
nothing but a declaration.
The libc crate is enormously useful — it gives you correct, per-platform
declarations for thousands of functions and constants so you do not have to write
them yourself. But it is a convenience, not a requirement, and knowing that turns
FFI from theory into something you can execute one line at a time.
Declaring a foreign function
use std::ffi::{c_char, c_int};
unsafe extern "C" {
fn abs(v: c_int) -> c_int;
fn strlen(s: *const c_char) -> usize;
fn toupper(c: c_int) -> c_int;
}
extern "C" fixes the calling convention (18.21). The unsafe on the block is
edition 2024 and item 18.23’s subject; take it as given for now.
Three details in those three lines matter more than they look:
Use the c_* aliases, never concrete Rust types. c_int is 32 bits on every
common target but the C standard does not guarantee it, and c_char is
genuinely different: i8 on x86-64 Linux and macOS, u8 on ARM Linux. Write
i8 and your code is wrong on a Raspberry Pi and right everywhere you tested it.
The aliases cost nothing and are always correct.
A wrong signature is instant undefined behaviour with no diagnostic. The
compiler has exactly one source of truth about strlen: your declaration. It
does not read the C header, it cannot check anything, and it will emit a call
that passes arguments the way you described. Declare strlen as taking an i32
and the program will happily truncate a pointer to 32 bits and hand the result to
C. There is no error, no warning, and often no crash — until there is.
Do not lie about pointer types. malloc returns *mut c_void, not
*mut u8. qsort‘s comparator takes *const c_void. Using the honest type is
what lets rustc’s improper_ctypes lint help you (18.25).
The three functions
toupper(c_int) -> c_int. C’s contract is that the argument must be
representable as an unsigned char, or EOF. Passing an arbitrary byte above
127 is implementation-defined at best. So uppercase only the ASCII bytes and pass
everything else through — which also happens to keep the string valid UTF-8, since
multi-byte sequences are left alone.
abs(c_int) -> c_int. Here is the trap, and it is a good one.
abs(INT_MIN) is undefined behaviour in C: there is no positive int equal
to 2147483648, so there is no correct answer, and the standard declines to
specify one. The C library does not check. Rust cannot check. Your wrapper has
to.
This is worth pausing on because it generalises: crossing into C means taking on
C’s preconditions, and those preconditions are in prose in a standards document,
not in any type. abs_sum must handle i32::MIN itself.
(Note that i32::abs in Rust has the same problem stated differently: it panics
in debug and wraps in release. Compilation here uses -O, so overflow checks are
off — see 18.26.)
strlen(*const c_char) -> usize. The one that shows you what a C string is.
Build a byte buffer, push a 0, and hand C a pointer to it. C walks forward
until it finds the nul and reports the distance.
Give it [104, 105, 0, 120] — “hi”, nul, “x” — and it answers 2, not 4. A
Rust &[u8] knows its own length; a C string does not, and stops at the first
nul it meets. That single asymmetry is the source of a large fraction of all C
security bugs, and of all of item 18.25.
Safe wrappers, not pub unsafe fn
All three of your functions are safe. Every unsafe block is small, and each
one is preceded by a // SAFETY: comment saying which precondition you are
discharging.
That is not a stylistic preference; the gate enforces it. If you write
pub unsafe fn shout(s: &str) -> String
then clippy’s missing_safety_doc requires a # Safety section in the doc
comment explaining what the caller must guarantee — and you would have nothing to
put in it, because there is nothing the caller can get wrong. Making a function
unsafe when its safety is entirely your responsibility pushes the burden onto
every caller for no reason.
The related lint not_unsafe_ptr_arg_deref (which you will meet properly in
18.24) is the other half of the rule: a safe public function must not take a
raw pointer and dereference it, because a caller has no way to satisfy the
precondition. Safe wrapper around unsafe internals, or unsafe fn with a
documented contract. Not a safe function with a hidden landmine.
::: question Why does the starter fail with E0133 rather than just working?
Because calling a foreign function is unsafe, and edition 2024 will not let that slide.
error[E0133]: call to unsafe function `toupper` is unsafe and requires
unsafe function or block
The compiler cannot verify anything about toupper — it has no body to analyse,
only your declaration. Whether the call is sound depends on whether the
declaration matches reality, and only you know that. unsafe { } is where you
say so.
E0133 is the most common error in FFI work and it is a good one: it is impossible
to call foreign code by accident. Item 18.23 shows the edition-2024 refinement,
where a declaration can be marked safe so that particular function needs no
block — because for some functions, sqrt for instance, there genuinely is no
precondition.
:::
Your job
shout is written but does not compile. abs_sum and c_strlen_of are todo!().
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.