FFI wrapper
- Rust course
- Advanced
- Lesson 81/81
- 7 min
Write a safe wrapper around a C library.
Final task: FFI wrapper
C's strtol converts text to a number in a given base and reports where it stopped reading through an output pointer. The raw interface is very error-prone; the task is to wrap it in a safe Rust function:
unsafe extern "C" {
fn strtol(s: *const c_char, son: *mut *mut c_char, taban: c_int) -> c_long;
}
fn sayiya(s: &str, taban: i32) -> Result<i64, String> { .. }The safe interface's responsibilities: converting the text into a CString (inner null byte error), finding the invalid character by looking at how far the pointer advanced, and returning the result in Rust types.
The safe wrapper
offset_from gives the element difference between two pointers within the same allocation:
fn sayiya(s: &str, taban: i32) -> Result<i64, String> {
let c = CString::new(s).map_err(|_| String::from("metinde boş bayt var"))?;
let mut son: *mut c_char = std::ptr::null_mut();
// SAFETY: c is a valid C string; son is a writable pointer.
let n = unsafe { strtol(c.as_ptr(), &mut son, taban) };
// SAFETY: strtol sets son to somewhere inside c.
let okunan = unsafe { son.cast_const().offset_from(c.as_ptr()) } as usize;
match s[okunan..].chars().next() {
None if okunan > 0 => Ok(n as i64),
None => Err(String::from("boş girdi")),
Some(k) => Err(format!("geçersiz karakter: {k}")),
}
}Practice strtol wrapper
Write the declaration above and the safe sayiya function. Print the results of sayiya("ff", 16) and sayiya("101", 2) with unwrap, and of sayiya("12x", 10) with {:?}, separated by spaces.
Starter code
use std::ffi::{c_char, c_int, c_long, CString};
fn main() {
// buraya yaz
}Sample solution
use std::ffi::{c_char, c_int, c_long, CString};
unsafe extern "C" {
fn strtol(s: *const c_char, son: *mut *mut c_char, taban: c_int) -> c_long;
}
fn sayiya(s: &str, taban: i32) -> Result<i64, String> {
let c = CString::new(s).map_err(|_| String::from("metinde boş bayt var"))?;
let mut son: *mut c_char = std::ptr::null_mut();
// SAFETY: c geçerli bir C dizesi; son yazılabilir bir işaretçi.
let n = unsafe { strtol(c.as_ptr(), &mut son, taban) };
// SAFETY: strtol son'u c'nin içinde bir yere ayarlar.
let okunan = unsafe { son.cast_const().offset_from(c.as_ptr()) } as usize;
match s[okunan..].chars().next() {
None if okunan > 0 => Ok(n as i64),
None => Err(String::from("boş girdi")),
Some(k) => Err(format!("geçersiz karakter: {k}")),
}
}
fn main() {
println!("{} {} {:?}", sayiya("ff", 16).unwrap(), sayiya("101", 2).unwrap(), sayiya("12x", 10));
}Expected output
255 5 Err("geçersiz karakter: x")
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 After <code>strtol("12x", &son, 10)</code>, where does <code>son</code> point?
- To the start
- To NULL
- To the "x" character (where reading stopped) Correct answer
- To the end of the string
Explanation: The difference (end - start) is the number of bytes read; if it isn't equal to the text length, there's an invalid character.
2 Output pointer
let mut son: *mut c_char = std::ptr::____();
let n = unsafe { strtol(c.as_ptr(), &mut son, taban) };
let mut son: *mut c_char = std::ptr::null_mut();
let n = unsafe { strtol(c.as_ptr(), &mut son, taban) };
Explanation: null_mut creates a null *mut pointer; strtol fills it in.
3 Input and result
| Term | Meaning |
|---|---|
("ff", 16) |
Ok(255) |
("101", 2) |
Ok(5) |
("12x", 10) |
geçersiz karakter: x |
("", 10) |
boş girdi |