Skip to content

Commit 96fa89b

Browse files
Revise the FFI chapters (#190)
Co-authored-by: Marco Ieni <[email protected]>
1 parent dd265b1 commit 96fa89b

File tree

1 file changed

+36
-18
lines changed

1 file changed

+36
-18
lines changed

idioms/ffi-accepting-strings.md

+36-18
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,25 @@ When accepting strings via FFI through pointers, there are two principles that
66
should be followed:
77

88
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.
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

Comments
 (0)