|
|
|
@ -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;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|