@@ -6,17 +6,25 @@ When accepting strings via FFI through pointers, there are two principles that
66should be followed:
77
881 . Keep foreign strings "borrowed", rather than copying them directly.
9- 2 . Minimize ` unsafe ` code during the conversion.
9+ 2 . Minimize the amount of complexity and ` unsafe ` code involved in converting
10+ from a C-style string to native Rust strings.
1011
1112## Motivation
1213
13- Rust has built-in support for C-style strings with its ` CString ` and ` CStr `
14- types. However, there are different approaches one can take with strings that
15- are being accepted from a foreign caller of a Rust function.
14+ The strings used in C have different behaviours to those used in Rust, namely:
1615
17- The best practice is simple: use ` CStr ` in such a way as to minimize unsafe
18- code, and create a borrowed slice. If an owned String is needed, call
19- ` to_string() ` on the string slice.
16+ - C strings are null-terminated while Rust strings store their length
17+ - C strings can contain any arbitrary non-zero byte while Rust strings must be
18+ UTF-8
19+ - C strings are accessed and manipulated using ` unsafe ` pointer operations
20+ while interactions with Rust strings go through safe methods
21+
22+ The Rust standard library comes with C equivalents of Rust's ` String ` and ` &str `
23+ called ` CString ` and ` &CStr ` , that allow us to avoid a lot of the complexity
24+ and ` unsafe ` code involved in converting between C strings and Rust strings.
25+
26+ The ` &CStr ` type also allows us to work with borrowed data, meaning passing
27+ strings between Rust and C is a zero-cost operation.
2028
2129## Code Example
2230
@@ -25,20 +33,30 @@ pub mod unsafe_module {
2533
2634 // other module content
2735
36+ /// Log a message at the specified level.
37+ ///
38+ /// # Safety
39+ ///
40+ /// It is the caller's guarantee to ensure `msg`:
41+ ///
42+ /// - is not a null pointer
43+ /// - points to valid, initialized data
44+ /// - points to memory ending in a null byte
45+ /// - won't be mutated for the duration of this function call
2846 #[no_mangle]
29- pub extern "C" fn mylib_log(msg: *const libc::c_char, level: libc::c_int) {
47+ pub unsafe extern "C" fn mylib_log(
48+ msg: *const libc::c_char,
49+ level: libc::c_int
50+ ) {
3051 let level: crate::LogLevel = match level { /* ... */ };
3152
32- let msg_str: &str = unsafe {
33- // SAFETY: accessing raw pointers expected to live for the call,
34- // and creating a shared reference that does not outlive the current
35- // stack frame.
36- match std::ffi::CStr::from_ptr(msg).to_str() {
37- Ok(s) => s,
38- Err(e) => {
39- crate::log_error("FFI string conversion failed");
40- return;
41- }
53+ // SAFETY: The caller has already guaranteed this is okay (see the
54+ // `# Safety` section of the doc-comment).
55+ let msg_str: &str = match std::ffi::CStr::from_ptr(msg).to_str() {
56+ Ok(s) => s,
57+ Err(e) => {
58+ crate::log_error("FFI string conversion failed");
59+ return;
4260 }
4361 };
4462
0 commit comments