Skip to content
tezvyn:
Menu

FFI Error Handling: Translation and Unwinding

Source: nrc.github.ioMediumHow cards are made

FFI error handling is a translation layer: foreign errors must become Rust Results before safe code sees them, or you risk UB. You do this in -sys wrappers around C libraries. The footgun: foreign exceptions unwinding across boundary without -unwind ABI is UB.

Why it exists

Rust's ownership and Result-based error model cannot see across language boundaries. When you call C or OS syscalls, errors arrive as integers, null pointers, sentinel values, or thread-unwinding exceptions. If you do not normalize these foreign mechanisms into Rust's type system at the boundary, safe code ends up carrying raw error numbers or, worse, letting foreign stack unwinding crash or corrupt the process.

The mental model

Think of the FFI boundary as a customs checkpoint. Every error must declare its nature in Rust terms before entering. C errors are just data, so translating them is like converting any other struct or integer. Exceptions are radioactive cargo: they must be caught on the side that throws them, because an unwinding stack cannot cross the language border without risking undefined behavior or an immediate abort.

How it works

The standard pattern is a pair of crates. The -sys crate contains thin, auto-generated bindings that mirror the foreign API exactly, often returning raw integers or pointers. The idiomatic wrapper crate converts those into Result types. For C error codes, you match the raw value: negative numbers might become Err variants, non-negative numbers become Ok. For OS errors, std::io::Error::from_raw_os_error builds a Rust error from an errno, while last_os_error reads the thread's last error value. For exceptions, Rust extern functions use an ABI with or without the -unwind suffix. Without it, a foreign unwind entering Rust is undefined behavior. With it, the unwind may propagate, but if you try to catch it with catch_unwind you get an opaque error or an abort. Rust panics reaching foreign code always abort the process.

When to use it

Use this pattern whenever you maintain bindings to a C library, wrap platform-specific syscalls, or expose a Rust library to another language. If the foreign API returns error codes, translate them immediately in the wrapper. If the foreign API uses exceptions or setjmp/longjmp, install a catch barrier on the foreign side before calling back into Rust.

When not to use it

Do not try to unify every C error into a single Rust error type if the wrapper is meant to be a thin, zero-cost shim. Also, do not use last_os_error after any other library call has had a chance to overwrite the thread-local error state. Do not let Rust panics escape into C, and do not assume catch_unwind gives you a useful payload after a foreign unwind.

One canonical example

A C library returns an isize where negative values signal failure. In the -sys crate you get that isize directly. In the safe wrapper you write a match that maps -1 to Err(MyError::Foo), -2 to Err(MyError::Bar), and other negatives to Err(MyError::UnknownForeign(c)), while zero or positive values return Ok(c as usize). If you need to pass the error back to C later, you reverse the mapping in a From impl.

Interview question

If a foreign exception unwinds into Rust through an extern function that lacks the -unwind ABI, what is the result?

  • a.The behavior is undefined.Correct
  • b.The runtime guarantees an immediate abort before any Rust frame is corrupted.
  • c.The exception is converted to a Rust panic that can be caught with catch_unwind.
  • d.The unwind may propagate and be observable as an opaque error through catch_unwind.
Why?

The card explicitly states that without the -unwind suffix, a foreign unwind entering Rust is undefined behavior. Option D describes the behavior when the -unwind ABI is used, and B confuses this with Rust panics escaping into foreign code.

Just read this? Test yourself on what you have been reading.

Read the original → nrc.github.io

Put your scrolling time to good use

Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Every open role lists the topics its interview covers, so you can prepare for the real thing rather than guessing.

See open roles