This article will introduce the design and implementation of a Rust FFI (Foreign Function Interface) framework that I created for calling Golang code from Rust. It will cover the design from the perspective of a designer and implementer, considering various options and explaining the choices made and the reasons behind them. It will also cover some implementation details.
The project is open-sourced on GitHub: https://github.com/ihciah/rust2go.
Compared to Golang, Rust programs are not garbage-collected and have stronger compile-time checks. Also thanks to LLVM, Rust gets the best possible compiler optimizations, which results in better performance and safety.
At ByteDance, to promote cost optimization, I participated in the development of a Rust RPC framework and led the implementation of several essential internal Rust SDKs for service discovery, metrics, logging, dynamic configuration, etc. Additionally, I provided compilation and runtime images, internal crates registry, and a public mirror (rsproxy.cn). Building on these infrastructure components, several core services have migrated to Rust, achieving significant performance gains: CPU usage decreased by more than 30%, and a marked reduction in the P99 latency for some latency-sensitive services. However, many of these services, such as proxies and caches, which do not require active maintenance, and services with complex and actively evolving business logic, are harder to migrate to Rust.
In theory, we could rewrite all Golang programs in Rust to achieve better performance, but in practice, this is met with considerable difficulties: First, rewriting all Golang dependencies may not be feasible; second, completing the rewrite all at once is difficult. If we could provide an efficient way of calling Golang from Rust, it would allow businesses to gradually make the switch to Rust, thereby addressing both issues.
This article covers a lot of ground. The overall narrative flow is as follows: first, I’ll discuss the overall solution selection and provide a minimal PoC; then, starting from this minimal PoC, I’ll expand and refine the solution to support the necessary features; finally, I’ll discuss some implementation details of interest from a framework implementation perspective.