@@ -6,17 +6,25 @@ When accepting strings via FFI through pointers, there are two principles that
6
6
should be followed:
7
7
8
8
1 . 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.
10
11
11
12
## Motivation
12
13
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:
16
15
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.
20
28
21
29
## Code Example
22
30
@@ -25,20 +33,30 @@ pub mod unsafe_module {
25
33
26
34
// other module content
27
35
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
28
46
#[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
+ ) {
30
51
let level: crate::LogLevel = match level { /* ... */ };
31
52
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;
42
60
}
43
61
};
44
62
0 commit comments