ihciah/rust2go

Call Between Golang and Rust Asynchronously

Rust

428

126 commits

updated Sep 17, 2026

See the code

README

Rust2Go

Crates.io codecov

Rust2Go is a project that provides users with a simple and efficient way to call Golang from Rust with native async support. It also support user calling Rust from Golang.

Blogs

Features

  • Sync and async calls from Rust to Golang
  • Sync calls from Golang to Rust
  • Efficient data exchange: no serialization or socket communication, but FFI
  • Simple interface design: no new invented IDL except for native rust

How to Use

  1. Define the structs and calling interfaces in restricted Rust syntax, and include generated code in the same file.
  2. Generate golang code with rust2go-cli --src src/user.rs --dst go/gen.go
    • Use --package-name <name> to set the package name of the generated go file (defaults to main).
    • Use --without-main to omit the go main function, --go118 for Go 1.18/1.19 compatibility, and --no-fmt to skip formatting the generated file.
  3. Write a build.rs for you project (see docs/build-rs.md for the full build script helper reference, including dynamic linking and custom go build arguments).
  4. You can then use generated implementation to call golang in your Rust project!

For detailed example, please checkout the example projects.

Binding File Notes

  • Supported types: i8/i16/i32/i64/isize, u8/u16/u32/u64/usize, f32/f64, bool, char, String, Vec<T>, user-defined structs, and non-generic type aliases (e.g. pub type Amount = i64;, expanded during code generation). Option<T> is treated as Vec<T>: None maps to an empty list on the Go side.
  • Trait functions may take zero, one or multiple parameters; empty (nil) slices are allowed as arguments and return values.
  • Structs keep their own attribute macros (e.g. #[derive(...)]) in the generated code, and #[rust2go::r2g_struct_tag(json = "snake_case")] adds tags to the generated Go struct fields. See docs/trait-attrs.md for the full attribute reference.

Stateful Go-to-Rust Traits (&self methods)

For the Go-to-Rust direction (#[rust2go::g2r]), if every method of the trait takes &self, the trait becomes stateful: instead of expecting you to implement the trait on the generated unit struct, the macro generates a process-wide instance registry plus a register function, and the FFI entries dispatch through the registered instance. (Mixing &self and non-&self methods in one trait is a compile error, as are &mut self or by-value self receivers.)

#[rust2go::g2r]
pub trait G2RCounter {
    fn incr(&self, by: u64) -> u64;
    fn current(&self) -> u64;
}

struct Counter { count: AtomicU64 }

impl G2RCounter for Counter {
    fn incr(&self, by: u64) -> u64 { self.count.fetch_add(by, Ordering::SeqCst) + by }
    fn current(&self) -> u64 { self.count.load(Ordering::SeqCst) }
}

// Call once at startup, before Go invokes any method:
G2RCounterImpl::register(Counter { count: AtomicU64::new(0) })
    .unwrap_or_else(|_| panic!("register once"));

Rules to know:

  • The implementation must be Send + Sync + 'static: Go may call the methods from any thread, so keep mutable state behind atomics or a Mutex.
  • Register exactly once at startup; a second register call returns Err (the global OnceLock is consumed).
  • If Go calls a method before registration, the FFI entry prints an error and aborts the process (fail-fast for a startup-ordering bug).
  • The registered instance lives for the whole process; there is intentionally no unregister.
  • The Go-side calling convention and the FFI ABI are unchanged relative to stateless traits.

See examples/example-go2rust for a complete runnable demo (including the rust_lib_init cgo init hook used to register from a staticlib).

Key Design

Detailed design details can be found in this article: Design and Implementation of a Rust-Go FFI Framework.

Why Fast?

  1. Memory layout: Rust2go only manipulates memory when needed. In most cases it passes memory reference.
  2. Message passing: Rust2go relies on CGO to pass calling information. In addition, it also supports lock-free queues based on shared memory to improve performance during high-frequency communication.
  3. Other optimizations: Rust2go uses Go callback based on manual assembly instead of CGO to achieve better performance.

In order to achieve the ultimate performance, this project is not purely based on communication, but on FFI to pass specially encoded data. In order to reduce memory operations to a minimum, data that satisfies a specific memory layout is passed directly by reference rather than copied.

For example, Vec<u8> and String is represented as a pointer and a length. However, structs like Vec<String> or Vec<Vec<u8>> require intermediate representation. In order to reduce the number of memory allocations to one, I use a precomputed size buffer to store these intermediate structures.

Memory Safety

On the Golang side, the data it receives is referenced from Rust. The Rust side will do its best to ensure the validity of this data during the call. So the Golang side can implement the handler arbitrarily, but manually deep copy when leaking data outside the function life cycle.

On the Rust side, it is needed to ensure that the slot pointer of the callback ffi operation, and the user parameters are valid when the future drops. This is archieved by implementing an atomic slot structure and providing a [drop_safe] attribute to require user passing parameters with ownership.

Note: Since golang may scan the stack, and when it meets peer pointer, it may panic. You should run the program with GODEBUG=invalidptr=0,cgocheck=0 env to bypass it.

Toolchain Requirements

  • Golang: >=1.18
    • For >=1.18 && < 1.20: generate golang code with --go118
    • For >=1.20: generate golang code normally
  • Rust: >=1.75 if you want to use async; crates using rust2go may use edition 2021 or 2024 (edition 2024 requires Rust >=1.85)

Platform Support

  • Linux, macOS and Windows are supported.
  • The ASM-based callback is available on amd64 and arm64; on other platforms it falls back to the CGO implementation automatically.
  • The shared memory based implementation (#[mem]/#[shm]) requires unix.

Milestones

Init Version

  • IDL(in rust) parse
  • Go code generation
  • Build script helper
  • Basic data types and convertion generation
  • Rust impl generation
  • Future and basic synchronization primitives used

Basic Ability Enhancement

  • More complicated data types support
  • Support user passing references
  • More elegant code generation implementation
  • Better build cache control
  • Golang interface support(separate user code from generated code)
  • Dynamic linking support
  • Golang helper library

Performance Optimization

  • Shared memory based implementation
  • Faster ASM-based callback instead of CGO

Extended Features

  • Support calling rust from golang

Engineering & Maintainability

  • Codegen restructured: one primitive-type table shared by all emitters, ir/emit layering for both call directions, and Go templates as standalone .go.tmpl files
  • generate() extracted into the rust2go-gen library crate; the CLI is a thin shell over it
  • Macro errors reported as spanned syn::Error diagnostics instead of compiler panics
  • mem-ring hardened: error returns instead of dead loops, stop mechanisms for background goroutines/tasks, fd ownership and error-path leak fixes
  • Examples deduplicated onto a shared demo template, with CI sync/freshness checks
  • CI: pinned Go toolchains (1.18 minimum + stable), gofmt/go vet gates, and linux/macOS/Windows coverage across amd64 and arm64 legs
  • Code coverage introduced and gated (project target 97%)
  • Documentation overhauled to match the code: build script reference, attribute reference, CI guide, and per-package READMEs

Coverage

codecov sunburst

Credit

This project is inspired by fcplug.

Contributors

ihciah

57 commits

lirenjie95

34 commits

hanabi1224

9 commits

ihciah/rust2go

Call Between Golang and Rust Asynchronously

Rust

428

126 commits

updated Sep 17, 2026

See the code

README

Rust2Go

Crates.io codecov

Rust2Go is a project that provides users with a simple and efficient way to call Golang from Rust with native async support. It also support user calling Rust from Golang.

Blogs

Features

  • Sync and async calls from Rust to Golang
  • Sync calls from Golang to Rust
  • Efficient data exchange: no serialization or socket communication, but FFI
  • Simple interface design: no new invented IDL except for native rust

How to Use

  1. Define the structs and calling interfaces in restricted Rust syntax, and include generated code in the same file.
  2. Generate golang code with rust2go-cli --src src/user.rs --dst go/gen.go
    • Use --package-name <name> to set the package name of the generated go file (defaults to main).
    • Use --without-main to omit the go main function, --go118 for Go 1.18/1.19 compatibility, and --no-fmt to skip formatting the generated file.
  3. Write a build.rs for you project (see docs/build-rs.md for the full build script helper reference, including dynamic linking and custom go build arguments).
  4. You can then use generated implementation to call golang in your Rust project!

For detailed example, please checkout the example projects.

Binding File Notes

  • Supported types: i8/i16/i32/i64/isize, u8/u16/u32/u64/usize, f32/f64, bool, char, String, Vec<T>, user-defined structs, and non-generic type aliases (e.g. pub type Amount = i64;, expanded during code generation). Option<T> is treated as Vec<T>: None maps to an empty list on the Go side.
  • Trait functions may take zero, one or multiple parameters; empty (nil) slices are allowed as arguments and return values.
  • Structs keep their own attribute macros (e.g. #[derive(...)]) in the generated code, and #[rust2go::r2g_struct_tag(json = "snake_case")] adds tags to the generated Go struct fields. See docs/trait-attrs.md for the full attribute reference.

Stateful Go-to-Rust Traits (&self methods)

For the Go-to-Rust direction (#[rust2go::g2r]), if every method of the trait takes &self, the trait becomes stateful: instead of expecting you to implement the trait on the generated unit struct, the macro generates a process-wide instance registry plus a register function, and the FFI entries dispatch through the registered instance. (Mixing &self and non-&self methods in one trait is a compile error, as are &mut self or by-value self receivers.)

#[rust2go::g2r]
pub trait G2RCounter {
    fn incr(&self, by: u64) -> u64;
    fn current(&self) -> u64;
}

struct Counter { count: AtomicU64 }

impl G2RCounter for Counter {
    fn incr(&self, by: u64) -> u64 { self.count.fetch_add(by, Ordering::SeqCst) + by }
    fn current(&self) -> u64 { self.count.load(Ordering::SeqCst) }
}

// Call once at startup, before Go invokes any method:
G2RCounterImpl::register(Counter { count: AtomicU64::new(0) })
    .unwrap_or_else(|_| panic!("register once"));

Rules to know:

  • The implementation must be Send + Sync + 'static: Go may call the methods from any thread, so keep mutable state behind atomics or a Mutex.
  • Register exactly once at startup; a second register call returns Err (the global OnceLock is consumed).
  • If Go calls a method before registration, the FFI entry prints an error and aborts the process (fail-fast for a startup-ordering bug).
  • The registered instance lives for the whole process; there is intentionally no unregister.
  • The Go-side calling convention and the FFI ABI are unchanged relative to stateless traits.

See examples/example-go2rust for a complete runnable demo (including the rust_lib_init cgo init hook used to register from a staticlib).

Key Design

Detailed design details can be found in this article: Design and Implementation of a Rust-Go FFI Framework.

Why Fast?

  1. Memory layout: Rust2go only manipulates memory when needed. In most cases it passes memory reference.
  2. Message passing: Rust2go relies on CGO to pass calling information. In addition, it also supports lock-free queues based on shared memory to improve performance during high-frequency communication.
  3. Other optimizations: Rust2go uses Go callback based on manual assembly instead of CGO to achieve better performance.

In order to achieve the ultimate performance, this project is not purely based on communication, but on FFI to pass specially encoded data. In order to reduce memory operations to a minimum, data that satisfies a specific memory layout is passed directly by reference rather than copied.

For example, Vec<u8> and String is represented as a pointer and a length. However, structs like Vec<String> or Vec<Vec<u8>> require intermediate representation. In order to reduce the number of memory allocations to one, I use a precomputed size buffer to store these intermediate structures.

Memory Safety

On the Golang side, the data it receives is referenced from Rust. The Rust side will do its best to ensure the validity of this data during the call. So the Golang side can implement the handler arbitrarily, but manually deep copy when leaking data outside the function life cycle.

On the Rust side, it is needed to ensure that the slot pointer of the callback ffi operation, and the user parameters are valid when the future drops. This is archieved by implementing an atomic slot structure and providing a [drop_safe] attribute to require user passing parameters with ownership.

Note: Since golang may scan the stack, and when it meets peer pointer, it may panic. You should run the program with GODEBUG=invalidptr=0,cgocheck=0 env to bypass it.

Toolchain Requirements

  • Golang: >=1.18
    • For >=1.18 && < 1.20: generate golang code with --go118
    • For >=1.20: generate golang code normally
  • Rust: >=1.75 if you want to use async; crates using rust2go may use edition 2021 or 2024 (edition 2024 requires Rust >=1.85)

Platform Support

  • Linux, macOS and Windows are supported.
  • The ASM-based callback is available on amd64 and arm64; on other platforms it falls back to the CGO implementation automatically.
  • The shared memory based implementation (#[mem]/#[shm]) requires unix.

Milestones

Init Version

  • IDL(in rust) parse
  • Go code generation
  • Build script helper
  • Basic data types and convertion generation
  • Rust impl generation
  • Future and basic synchronization primitives used

Basic Ability Enhancement

  • More complicated data types support
  • Support user passing references
  • More elegant code generation implementation
  • Better build cache control
  • Golang interface support(separate user code from generated code)
  • Dynamic linking support
  • Golang helper library

Performance Optimization

  • Shared memory based implementation
  • Faster ASM-based callback instead of CGO

Extended Features

  • Support calling rust from golang

Engineering & Maintainability

  • Codegen restructured: one primitive-type table shared by all emitters, ir/emit layering for both call directions, and Go templates as standalone .go.tmpl files
  • generate() extracted into the rust2go-gen library crate; the CLI is a thin shell over it
  • Macro errors reported as spanned syn::Error diagnostics instead of compiler panics
  • mem-ring hardened: error returns instead of dead loops, stop mechanisms for background goroutines/tasks, fd ownership and error-path leak fixes
  • Examples deduplicated onto a shared demo template, with CI sync/freshness checks
  • CI: pinned Go toolchains (1.18 minimum + stable), gofmt/go vet gates, and linux/macOS/Windows coverage across amd64 and arm64 legs
  • Code coverage introduced and gated (project target 97%)
  • Documentation overhauled to match the code: build script reference, attribute reference, CI guide, and per-package READMEs

Coverage

codecov sunburst

Credit

This project is inspired by fcplug.

Contributors

ihciah

57 commits

lirenjie95

34 commits

hanabi1224

9 commits

Languages

Rust

74.7%

Go

20.4%

Go Template

3.2%

Assembly

1.8%