Introduction
Rust is a system programming Language developed by Mozilla. For a C-like language, it features interesting paradigms:
- functional style,
- actors,
- object oriented,
- safe memory allocation and free
In this article we will link a Rust library with a C program.
Tools used:
- rustc 0.13.0-nightly (5484d6f6d 2014-12-02)
- gcc (Debian 4.9.1-19) 4.9.1
- docker
Installation
Compiling the Rust compiler takes quite some time, so I decided to use a nightly built container of the compiler:
$ sudo docker run --rm -it -v $(pwd)/play_rust:/source schickling/rust
The Rust library
Here is a simple Rust library called rust_test
:
#![crate_type = "lib"]
#![crate_name = "rust_test"]
pub fn add(a: int, b: int) -> int {
a + b
}
pub fn mult2(a: int) -> int {
a * 2
}
The create_type
is rlib
which means it’s a standard Rust library. This will compile to librust_test.rlib
which is not the expected format for linking with a C program.
To have a standard .so file, use #![crate_type = "dylib"]
Lets compile:
$ rustc src/rust_test.rs
librust_test.rlib
is a simple ar
archive:
$ file librust_test.rlib
librust_test.rlib: current ar archive
whereas when compiled with crate_lib = "dylib"
, the librust_test.so
is a traditional shared library:
$ file librust_test.so
librust_test.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0xe257293ebac1993f4d09926d3b3f66bb19dd4377, not stripped
The C program
#include <stdio.h>
#include <lib_test.h>
int main(int argc, const char *argv[]) {
int a, b;
a = 4;
b = 6;
printf("%d + %d = %d\n", a, b, add(a, b));
printf("2 * %d = %d\n", a, mult2(a));
return (0);
}
The library header:
#ifndef LIB_TEST_H_
#define LIB_TEST_H_
int add(int, int);
int mult2(int);
#endif /* LIB_TEST_H_ */
The library code:
int add(int a, int b) {
return (a + b);
}
int mult2(int a) {
return (2 * a);
}
Simple test:
$ gcc -shared c_test.c -o libc_test.so
$ gcc -Wall -I. -L. -lc_test test.c -o test
$ LD_LIBRARY_PATH=. ./test
4 + 6 = 10
2 * 4 = 8
Let’s do link with our Rust library
First, some simple comparison between our two libraries.
$ ls -l *.so
-rwxr-xr-x 1 klyr klyr 6176 Dec 17 14:05 libc_test.so
-rwxr-xr-x 1 klyr klyr 1333512 Dec 17 14:01 librust_test.so
The Rust library is bigger in size (even after being stripped) because it includes the Rust runtime code.
Now let’s check for exported symbols:
$ nm c_test.o
0000000000000000 T add
0000000000000014 T mult2
$ nm -C librust_test.so|grep -E 'T add|T mult2'
0000000000071850 T add::hf274835eff4dd2c6eaa
00000000000718a0 T mult2::h3931ba25cb6746acuaa
Now we want to replace our C library with the Rust one.
Let’s try to link with our Rust library:
$ gcc -Wall -std=c99 -I. -L. -lrust_test test.c -o test
/tmp/ccbjLrt6.o: In function `main':
test.c:(.text+0x28): undefined reference to `add'
test.c:(.text+0x4b): undefined reference to `mult2'
collect2: error: ld returned 1 exit status
As Rust has its own way of doing name mangling the linker is not able to find the add
and mult2
functions. To fix this we need to tell the Rust compiler that we do not want to do name mangling on these to functions. Simply add the #[no_mangle]
attribute to both functions.
We end up with the following Rust library:
#![crate_type = "lib"]
#![crate_name = "rust_test"]
#[no_mangle]
pub fn add(a: int, b: int) -> int {
a + b
}
#[no_mangle]
pub fn mult2(a: int) -> int {
a * 2
}
Compile again:
$ gcc -Wall -std=c99 -I. -L. -lrust_test test.c -o test
$ export LD_LIBRARY_PATH=.
$ ldd ./test
linux-vdso.so.1 (0x00007fffd11fc000)
librust_test.so => ./librust_test.so (0x00007fd499328000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd498f5d000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fd498d3f000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fd498b29000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd499676000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd498828000)
$
We are well linked with our Rust library.
Check if it works:
$ ./test
4 + 6 = 10
2 * 4 = 8
$
Last words
Integrating Rust libraries with C code is quite straightforward:
- specify
dylib
ascreate_type
attribute - add
#[no_mangle]
attribute to functions to prevent name mangling
On some blogs and documentations, people also add the extern
keyword for function definition. It does not seem to be required in my tests.
I’m not sure if we can use Rust functions ins the standard library like println
for example. This needs to be checked maybe in a later article.
One more thing with Lua
Just wanted to check how easy it can be to integrate with LuaJIT ffi.
$ luajit
LuaJIT 2.0.3 -- Copyright (C) 2005-2014 Mike Pall. http://luajit.org/
JIT: ON CMOV SSE2 SSE3 SSE4.1 fold cse dce fwd dse narrow loop abc sink fuse
> ffi = require("ffi")
> ffi.cdef[[
>> int add(int, int);
>> int mult2(int);
>> ]]
> rust_lib = ffi.load("./librust_test.so")
> sum = rust_lib.add(1, 2)
> mult = rust_lib.mult2(5)
> print("Sum:", sum)
Sum: 3
> print("Mult2:", mult)
Mult2: 10