Skip to the content.

Foreign Function Interface (FFI)

FFI enables programs written in one language to leverage libraries and functions from another language. For example, a Python program can call a C library to utilize high-performance functions.

Common use cases for FFI include:

Further reading:

Difference between FFI and ABI

Example: use c code from within rust code

Suppose we have a c code:

// example.c
#include <stdio.h>

void hello_from_c() {
    printf("Hello from C!\n");
}

int add_numbers(int a, int b) {
    return a + b;
}

generate a shared library and install it to libs

gcc -shared -o example.dll example.c -Wl,--out-implib,example.lib
copy example.dll D:\proj\example.com\lib\
copy example.lib D:\proj\example.com\lib\

use this library in rust code:

// main.rs
use std::os::raw::{c_int};

// Declare the functions from the C library that we want to call.
// We use `extern "C"` to specify the C calling convention.
extern "C" {
    fn hello_from_c();
    fn add_numbers(a: c_int, b: c_int) -> c_int;
}

fn main() {
    unsafe {
        // Call the function that prints a message
        hello_from_c();

        // Call the function that adds two numbers
        let sum = add_numbers(5, 7);
        println!("The sum is: {}", sum);
    }
}

to build the rust code, generate a build script:

// build.rs

fn main() {
    println!("cargo:rustc-link-lib=dylib=example");
    println!("cargo:rustc-link-search=native=D://proj//example.com//lib");
}

build:

D:\proj\example.com\rust\hello>cargo build
   Compiling hello v0.1.0 (D:\proj\example.com\rust\hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.12s

D:\proj\example.com\rust\hello>
D:\proj\example.com\rust\hello>cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
     Running `target\debug\hello.exe`
error: process didn't exit successfully: `target\debug\hello.exe` (exit code: 0xc0000135, STATUS_DLL_NOT_FOUND)

set the environment and run it again:

D:\proj\example.com\rust\hello>set PATH=D:\proj\example.com\lib;%PATH%

D:\proj\example.com\rust\hello>cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
     Running `target\debug\hello.exe`
Hello from C!
The sum is: 12

D:\proj\example.com\rust\hello>

Example: use rust code from within golang code

suppose we generate a lib of “add”:

cargo new add --lib

add code in the file:

// src/lib.rs
#[no_mangle]
pub extern "C" fn add(left: u32, right: u32) -> u32 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

Note:

set Cargo.toml files:

[lib]
crate-type = ["staticlib"] 

build the “add” lib and install it

D:\proj\example.com\rust\add>cargo build --release
   Compiling add v0.1.0 (D:\proj\example.com\rust\add)
    Finished `release` profile [optimized] target(s) in 0.16s

D:\proj\example.com\rust\add>copy target\release\add.lib D:\proj\example.com\lib\

write go code to use add.lib:

package main

/*
#cgo LDFLAGS: -LD:/proj/example.com/lib -ladd
#include <add.h>
*/
import "C"
import "fmt"

func main() {
    a := C.uint32_t(5)
    b := C.uint32_t(7)
    sum := C.add(a, b)
    fmt.Printf("The sum is: %d\n", sum)
}

add header file of add.h inside go workspace

// add.h

#include <stdint.h>

int32_t add(uint32_t a, uint32_t b);

build an exe to run:

D:\proj\example.com\go\main>go build -o main.exe main.go

D:\proj\example.com\go\main>.\main.exe
The sum is: 12

Note: to build such a “cgo” file, gcc shall be pre-installed.

D:\proj\example.com\go\main>gcc --version
gcc (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.