Linking with custom C library

The FFI documentation has been really helpful, but I have a problem I haven't been able to figure out. I think probably what I don't know is how the C ecosystem works, but anyway -

I have a custom library that I wrote in C which I want to link with my Rust code. I've written a compatible interface and tagged it #[link(name="foo", kind="static")]. I compiled the C code into a .o file. The .c, .h, & .o files are all in a directory src/c/ right now.

Running "cargo build," it tells me that it can't find my library. What step have I missed to make my library linkable? Is there something I am supposed to add to Cargo.toml to configure cargo to find it? Is there some additional step I need to perform on the C code to make it available for linking? What am I missing?

EDIT: I got it to compile with #[link_args = "-L -lfoo"], but that is recommended against so I'd still like advice on how to link properly.

EDIT2: Actually, with those settings "cargo build" compiled but "cargo test" does not. Hmm..

3 Likes

If I understand everything right, it sounds like what's needed is some way to pass the right flags to rustc to tell it where to find your library. I believe the recommended way to do this is to emit the flags from build.rs, as documented here: Page Moved (in particular, the “Outputs of the Build Script” section). You likely want something like:

cargo:rustc-flags=-l foo -L src/c/foo

1 Like

In addition to possible path issues, you need to actually make a static library using ar, rather than just having a .o file. You can Google for more details on how C static libraries work.

1 Like

Thanks @td_ for an excellent question, and to @felixc and @comex for great guidance. I was actually reading the FFI chapter of a Rust book when this was posted, and I decided to have a go at linking a Rust Project with a custom C Library that simply printed out "Hello, World!".

Here's how I got it to work:

  • Cargo.toml was updated to use a Build Script in a file named build.rs. The 'links' entry was added to the manifest, stating that that the package links to the 'libhello' native library. I used the gcc-rs Rust Build Library (helpful in invoking gcc as a C compiler for Rust). I read the Build Script link shared by @felixc at http://doc.crates.io/build-script.html. I updated Cargo.toml with a Build Dependency to gcc-rs on GitHub.
[package]
links = "hello"
build = "build.rs"

[build-dependencies.gcc]
git = "https://github.com/alexcrichton/gcc-rs"

Note: As an alternative to using the GitHub link, as described here one may instead add the gcc dependency with:

[build-dependencies]
gcc = "0.3"
  • hello.c source code was created in the folder ./src/c/ (relative to the Rust Project root directory)
#include <stdio.h>

void hello() {
    printf("Hello, World!\n");
}
  • build.rs file was created in the same directory as Cargo.toml with the following contents. I referred to the gcc Crate's Documentation and used the advanced configuration. This gcc Crate compiles the C code contained in hello.c into a Static C Library archive libhello.a (.a extension is an object code archive file):
extern crate gcc;

fn main() {
    gcc::Config::new()
                .file("src/c/hello.c")
                .include("src")
                .compile("libhello.a");
}
  • main.rs (Crate Root of the Binary Crate) was created in ./src
use std::old_io;

fn main() {
    #[link(name="hello", kind="static")]
    extern { fn hello(); }
    unsafe { hello(); };
}

Running cargo run successfully compiles and prints "Hello, World!" in the terminal.

Note however that for some reason when I pushed this code to GitHub, the Travic CI build failed with error (I do not know how to fix this):

error: linking with `cc` failed: exit code: 1
7 Likes

Thanks for this!

The build script guide you linked to is also great and if I had found it beforehand it would have covered my questions it seems like.

Thanks for your detailed howto, it was a huge help. I have one suggestion.

It appears that the gcc module will magically cause the output code to be linked into the binary, making the #[link(...)] attribute unnecessary. In certain cases having both causes a build failure. If you had the full text of your failure on Travis, it might have resembled the following:

note: /home/cbiffle/proj/rs/httpd/target/debug/build/httpd-a90ca0c92eca1acb/out/libtimeout.a(timeout.o): In function `wait_for_data':
timeout.c:(.text.wait_for_data+0x0): multiple definition of `wait_for_data'
/home/cbiffle/proj/rs/httpd/target/debug/build/httpd-a90ca0c92eca1acb/out/libtimeout.a(timeout.o):timeout.c:(.text.wait_for_data+0x0): first defined here
/home/cbiffle/proj/rs/httpd/target/debug/build/httpd-a90ca0c92eca1acb/out/libtimeout.a(timeout.o): In function `wait_for_writeable':
timeout.c:(.text.wait_for_writeable+0x0): multiple definition of `wait_for_writeable'
/home/cbiffle/proj/rs/httpd/target/debug/build/httpd-a90ca0c92eca1acb/out/libtimeout.a(timeout.o):timeout.c:(.text.wait_for_writeable+0x0): first defined here
collect2: error: ld returned 1 exit status

(Except with your function names, clearly.)

The problem? -ltimeout appears on the command line twice. Removing the #[link(...)] attribute and relying on gcc to set up the command line fixes this.

In my case it only appeared during cargo test, not cargo build, for reasons I don't entirely understand -- but it seems I am not the first to have noticed this.

1 Like

I followed this same method but I received this error: /usr/bin/ld: cannot find -llibtest (I called it libtest). I see that the library is generated with no problem but it seems like it couldn't be found by the linker. I'm running this under ubuntu 16. Is this method still working?

You need to link against "test", not "libtest".

Great.. Thanks! After that I received a strange error:

libtest.a(mylib.o): In function `hello':
.../mylib.c:3: multiple definition of `hello'
.../out/libtest.a(mylib.o):.../mylib.c:3: first defined here
collect2: error: ld returned 1 exit status

error: aborting due to previous error

mylib.c just has that hello function and nothing else. The funny part is that both the multiple definition and the first defined here messages point to line 3 which is the start of function hello. It appears that the function is included in the compilation twice. It sounds like this has happened before. I just fixed it by removing #[link(name="test", kind="static")].

Leaving this here for other people who might have had the same issue - this is because you're linking the source file (mylib.c) in the build process to create the shared library, libtest.a. It looks like both get linked if built in this fashion, and so the solution here is comment out the "main" function that you have in your C source code. It should work fine then.

Alternately, you can directly follow the procedure mentioned in the Rust Book, and link the static library manually instead of building the C source code as part of your Cargo project. I found that that worked cleanly (and without a need to comment out the main function in the C source file).

I think *.o is not static library :joy::joy::joy:

1 Like