Skip to content

Instantly share code, notes, and snippets.

@cfstras
Last active December 3, 2023 22:01
Show Gist options
  • Save cfstras/dda9bace3500c35678df52a30bc0155b to your computer and use it in GitHub Desktop.
Save cfstras/dda9bace3500c35678df52a30bc0155b to your computer and use it in GitHub Desktop.
Building & Calling go code from rust.

Using Go code from within Rust

This example, mostly stolen adapted from arranfrance.com, shows how to easily call into some go code from Rust.

Disclaimer: I have not used this in production, and would advise cautiosly reviewing ABI guarantees and threading considerations before doing so. For toy programs, and fun stuff such as solving half of an Advent-of-Code day in a second language, it seems to work without issues.

Main actors:

  1. main.go: Your Go code, with at least one exported CGo function. You can also import other go library as you wish.
  2. build.rs: A script telling cargo to invoke the go compiler, with an argument to build a static library instead of an executable program, and tell cargo to add that static library during linking.
  3. main.rs: Your Rust code, using std::ffi to call to the exported CGo functions.

Usage:

cargo run

That's all there is to it! 🎉

Example output:

➜ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/using_go_from_rust`
Hello from Go. I got the input: This is a test string
Result from Go: 123

Happy hacking!

use std::process::Command;
fn main() {
let path = "./";
println!("cargo:rustc-link-search=native={}", path);
println!("cargo:rerun-if-changed=main.go");
//println!("cargo:rerun-if-changed=<more_files...>.go");
eprintln!("build.rs running now");
let o = Command::new("go")
.args(["build", "-buildmode=c-archive", "-o=libgo.a"])
.output();
let o = o.expect("go build failed");
eprintln!("{}", String::from_utf8(o.stdout).unwrap());
eprintln!("{}", String::from_utf8(o.stderr).unwrap());
assert!(o.status.success());
println!("cargo:rustc-link-lib=static=go"); // -> libgo.a
}
[package]
name = "using_go_from_rust"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "using_go_from_rust"
path = "main.rs"
module gist.github.com/cfstras/dda9bace3500c35678df52a30bc0155b
go 1.20
package main
import "C"
import "fmt"
// Unused, but needs to be declared
func main() {}
// The following magic "export" comment tells CGo to export this and write its signature to `libgo.h`
//
//export MyGoFunction
func MyGoFunction(cInput *C.char) int64 {
input := C.GoString(cInput)
fmt.Println("Hello from Go. I got the input:", input)
// You can also return structs, *C.chars, or even Go strings.
// These will also be declared by the go compiler as C code in `libgo.h`, so that you can write your Rust FFI bindings.
return 123
}
use std::ffi::CString;
use std::os::raw::c_char;
extern "C" {
fn MyGoFunction(input: *const c_char) -> i64;
}
fn main() {
let c_input = CString::new("This is a test string").expect("CString::new failed");
let result: i64 = unsafe { MyGoFunction(c_input.as_ptr()) };
println!("Result from Go: {result}");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment