Internals of C program call to rust program call
One of the main strength of Rust programming language is that it can easily inter-operate with other programming languages.
But as we know Rust is very very strongly typed language. When you are planning Rust to use some of the C libraries , the C type representation attributes help us .
#[repr( C )]
There are some difference in the type representation for C type corresponding rust type.
We can go-ahead and check what is the size of the below struct
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7392513792a8d390ce4756a6a8a0ed15
use std::mem;
#[repr(C)]
struct FieldStruct {
first: u8,
second: u16,
third: u8
}
// The size of the first field is 1, so add 1 to the size. Size is 1.
// The alignment of the second field is 2, so add 1 to the size for padding. Size is 2.
// The size of the second field is 2, so add 2 to the size. Size is 4.
// The alignment of the third field is 1, so add 0 to the size for padding. Size is 4.
// The size of the third field is 1, so add 1 to the size. Size is 5.
// Finally, the alignment of the struct is 2 (because the largest alignment amongst its
// fields is 2), so add 1 to the size for padding. Size is 6.
assert_eq!(6, mem::size_of::<FieldStruct>());
}
But if you remove #[repr( c )] , the struct size becomes 4.
#![allow(unused)]
fn main() {
use std::mem;
struct FieldStruct {
first: u8,
second: u16,
third: u8
}
assert_eq!(6, mem::size_of::<FieldStruct>());
}
How to use a Rust function in C program.
I referred the blog for Sergey Potapov for the details.
In this below program, you would see the function print_hello_from_rust defined with extern keyword and [no_mangle] attribute.
[no_mangle] makes the compiler ignores the unknown symbols and it knows this function is going to get called from other languages.
extern keyword makes the function outside of the our library.
std::ffi::CStr. Representation of a borrowed C string. This type represents a borrowed reference to a nul-terminated array of bytes. It can be constructed safely from a &[ u8 ] slice, or unsafely from a raw *const c_char
In this example we are using *const c_char , but what is c_char ?!
c_char is coming from the standard library ‘os’ module and it is Equivalent to C’s char
type.C’s char
type is completely unlike Rust’s char
type; while Rust’s type represents a unicode scalar value, C’s char
type is just an ordinary integer. This type will always be either i8
or u8
, as the type is defined as being one byte long
reference : c_char
then why *const in front of it ?
*const are called Raw pointers in Rust.Sometimes, when writing certain kinds of libraries, you’ll need to get around Rust’s safety guarantees for some reason. In this case, you can use raw pointers to implement your library, while exposing a safe interface for your users. Ref: Raw pointers
use std::ffi::{CString,CStr};
use std::os::raw::{c_char,c_int};
#[repr(C)]
pub struct Point{
x: c_int,
y: c_int,
}
impl Point {
fn new(x:c_int,y:c_int) -> Point{
// println!("Creating a Point with x = {},y = {}",x,y);
Point {x: x,y: y }
}
}
#[no_mangle]
pub extern fn create_point(x:c_int,y:c_int) -> *mut Point{
Box::into_raw(Box::new(Point::new(x,y)))
}
#[no_mangle]
pub extern fn print_hello_from_rust(data: *const c_char ){
unsafe{
let c_str = CStr::from_ptr(data);
println!("hello from rust {:?}",c_str.to_str().unwrap());
}
}
You can build the rust program with cargo
cargo new whatland -- lib
cd whatland
edit the lib.rs file with above code.
also make sure that your cargo file has
[lib]
name = "whatland"
crate-type = ["staticlib","cdylib"]
cdylib helps - A dynamic system library creation. This is used when compiling a dynamic library to be loaded from another language. This output type will create *.so
files on Linux, *.dylib
files on macOS, and *.dll
files on Windows.
cargo build --release
C program would need to compile with linking the .so file generated.
I am not detailing the C program compilation, but it detailed in the blogpost: https://www.greyblake.com/blog/2017-08-10-exposing-rust-library-to-c/
You would need to use,
gcc -o ./examples/hello ./examples/hello.c -Isrc -L. -l:target/release/libwhatlang.so
Passing String parameter from C to Rust function.
We can use the trick of accepting the string as a Raw pointer using *const c_char
In order to print that bytes into a valid string slice in Rust, first we need to convert that to CStr - Representation of a borrowed C string.
then we can convert that to Str ( rust string slice) using c_str.to_str().unwrap()
How to use a C struct in Rust program
I believe , Its always recommended to use #[repr©] when working with C structs, enums because it makes alignment https://doc.rust-lang.org/reference/type-layout.html#the-c-representation
#[repr(C)]
pub struct Point{
x: c_int,
y: c_int,
// x:u8, -- if you are using u8, compilier throws error,
// y:u8, stating "expected `u8`, found `i32'"
}
We implemented a method new in the rust program so that we can use in the Point struct instance creation.
impl Point {
fn new(x:c_int,y:c_int) -> Point{
Point {x: x,y: y }
}
}
Next in this example we have a export function which will be called from C program to creating Point Struct. For creating any object, we need to memory. In Rust we know that we can allocate heap memory through Box::new.
But we need to return a raw pointer to the C program, which can be done through Box::into_raw which Consumes the Box
, returning a wrapped raw pointer.
23 #[no_mangle]
24 pub extern fn create_point(x:c_int,y:c_int) -> *mut Point{
25
26 Box::into_raw(Box::new(Point::new(x,y)))
27 }
C program can now just call the Rust function to create struct.
We need to have our C program included with struct and create function declaration.
header file whatland.h
1 void print_hello_from_rust();
2
3 typedef struct Point{
4 int x,y;
5 }Point;
6
7 Point* create_point(int x, int y);
Now we can just call it in our main function in C
11 Point* p1 = create_point(10,20);
12 printf("Point={%d},{%d}",p1 -> x,p1 -> y);