As I once said:
My life
is a lifelong battle with the Rust compiler.
—Me
And also:
When you think Rust has a problem,
it’s not because Rust has a problem—
it’s because you have a problem.
—Also me
CString Ownership
While working on Teaclave development, I used a lot of FFI. CString left a lasting impression.
Problem
NativeSymbol {
symbol: CString::new("teaclave_open_input").as_ptr() as _,
func_ptr: wasm_open_input as *const c_void,
signature: CString::new("($)i").as_ptr() as _,
attachment: std::ptr::null(),
},
This code creates a NativeSymbol struct, where symbol and signature are C strings created inline and converted to raw pointers.
Here’s the problem: according to Rust’s ownership rules, data created within a scope is dropped when that scope ends. In other words, the CString gets dropped, but the pointer lives on. What do we have? A textbook dangling pointer!
The most frustrating part is that Rust won’t emit any errors here. Creating raw pointers in Rust is safe. Dereferencing them, however, is unsafe. Since the actual dereference happens inside an unsafe FFI call (not in safe Rust), the compiler has no way to catch this at compile time.
Solution
The fix? Use string literals:
NativeSymbol {
symbol: b"teaclave_open_input\0".as_ptr() as _,
func_ptr: wasm_open_input as *const c_void,
signature: b"($)i\0".as_ptr() as _,
attachment: std::ptr::null(),
},
String literals in Rust have a 'static lifetime, so pointers to them won’t be invalidated.
Another approach might be to use references (&), but I haven’t tried that yet. I’ll update this post once I do.