Revise the FFI chapters (#190)

Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com>
pull/241/head
Michael Bryan 3 years ago committed by GitHub
parent dd265b1ec1
commit 96fa89b923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,17 +6,25 @@ When accepting strings via FFI through pointers, there are two principles that
should be followed:
1. Keep foreign strings "borrowed", rather than copying them directly.
2. Minimize `unsafe` code during the conversion.
2. Minimize the amount of complexity and `unsafe` code involved in converting
from a C-style string to native Rust strings.
## Motivation
Rust has built-in support for C-style strings with its `CString` and `CStr`
types. However, there are different approaches one can take with strings that
are being accepted from a foreign caller of a Rust function.
The strings used in C have different behaviours to those used in Rust, namely:
The best practice is simple: use `CStr` in such a way as to minimize unsafe
code, and create a borrowed slice. If an owned String is needed, call
`to_string()` on the string slice.
- C strings are null-terminated while Rust strings store their length
- C strings can contain any arbitrary non-zero byte while Rust strings must be
UTF-8
- C strings are accessed and manipulated using `unsafe` pointer operations
while interactions with Rust strings go through safe methods
The Rust standard library comes with C equivalents of Rust's `String` and `&str`
called `CString` and `&CStr`, that allow us to avoid a lot of the complexity
and `unsafe` code involved in converting between C strings and Rust strings.
The `&CStr` type also allows us to work with borrowed data, meaning passing
strings between Rust and C is a zero-cost operation.
## Code Example
@ -25,20 +33,30 @@ pub mod unsafe_module {
// other module content
/// Log a message at the specified level.
///
/// # Safety
///
/// It is the caller's guarantee to ensure `msg`:
///
/// - is not a null pointer
/// - points to valid, initialized data
/// - points to memory ending in a null byte
/// - won't be mutated for the duration of this function call
#[no_mangle]
pub extern "C" fn mylib_log(msg: *const libc::c_char, level: libc::c_int) {
pub unsafe extern "C" fn mylib_log(
msg: *const libc::c_char,
level: libc::c_int
) {
let level: crate::LogLevel = match level { /* ... */ };
let msg_str: &str = unsafe {
// SAFETY: accessing raw pointers expected to live for the call,
// and creating a shared reference that does not outlive the current
// stack frame.
match std::ffi::CStr::from_ptr(msg).to_str() {
Ok(s) => s,
Err(e) => {
crate::log_error("FFI string conversion failed");
return;
}
// SAFETY: The caller has already guaranteed this is okay (see the
// `# Safety` section of the doc-comment).
let msg_str: &str = match std::ffi::CStr::from_ptr(msg).to_str() {
Ok(s) => s,
Err(e) => {
crate::log_error("FFI string conversion failed");
return;
}
};

Loading…
Cancel
Save