Calling Rust from C is nor complicated neither really straightforward. I decided to experiment this feature with a list of functions callable from C for nearly each possible C type.
The Rust module is compiled as a dynamic library, by setting the [lib] tag in the Cargo.toml file:
[lib]
crate-type =["cdylib"]
The C file is compiled as a 64-bit executable, dynamically linked at runtime with the previous library.
All C-types have their equivalence in the Rust type system, using the FFI module or the libc crate (not used here).
Rust functions
All Rust functions should be marked as #[no_mangle] and extern because they will be marked as exported in the resulting library.
The Rust source file is using a simple macro to define most of the functions (this macro is found in the Rust book) to avoid repetitive code:
use std::ffi::{c_void, CStr};
use std::os::raw::c_char;
use std::slice;
// A Rust struct mapping the C struct
#[repr(C)]
#[derive(Debug)]
pub struct RustStruct {
pub c: char,
pub ul: u64,
pub c_string: *const c_char,
}
macro_rules! create_function {
// This macro takes an argument of designator `ident` and
// creates a function named `$func_name`.
// The `ident` designator is used for variable/function names.
($func_name:ident, $ctype:ty) => {
#[no_mangle]
pub extern "C" fn $func_name(v: $ctype) {
// The `stringify!` macro converts an `ident` into a string.
println!(
"{:?}() is called, value passed = <{:?}>",
stringify!($func_name),
v
);
}
};
}
// create simple functions where C type is exactly mapping a Rust type
create_function!(rust_char, char);
create_function!(rust_wchar, char);
create_function!(rust_short, i16);
create_function!(rust_ushort, u16);
create_function!(rust_int, i32);
create_function!(rust_uint, u32);
create_function!(rust_long, i64);
create_function!(rust_ulong, u64);
create_function!(rust_void, *mut c_void);
// for NULL-terminated C strings, it's a little bit clumsier
#[no_mangle]
pub extern "C" fn rust_string(c_string: *const c_char) {
// build a Rust string from C string
let s = unsafe { CStr::from_ptr(c_string).to_string_lossy().into_owned() };
println!("rust_string() is called, value passed = <{:?}>", s);
}
// for C arrays, need to pass array size
#[no_mangle]
pub extern "C" fn rust_int_array(c_array: *const i32, length: usize) {
// build a Rust array from array & length
let rust_array: &[i32] = unsafe { slice::from_raw_parts(c_array, length as usize) };
println!(
"rust_int_array() is called, value passed = <{:?}>",
rust_array
);
}
#[no_mangle]
pub extern "C" fn rust_string_array(c_array: *const *const c_char, length: usize) {
// build a Rust array from array & length
let tmp_array: &[*const c_char] = unsafe { slice::from_raw_parts(c_array, length as usize) };
// convert each element to a Rust string
let rust_array: Vec<_> = tmp_array
.iter()
.map(|&v| unsafe { CStr::from_ptr(v).to_string_lossy().into_owned() })
.collect();
println!(
"rust_string_array() is called, value passed = <{:?}>",
rust_array
);
}
// for C structs, need to convert each individual Rust member if necessary
#[no_mangle]
pub unsafe extern "C" fn rust_cstruct(c_struct: *mut RustStruct) {
let rust_struct = &*c_struct;
let s = CStr::from_ptr(rust_struct.c_string)
.to_string_lossy()
.into_owned();
println!(
"rust_cstruct() is called, values passed = <{} {} {}>",
rust_struct.c, rust_struct.ul, s
);
}
A mere cargo build will compile this lib.rs file and place the result in target/debug subdirectory of the Rust project directory. My lib is called librustcalls.so
You can look at exported functions with the nm command:
$ nm --defined-only -D ./rustcalls/target/debug/librustcalls.so
000000000000b5e0 T rust_char
000000000000b3b0 T rust_cstruct
0000000000016740 T rust_eh_personality
000000000000ba20 T rust_int
000000000000b140 T rust_int_array
000000000000bc40 T rust_long
000000000000b800 T rust_short
000000000000b010 T rust_string
000000000000b240 T rust_string_array
000000000000bb30 T rust_uint
000000000000bd50 T rust_ulong
000000000000b910 T rust_ushort
000000000000be60 T rust_void
000000000000b6f0 T rust_wchar
C functions
C function declarations are necessary to link the external library (at runtime) compiled with Rust.
Compilation is done by:
$ gcc -g call_rust.c -o call_rust -lrustcalls -L./rustcalls/target/debug
where:
- -g is for including symbols for debugging with gdb
- -o call_rust will create call_rust as the executable file
- -lrustcalls tells the compiler to look for function definitions in the librustcalls.so shared lib file
- -L./rustcalls/target/debug tells the compiler to look into this directory for the previous file
call_rust.c is this file:
// compile with gcc -g call_rust.c -o call_rust -lrustcalls -L./rustcalls/target/debug
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
// sample struct to illustrate passing a C-struct to Rust
struct CStruct {
char c;
unsigned long ul;
char *s;
};
// functions called in the Rust library
extern void rust_char(char c);
extern void rust_wchar(wchar_t c);
extern void rust_short(short i);
extern void rust_ushort(unsigned short i);
extern void rust_int(int i);
extern void rust_uint(unsigned int i);
extern void rust_long(long i);
extern void rust_ulong(unsigned long i);
extern void rust_string(char *s);
extern void rust_void(void *s);
extern void rust_int_array(const int *array, int length);
extern void rust_string_array(const char **array, int length);
extern void rust_cstruct(struct CStruct *c_struct);
int main() {
// pass char to Rust
rust_char('A');
rust_wchar(L'ζ');
// pass short to Rust
rust_short(-100);
rust_ushort(100);
// pass int to Rust
rust_int(-10);
rust_uint(10);
// pass long to Rust
rust_long(-1000);
rust_ulong(1000);
// pass a NULL terminated string
rust_string("hello world");
// pass a void* pointer
void *p = malloc(1000);
rust_void(p);
// pass an array of ints
int digits[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
rust_int_array(digits, 10);
// pass an array of c strings
const char *words[] = { "This", "is", "an", "example"};
rust_string_array(words, 4);
// pass a C struct
struct CStruct c_struct;
c_struct.c = 'A';
c_struct.ul = 1000;
c_struct.s = malloc(20);
strcpy(c_struct.s, "0123456789");
rust_cstruct(&c_struct);
// don't forget to clean up ;-)
free(p);
free(c_struct.s);
return 0;
}
You can look at linked libraries using the ldd command:
$ ldd call_rust
linux-vdso.so.1 (0x00007ffe6d69c000)
librustcalls.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb695054000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb695648000)
librustcalls.so is reported not found because not in the standard paths for shared libs. It's necessary to tell the linker where to find this file, using the LD_LIBRARY_PATH environment variable:
$ LD_LIBRARY_PATH=rustcalls/target/debug ldd call_rust
linux-vdso.so.1 (0x00007ffcb8f62000)
librustcalls.so => rustcalls/target/debug/librustcalls.so (0x00007f60a975b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f60a936a000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f60a9166000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f60a8f5e000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f60a8d3f000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f60a8b27000)
/lib64/ld-linux-x86-64.so.2 (0x00007f60a9b97000)
Now to execute:
$ LD_LIBRARY_PATH=rustcalls/target/debug ./call_rust
"rust_char"() is called, value passed = <'A'>
"rust_wchar"() is called, value passed = <'ζ'>
"rust_short"() is called, value passed = <-100>
"rust_ushort"() is called, value passed = <100>
"rust_int"() is called, value passed = <-10>
"rust_uint"() is called, value passed = <10>
"rust_long"() is called, value passed = <-1000>
"rust_ulong"() is called, value passed = <1000>
rust_string() is called, value passed = <"hello world">
"rust_void"() is called, value passed = <0x55f0a68f17d0>
rust_int_array() is called, value passed = <[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>
rust_string_array() is called, value passed = <["This", "is", "an", "example"]>
rust_cstruct() is called, values passed = <A 1000 0123456789>
Hope this helps !
Photo by Deanna Ritchie on Unsplash
Top comments (2)
Finally a complete example and that worked for me. Thanks!
based