Rust Foreign Function Interface

January 2021

Recently I was experimenting with calling functions from .so files using rust. I decided to test this out by trying to call the standard printf function from libc. I already know there are a standard libc crate on Github and a good tutorial for the Foreign Function Interface, so I decided to start from there and work my way down to the barebones.

To extrapolate, my goal here is to understand a bit better how the FFI works and in the future use it for other non-libc functions.

In any case, the simplest working example I got was

use libc::printf;
use std::ffi::CString;
fn main() {
let fmt = CString::new("%s\n").expect("CString::new failed");
let s = CString::new("hello world!").expect("CString::new failed");
unsafe {
printf(fmt.as_ptr(), s.as_ptr());
}
}

This example uses the aforementioned libc crate which contains a public external function referencing printf. Furthermore, it also uses the CString type. Unlike the more usual String type, CString ensures that there are no null bytes in the middle of the string and, more importantly, that the string is null terminated. Potentially for convenience, the CString::as_ptr() function returns *const c_char ( equivalent to *const i8), unlike the String::as_ptr() function which returns *const u8. The latter requires an additional cast before being used by libc::print.

Thus an alternative, not necessarily simpler, is to replace the CString objects with String ones and obtain

use libc::{c_char, printf};
use std::string::String;
fn main() {
let fmt = String::from("%s\n\0");
let s = String::from("hello world!\0");
unsafe {
printf(
fmt.as_ptr() as *const c_char,
s.as_ptr() as *const c_char,
);
}
}

Curiosity satisfied, but we haven't really touched on the printf part. Let's do that next.

use libc::{c_char, c_int};
use std::ffi::CString;
#[link(name = "c")]
extern "C" {
fn printf(format: *const c_char, ...) -> c_int;
}
fn main() {
let fmt = CString::new("%s\n").expect("CString::new failed");
let s = CString::new("hello world!").expect("CString::new failed");
unsafe {
printf(fmt.as_ptr(), s.as_ptr());
}
}

Instead of using the printf function from the libc crate, we are defining it as an external function ourselves. This is literally the same as what the crate does, but again, this is just a simple example for experimentation.

As a final step, we could remove the dependency on the libc crate together with CString and obtain

#[link(name = "c")]
extern "C" {
fn printf(format: *const i8, ...) -> i32;
}
fn main() {
unsafe {
printf(
"%s\n\0".as_ptr() as *const i8,
"hello world!\0".as_ptr() as *const i8,
);
}
}

Note here the use of the str type which requires strings to be explicitly null terminated.

In any case, that's it.