432 words
2 minutes
How to Call Rust from Go Using FFI
2025-05-25

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 :

nu1lspaxe
/
go-ffi-rust
Waiting for api.github.com...
00K
0K
0K
Waiting...

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?

  1. Clone the GitHub repository :
git clone https://github.com/nu1lspaxe/go-ffi-rust.git
  1. Open rust_ffi and build files :
cd rust_ffi
cargo build --release
  1. Find rust_ffi.dll file in rust_ffi/target/release and copy to the same path as main.go :

Otherwise, the file path could not be found under Windows system.

cp rust_ffi/target/release/rust_ffi.dll .
  1. Happy runtime :
go run main.go

# Output:
# 5 + 3 = 8
# 4 * 6 = 24
# Hello, Gopher!
IMPORTANT

Foreign 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)
How to Call Rust from Go Using FFI
https://nu1lspaxe.github.io/posts/20250525/
Author
nu1lspaxe
Published at
2025-05-25