Rust
FFI wrapper
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}")),
}
}
Exercise 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.
Show example 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));
}
Test yourself
-
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)
- To the end of the string
Show answer
To the "x" character (where reading stopped) — The difference (end - start) is the number of bytes read; if it isn't equal to the text length, there's an invalid character.
-
Output pointer
let mut son: *mut c_char = std::ptr::____();
let n = unsafe { strtol(c.as_ptr(), &mut son, taban) };
Show answer
let mut son: *mut c_char = std::ptr::null_mut();
let n = unsafe { strtol(c.as_ptr(), &mut son, taban) };
-
Input and result
- ("ff", 16)
- Ok(255)
- ("101", 2)
- Ok(5)
- ("12x", 10)
- geçersiz karakter: x
- ("", 10)
- boş girdi
Next lesson: Advanced section exam