Linking Rust Library with C

Published 12-17-2014 00:00:00

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

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 as create_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