Background
I worked on an industry-academia collaboration proejct during my sophomore year, where we used C++ to build computation API and compiled it for use in a Golang server. This was probably my first experience working on a cross-language project.
This new project came about because I’ve been learning Rust recently, and I’ve always wanted to try compiling cross-language scripts myself.
The source code is referred from :
Preface
Assuming readers already have a basic understanding of Rust and Golang, and have both environments set up, this article will focus on the concept of FFI (Foreign Function Interface).
Since I’m using a Windows system, please note that path resolution and linking to DLL files may differ slightly on other OS. You may need to make some adjustments based on your operation system.
Setup
Nothing beats the feeling of getting the program to run, right?
- Clone the GitHub repository :
git clone https://github.com/nu1lspaxe/go-ffi-rust.git
- Open
rust_ffi
and build files :
cd rust_ffi
cargo build --release
- Find
rust_ffi.dll
file inrust_ffi/target/release
and copy to the same path asmain.go
:
Otherwise, the file path could not be found under Windows system.
cp rust_ffi/target/release/rust_ffi.dll .
- Happy runtime :
go run main.go
# Output:
# 5 + 3 = 8
# 4 * 6 = 24
# Hello, Gopher!
IMPORTANTForeign Function Interface (FFI)
Foreign Function interface (FFI) acts as a bridge between different languages, enabling interoperability by allowing a program to invoke functions, access data structures, or use libraries from a different langauge’s environment.
Since Rust and Golang don’t have stable API support yet, we compile the code into C.
Rust Library
1. cbindgen
We use the cbindgen crate to generate header files, just like in C++.
First, create a file named cbindgen.toml
in rust_ffi/
.
language = "C"
header = "/* Auto-generated by cbindgen */"
include_version = true
include_guard = "RUST_FFI_H"
2. C Interface Definition
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32
- With
#[no_mangle]
trait to prevent compiler from mangling function names - Use
extern "C"
to specify C calling convention - Functions must be public (
pub
)
#[no_mangle]
pub extern "C" fn greet(name: *const c_char) -> *mut c_char {
let name_str = unsafe { std::ffi::CStr::from_ptr(name) }.to_str().unwrap_or("Unknown");
let greeting = format!("Hello, {}!", name_str);
let c_str = std::ffi::CString::new(greeting).unwrap();
c_str.into_raw()
}
- Use C-compatible types like
std::os::raw::c_char
(avoid Rust-sepcific types) - With
unsafe
blocks for pointer operations - Import
std::ffi::CString
for string conventsions
pub extern "C" fn free_string(s: *mut c_char) {
unsafe {
if !s.is_null() {
let _ = std::ffi::CString::from_raw(s);
}
}
}
- Provide explicit resource cleanup functions
- With
unsafe
blocks for memory deallocation
Invoking From Golang
1. C Interface Invocation
// #cgo LDFLAGS: -L./rust_ffi/target/release -lrust_ffi
// #include <stdlib.h>
// #include "./rust_ffi/src/rust_ffi.h"
import "C"
result := C.add(5, 3)
fmt.Printf("5 + 3 = %d\n", result)
2. Calling Functions
name := "Gopher"
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
cGreeting := C.greet(cName)
defer C.free_string(cGreeting)
greeting := C.GoString((*C.char)(cGreeting))
fmt.Println(greeting)