1

I trying to work though some simple FFI things to figure out how to get Rust to work with C or C++ code. My immediate question is:

It seems when I compile Rust code to an object file, it is missing some of the bindings for the clang linker to finalize an executable. Here are two very simple files that I am using.

clink.c

#include <stdio.h>
#include <stdbool.h>

int rust_int(int);
bool rust_bool(int, int);

/********************************/
/* void rust_char_star(char *); */
/********************************/

int main(void) {
    printf("%d\n", rust_int(5));
    if (rust_bool(5, 6)) {
        printf("True\n");
    }
    else { printf("False\n"); }

    /******************************/
    /* rust_char_star("Testing"); */
    /******************************/

    return 0;
}

rustlink.rs

use std::os::raw::{c_int, c_char};
// use std::ffi::{CStr, CString};

#[no_mangle]
pub extern "C" fn rust_int(i: c_int) -> c_int {
    i
}

#[no_mangle]
pub extern "C" fn rust_bool(x: c_int, y: c_int) -> bool {
    if x > y {
        true
    }
    else {
        false
    }
}

////////////////////////////////////////////////////////////////////////////
// #[no_mangle]                                                           //
// pub extern "C" fn rust_char_star(c: *const c_char) {                   //
//     let str_printable = unsafe {CString::from_raw(c as *mut c_char) }; //
//                                                                        //
//     println!("{:?}", str_printable);                                   //
// }                                                                      //
////////////////////////////////////////////////////////////////////////////

When I compile each of these without the CString, everything works as expected. Ignore the warning.

~/dev/rust/learn/clink$ rustc --emit obj --crate-type staticlib rustlink.rs
warning: unused import: `c_char`
use std::os::raw::{c_int, c_char};
                          ^^^^^^
= note: #[warn(unused_imports)] on by default

~/dev/rust/learn/clink$ clang clink.c rustlink.o -o test
~/dev/rust/learn/clink$ ./test
5
False

When I uncomment all the code to use the type CString though, that's where I'm missing something. I'm not going to repost the above code with the comments removed. Here is what the output yields though.

~/dev/rust/learn/clink$ rustc --emit obj --crate-type staticlib rustlink.rs 
warning: unused import: `CStr`
 --> rustlink.rs:2:16
  |
2 | use std::ffi::{CStr, CString};
  |                ^^^^
  |
  = note: #[warn(unused_imports)] on by default

~/dev/rust/learn/clink$ clang clink.c rustlink.o -o test
rustlink.o: In function `alloc::alloc::dealloc':
rustlink.3a1fbbbh-cgu.0:(.text._ZN5alloc5alloc7dealloc17hca8aab9ecdf50cafE+0x43): undefined reference to `__rust_dealloc'
rustlink.o: In function `rust_char_star':
rustlink.3a1fbbbh-cgu.0:(.text.rust_char_star+0xa): undefined reference to `std::ffi::c_str::CString::from_raw'
rustlink.3a1fbbbh-cgu.0:(.text.rust_char_star+0x3a): undefined reference to `<std::ffi::c_str::CString as core::fmt::Debug>::fmt'
rustlink.3a1fbbbh-cgu.0:(.text.rust_char_star+0xa9): undefined reference to `std::io::stdio::_print'
rustlink.o:(.data.DW.ref.rust_eh_personality[DW.ref.rust_eh_personality]+0x0): undefined reference to `rust_eh_personality'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
~/dev/rust/learn/clink$ 

Going a little further. I attempted to compile the Rust code to a static library to be fed into clang. I'm uncertain why, but this is the result.

~/dev/rust/learn/clink$ rustc --crate-type staticlib rustlink.rs 
warning: unused import: `CStr`
 --> rustlink.rs:2:16
  |
2 | use std::ffi::{CStr, CString};
  |                ^^^^
  |
  = note: #[warn(unused_imports)] on by default

~/dev/rust/learn/clink$ ls
clink.c  librustlink.a  rustlink.o  rustlink.rs
~/dev/rust/learn/clink$ clang clink.c -o test -lrustlink
/usr/bin/ld: cannot find -lrustlink
clang: error: linker command failed with exit code 1 (use -v to see invocation)
~/dev/rust/learn/clink$ clang clink.c -o test -L -lrustlink
/tmp/clink-357672.o: In function `main':
clink.c:(.text+0x15): undefined reference to `rust_int'
clink.c:(.text+0x3a): undefined reference to `rust_bool'
clink.c:(.text+0x83): undefined reference to `rust_char_star'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
~/dev/rust/learn/clink$

This is where I am a little confused. I thought when you tell rustc that the crate-type is static that it includes all dependencies, but I guess that is not the case.

Clearly I am missing a step. I would prefer that rustc include all bindings so I can just use a simple command to clang to produce an executable.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366

1 Answers1

1

I was missing the standard library bindings. After a little more Googling I found a few links that were helpful:

It turns out I just needed to know where the Rust standard object file was. Running the following is a solution:

~/dev/rust/learn/clink$ rustc --crate-type staticlib rustlink.rs
~/dev/rust/learn/clink$ clang clink.c -o test ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/libstd-89cf9eb8d404bb7b.so -L . -lrustlink
nick@Void:~/dev/rust/learn/clink$ ./test
5
False
"Testing"
Did I get here?
~/dev/rust/learn/clink$ 

I added a line "Did I get here" because when I was originally successful in compilation, the program segfaulted before completion. I was trying to figure out if it was the CString printing that was causing it or not. Turns out I had a sort of double free condition. I will add the revised Rust code at the very bottom for completeness.

As a second solution, and more to my liking, you can use an object file:

~/dev/rust/learn/clink$ rustc --emit obj --crate-type staticlib rustlink.rs --verbose
~/dev/rust/learn/clink$ clang clink.c -o test rustlink.o ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/libstd-89cf9eb8d404bb7b.so 
~/dev/rust/learn/clink$ ./test
5
False
"Testing"
Did I get here?
~/dev/rust/learn/clink$ 

They both result in the same output, and I can't really say if one way is better than the other. It depends on what your requirements are in development.

Last. My original code above for rustlink.rs was wrong. I learned that using a CString that is bound to a raw pointer is pretty much a bad idea. It needs to allocate its own memory, I think, in most circumstances. This is due to drop. I changed the rust_char_star function from the original post to the following.

#[no_mangle]
pub extern "C" fn rust_char_star(c: *const c_char) {
    let ch = unsafe { CStr::from_ptr(c) };
    let str_printable = CString::from(ch);

    println!("{:?}", str_printable);
    println!("Did I get here?");
}

That prevents what is effectively a double free.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366