Já pensou em integrar módulos Rust às suas aplicações em Go? Quais as vantagens? Bom…
Desempenho sem GC Rust não tem coletor de lixo. Em trechos críticos (cripto, compressão, parsers binários, imagem/vídeo, simd), você ganha throughput e latência mais previsível que em Go, útil quando pausas do GC incomodam.
Segurança de memória melhor que C via cgo Se você já desceria para C por performance, Rust dá o mesmo nível de baixo nível com verificação de empréstimos, evitando use-after-free, double free e data races. É a opção “rápida e segura” para a fronteira FFI do Go.
Acesso a ecossistema e recursos de baixo nível Crates maduros para codecs, cripto, regex de alta perf., SIMD (via std::arch/portable-simd), zero-copy, io_uring, bindings de drivers nativos etc. Você reaproveita muito código de sistema existente sem reescrever em Go.
Latência previsível e p99 menor Para pipelines que exigem jitter baixo (trading, media processing, inference local), mover o hot path para Rust reduz caudas (p95/p99), especialmente sob pressão do heap do Go.
Binários simples de distribuir Rust compila bem como staticlib/cdylib. Dá para linkar estaticamente (musl) e simplificar deploy de um único binário Go chamando a lib Rust, útil em edge e containers mínimos.
Concorrência sem data races no trecho crítico Você pode manter a orquestração em Go (goroutines, canais) e executar paralelismo pesado no lado Rust (rayon, tokio para I/O), isolando o risco de corridas onde a performance importa.
Interoperabilidade previsível FFI C estável, assinatura simples (ptr + len, ints, códigos de erro), sem dependência de runtimes exóticos. Facilita versionar e testar.
Em que tipo de apps usar?
Nestes casos talvez seja melhor evitar:
Boas práticas rápidas:
extern "C"
, tipos primitivos, ptr + len
, códigos de erro; nunca deixe panic!
atravessar FFI.free_*
no lado Rust (usando o mesmo allocator).cbindgen
para evitar drift.staticlib
para facilitar link e distribuição; no Linux, às vezes adicione -ldl -lm -lpthread
.Esse projeto é extremamente simples, com uma estrutura de diretórios enxuta:
gorust/
|
+-src/lib.rs
|
+-Cargo.toml
|
+-goapp/
|
+-main.go
|
+-include/<header C da função Rust>
|
+-lib/<bin da lib rust>
O código Rust é extremamente simples:
#[unsafe(no_mangle)]
pub extern "C" fn somar(a: i32, b: i32) -> i32 {
a + b
}
E o Cargo.toml
também é simples:
[package]
name = "gorust"
version = "0.1.0"
edition = "2024"
[lib]
# Vai gerar duas libs: Estática e dinâmica:
crate-type = ["cdylib", "staticlib"]
[dependencies]
libc = "0.2.2"
E, do lado Go
também:
package main
/*
#cgo CFLAGS: -I${SRCDIR}/include
#cgo LDFLAGS: -L${SRCDIR}/lib -lgorust -ldl -lm -lpthread
#include "gorust.h"
*/
import "C"
import "fmt"
func main() {
res := C.somar(C.int(10), C.int(20))
fmt.Printf("Result from Rust: %d\n", int(res))
}
Esses comentários são processados pelo CGO para compilar seu código! Note que tem um flag para carregar nossa lib -lgorust
de forma estática, incorporando tudo ao executável Go
. Dá para fazer de forma dinâmica também, mas você terá que distribuir arquivos separados.
Para começar, compile o código Rust
com: cargo build
.
O CGO precisa da assinatura da função Rust
convertida em C
. Se você quiser, pode gerar um .h
simples ou utilizar o cbindgen
:
cargo install --force cbindgen
cbindgen --lang c --output ./goapp/include/gorust.h
Isto vai instalar o cbindgen
e gerar um .h
diretamente na subpasta include
do projeto Go
.
Uma alternativa é anotar o código Go
com a assinatura da função, mas você vai ter que saber C
:
package main
/*
extern int somar(int a, int b);
#cgo LDFLAGS: -L${SRCDIR}/lib -lgorust -ldl -lm -lpthread
*/
Eu fui testar isso e deram alguns erros, então preferi voltar à primeira forma.
Agora é só compilar o código Go
:
cp ./target/debug/libgorust.a ./goapp/lib
cd goapp
go mod init gorust
export CGO_ENABLED=1
go build
E executar:
./gorust
Result from Rust: 30