CodeBlanks Logo Icon CodeBlanks

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}")),
    }
}

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

main.rs
use std::ffi::{c_char, c_int, c_long, CString};

fn main() {
    // buraya yaz
}

Sample solution

main.rs
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", &amp;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

Question
let mut son: *mut c_char = std::ptr::____();
let n = unsafe { strtol(c.as_ptr(), &mut son, taban) };
Answer
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

All lessons in Advanced