Go vs Rust: productivitat, rendiment i complexitat real

Comparació pràctica entre Go i Rust per a backend i sistemes. Rendiment, corba d'aprenentatge, seguretat de memòria i velocitat d'entrega.

Cover for Go vs Rust: productivitat, rendiment i complexitat real

Cada vegada que algú pregunta Go vs Rust en un fòrum, les dues comunitats es posen a la defensiva. Els de Go diuen que Rust és innecessàriament complex. Els de Rust diuen que Go és un llenguatge de joguina. I així porten anys. I sent honestos, tots dos tenen una mica de raó i una mica d’exageració.

Vinc del món JVM (Kotlin, Java, Spring) i de Python, i porto un temps treballant amb Go i estudiant Rust per curiositat genuïna. El que puc aportar és una perspectiva d’algú que no té la samarreta posada de cap dels dos: els he avaluat com a eines, no com a identitats.

El que llegiràs aquí és una comparació honesta, amb exemples reals, sense fanatisme. Parlarem de rendiment, seguretat de memòria, corba d’aprenentatge, tooling, concurrència i, sobretot, de quan té sentit triar cadascun.


Objectius diferents: simplicitat vs control

El primer que cal entendre — i crec que és on comença gran part de la confusió — és que Go i Rust no competeixen pel mateix nínxol, tot i que es solapen en alguns casos d’ús.

Go va ser creat a Google per resoldre un problema concret: que equips grans poguessin escriure programari de servidor ràpid, amb un llenguatge senzill d’aprendre i amb temps de compilació curts. Rob Pike, Ken Thompson i Robert Griesemer van dissenyar Go per ser avorrit a propòsit. Poques abstraccions, poques formes de fer el mateix, productivitat d’equip per sobre de l’expressivitat individual.

Rust va néixer a Mozilla amb un altre objectiu: escriure codi de sistemes que fos segur a nivell de memòria sense necessitar un garbage collector. El focus és el control total: saber exactament quan s’assigna i allibera memòria, quan es fa una còpia i quan no. Rust vol que el compilador t’impedeixi cometre errors que en C o C++ descobriries en producció a les 3 de la matinada.

Triar entre Go i Rust no és triar el “millor” llenguatge. És triar quin problema estàs resolent.

Si el teu problema és construir un servei HTTP que respongui a peticions, es desplegui a Kubernetes i el mantingui un equip de cinc persones, Go és probablement l’opció més rendible. Si el teu problema és escriure un motor de base de dades, un compilador o un sistema encastat on cada microsegon importa, Rust té avantatges reals. La pregunta interessant no és “quin és millor”, sinó “què estic construint”.


Gestió de memòria: GC vs ownership

Aquesta és la diferència tècnica més profunda entre els dos llenguatges, i la que més conseqüències té en el dia a dia.

Go: garbage collector i a una altra cosa

Go fa servir un garbage collector concurrent amb pauses molt baixes (normalment per sota d’1 ms). A la pràctica, això significa que no penses en la memòria. Crees structs, passes punters, i el GC s’encarrega de netejar el que ja no s’usa.

func createUser(name string) *User {
    u := &User{Name: name, CreatedAt: time.Now()}
    return u // el GC gestiona el cicle de vida
}

És simple. Funciona. I per al 95% dels serveis backend és més que suficient. Crec que aquest percentatge és important, perquè molta gent optimitza per a aquell 5% restant sense estar-hi.

La contrapartida: el GC introdueix un overhead. No és dramàtic en Go (l’equip de Google porta anys optimitzant-lo), però existeix. En escenaris d’ultra-baixa latència o on necessites rendiment predictible al microsegon, aquell overhead importa. La pregunta que convé fer-se és: el meu cas d’ús és realment un d’aquests?

Rust: ownership i borrow checker

Rust no té garbage collector. En el seu lloc, té un sistema d’ownership que el compilador verifica en temps de compilació. Cada valor té un propietari, i quan aquell propietari surt de l’àmbit, el valor s’allibera.

fn create_user(name: String) -> User {
    User {
        name,
        created_at: Utc::now(),
    }
    // no hi ha GC, la memòria es gestiona per ownership
}

El concepte és elegant. I quan l’entens, té una lògica aclaparadora. Però la implementació requereix entendre borrowing, lifetimes, i quan usar &, &mut, Box, Rc, Arc, Clone… i és aquí on la cosa es complica. I no poc.

// Això no compila:
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}

// Necessites anotar lifetimes:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

Per a algú que ve de Java, Python o fins i tot Go, aquest codi és un mur. No perquè sigui dolent, sinó perquè exigeix un model mental completament diferent.

Rust t’obliga a pensar en la memòria des del minut u. Go et deixa oblidar-te’n gairebé sempre. Tots dos són decisions de disseny vàlides.


Corba d’aprenentatge: un cap de setmana vs diversos mesos

No hi ha forma suau de dir-ho, així que seré directe: la corba d’aprenentatge de Rust és brutal. He parlat amb desenvolupadors senior amb dècades d’experiència que han passat setmanes barallant-se amb el borrow checker. Això no significa que Rust sigui dolent — significa que exigeix un nivell d’inversió que cal tenir en compte.

Go: productiu en dies

Si ja saps programar en qualsevol llenguatge amb claus, Go s’aprèn ràpid. L’especificació del llenguatge cap en unes poques pàgines. No hi ha herència, no hi ha genèrics complexos (els genèrics de Go 1.18+ són intencionalment limitats), no hi ha macros, no hi ha traits amb implementacions per defecte. Si véns d’aprendre Go des de zero, pots estar escrivint serveis funcionals en un parell de dies.

package main

import (
    \"fmt\"
    \"net/http\"
)

func main() {
    http.HandleFunc(\"/health\", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, \"ok\")
    })
    http.ListenAndServe(\":8080\", nil)
}

Això és un servidor HTTP funcional. Sense dependències externes. Sense framework. Sense configuració.

Rust: productiu en mesos

En Rust, l’equivalent més mínim amb la biblioteca estàndard requereix bastant més cerimònia. La majoria de la gent fa servir frameworks com Actix o Axum:

use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new().route(\"/health\", get(|| async { \"ok\" }));
    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:8080\")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

No sembla gaire més, però la realitat és que per arribar a escriure això amb confiança necessites entendre: el runtime asíncron (Tokio), closures, traits (IntoResponse, Handler), macros procedurals (#[tokio::main]), gestió d’errors amb Result/unwrap/?… i això és només per a un endpoint trivial.

AspecteGoRust
Temps fins al primer servei funcional1-3 dies2-4 setmanes
Temps fins al domini raonable2-4 setmanes3-6 mesos
Complexitat del model mentalBaixa-mitjanaAlta
Documentació i recursos per aprendreExcel·lentBona però densa
Facilitat d’onboarding per a un equipAltaBaixa-mitjana

Rendiment: on Rust guanya de veritat i on Go és suficient

Aquesta és la part on els benchmarks s’extreuen de context constantment, i on crec que es genera més confusió innecessària. Intentem ser precisos.

On Rust és objectivament més ràpid

  • CPU-bound pur: parsing, serialització, criptografia, processament de dades en brut. Rust genera codi tan ràpid com C/C++. Go no pot competir aquí.
  • Latència predictible: sense GC no hi ha pauses. Això importa en trading d’alta freqüència, motors de jocs, o sistemes en temps real.
  • Ús de memòria: Rust permet controlar exactament quanta memòria fa servir el teu programa. Go pot consumir més per l’overhead del runtime i el GC.

On Go és “prou ràpid”

  • APIs HTTP: la diferència entre respondre en 0.8 ms (Rust) i 1.2 ms (Go) és irrellevant quan la teva base de dades tarda 15 ms.
  • Microserveis estàndard: CRUD, processament de missatges, workers. El coll d’ampolla gairebé mai és el llenguatge.
  • Tasques d’I/O: si el teu servei espera a la xarxa o al disc el 90% del temps, optimitzar el codi CPU-bound és micro-optimització.
// Benchmark típic: serialització JSON
// Go amb encoding/json: ~2.5 μs/op
// Go amb jsoniter: ~0.8 μs/op
// Rust amb serde: ~0.3 μs/op

Rust és 3-8x més ràpid en serialització JSON. I és temptador mirar aquests números i concloure que Rust és l’opció correcta. Però si el teu endpoint tarda 50 ms en total perquè hi ha una query a PostgreSQL pel mig, aquella diferència de microsegons és soroll. I optimitzar el soroll és una trampa en la qual és fàcil caure.

El rendiment de Rust és superior. La pregunta real és si el teu cas d’ús necessita aquell rendiment.

Per a escenaris on Go pot gestionar càrrega pesada sense problemes, pots consultar Go per a tasques pesades.


Serveis backend: tots dos poden, però Go lliura abans

Anem al concret. En l’ecosistema de backend i microserveis, Go té un avantatge clar: velocitat d’entrega. I això, en el món real on els sprints tenen data, importa més del que la comunitat tècnica sol admetre.

Go per a backend

  • net/http a la biblioteca estàndard és production-ready.
  • Frameworks com Gin, Echo o Fiber són madurs i ben documentats.
  • L’ecosistema de drivers (PostgreSQL, Redis, Kafka, gRPC) és sòlid.
  • Els binaris són estàtics i petits: un contenidor Docker pot pesar 10-15 MB.
  • El tooling (go build, go test, go vet) ve inclòs i funciona.
func (h *Handler) GetUser(c *gin.Context) {
    id := c.Param(\"id\")
    user, err := h.repo.FindByID(c.Request.Context(), id)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{\"error\": \"user not found\"})
        return
    }
    c.JSON(http.StatusOK, user)
}

Si vols aprofundir en com Go encaixa en arquitectures modernes, mira Go en cloud-native.

Rust per a backend

Rust també pot fer backend, i ho fa bé. Axum (de l’equip de Tokio) i Actix-web són frameworks seriosos. Però hi ha friccions:

  • Compilació lenta: un projecte mitjà pot tardar 2-5 minuts a compilar des de zero. Go compila el mateix projecte en segons.
  • Complexitat amb tipus: gestionar errors de forma ergonòmica requereix crear tipus d’error custom, implementar traits com From, i decidir entre anyhow, thiserror, o gestió manual.
  • Menys biblioteques “llestes per usar”: l’ecosistema creix ràpid, però hi ha nínxols on Go té opcions més madures.
async fn get_user(
    State(pool): State<PgPool>,
    Path(id): Path<String>,
) -> Result<Json<User>, AppError> {
    let user = sqlx::query_as!(User, \"SELECT * FROM users WHERE id = $1\", id)
        .fetch_one(&pool)
        .await
        .map_err(|_| AppError::NotFound(\"user not found\".into()))?;
    Ok(Json(user))
}

El codi Rust és correcte i segur. No ho discuteixo. Però requereix més cerimònia, més tipus custom, i més temps per escriure. I aquí ve la reflexió que em sembla més honesta: en un equip que ha de lliurar features cada sprint, aquella cerimònia addicional suma. No perquè sigui innecessària, sinó perquè té un cost d’oportunitat que no sempre es justifica.


Programació de sistemes: el domini real de Rust

Però hi ha un terreny on la conversa canvia per complet. Aquí és on Rust brilla sense discussió, i on seria injust no reconèixer-ho. Programació de sistemes significa:

  • Sistemes operatius (Linux té components en Rust).
  • Navegadors (Servo, parts de Firefox).
  • Bases de dades (TiKV, SurrealDB, Neon).
  • Eines de xarxa (Cloudflare fa servir Rust extensament).
  • Compiladors i tooling (el propi compilador de Rust, swc, turbopack).
  • Sistemes encastats i WASM.

En aquests dominis, Rust reemplaça C/C++ amb l’avantatge de seguretat de memòria en temps de compilació. I això no és una opinió — és un fet observable. Go simplement no és una opció aquí perquè:

  • El GC introdueix latència impredictible.
  • El runtime de Go consumeix recursos que en un sistema encastat no tens.
  • No tens el control de memòria que necessites per escriure un allocator o un driver.

Si estàs construint alguna cosa que històricament s’hauria fet en C, Rust és la resposta moderna. Go no pretén ser aquella resposta.


Tooling i ecosistema

Go

  • go build: compila ràpid, binari estàtic. Sense make, sense CMake, sense Cargo.toml. Sense drama.
  • go test: testing integrat amb benchmarks i coverage.
  • go vet i golangci-lint: anàlisi estàtica sòlida.
  • go mod: gestió de dependències que funciona sense sorpreses.
  • gofmt: formatació canònica. No hi ha discussions d’estil als PRs.
  • gopls: LSP que funciona bé amb qualsevol editor.

Tot ve inclòs o s’instal·la amb una comanda. L’experiència de tooling en Go és consistent i predictible. No t’emocionaràs amb ella, però tampoc et farà malbé un matí.

Rust

  • Cargo: possiblement el millor gestor de paquets i build system que existeix. De debò. És excel·lent.
  • rustfmt i clippy: formatació i linting de primer nivell.
  • crates.io: el registre de paquets funciona bé.
  • rust-analyzer: LSP potent tot i que de vegades pesat en projectes grans.
EinaGoRust
Build systemgo build (integrat)Cargo (excel·lent)
Gestor de paquetsgo modCargo/crates.io
Formataciógofmtrustfmt
Lintinggo vet + golangci-lintclippy
Testinggo test (integrat)cargo test (integrat)
Velocitat de compilacióMolt ràpidaLenta (millorant)
Cross-compilationTrivial (GOOS/GOARCH)Possible però més complex

El tooling de Rust és objectivament bo. Cargo és una meravella — probablement l’aspecte de Rust que més enveja sana genera. Però la velocitat de compilació segueix sent un punt de fricció real en el dia a dia. I no és un detall menor. Quan el teu bucle de desenvolupament és “canviar una línia, compilar, provar”, esperar 30 segons (o minuts en builds nets) canvia la teva forma de treballar. Et tornes més curós abans de compilar, sí, però també més lent per iterar.


Concurrència: goroutines vs async/Tokio

La concurrència és un dels punts forts de tots dos llenguatges, però amb filosofies molt diferents.

Go: goroutines i channels

Go va néixer amb la concurrència com a ciutadà de primera classe. Les goroutines són lleugeres (uns pocs KB d’stack), es creen amb go func(), i es comuniquen amb channels.

func processOrders(orders []Order) []Result {
    results := make(chan Result, len(orders))

    for _, order := range orders {
        go func(o Order) {
            result := process(o)
            results <- result
        }(order)
    }

    var processed []Result
    for range orders {
        processed = append(processed, <-results)
    }
    return processed
}

El model és intuïtiu: llances goroutines, et comuniques amb channels, i el scheduler de Go s’encarrega de la resta. No necessites pensar en runtimes, executors ni pinning.

Rust: async/await amb Tokio

Rust no té un runtime asíncron integrat. Fas servir Tokio (l’estàndard de facto), i el codi es basa en async/await amb futures:

async fn process_orders(orders: Vec<Order>) -> Vec<Result> {
    let handles: Vec<_> = orders
        .into_iter()
        .map(|order| {
            tokio::spawn(async move {
                process(order).await
            })
        })
        .collect();

    let mut results = Vec::new();
    for handle in handles {
        results.push(handle.await.unwrap());
    }
    results
}

Funciona, i Tokio és impressionant en rendiment. No tinc cap dubte d’això. Però el model asíncron de Rust té complexitats que Go simplement evita:

  • Pinning: alguns futures necessiten estar “pinned” en memòria. Això afegeix complexitat que no existeix en Go.
  • Send + Sync bounds: quan comparteixis dades entre tasks asíncrones, el compilador t’exigeix demostrar que és segur fer-ho. Correcte, però verbós.
  • Colored functions: async fn i fn són mons diferents. Cridar una funció síncrona des de codi async (i viceversa) requereix adaptar la interfície.
  • L’error “future is not Send”: probablement l’error més frustrant de Rust. Apareix quan mantens una referència que no és Send a través d’un .await.
AspecteGoRust
ModelGoroutines + channelsAsync/await + Tokio
Runtime integratNo (Tokio és extern)
Facilitat d’úsAltaMitjana-baixa
Rendiment brutExcel·lentSuperior
Overhead per tasca~4 KB (goroutine)~menor (future)
Complexitat en compartir estatMitjana (mutex, channels)Alta (Send, Sync, Arc)

Les goroutines de Go són més fàcils d’usar. L’async de Rust és més eficient. Per a la majoria de serveis backend, la facilitat guanya.


Adopció en equips: productivitat col·lectiva

Aquest punt s’ignora en la majoria de comparatives, i crec que és un error greu. Perquè en la meva experiència, és el factor decisiu en moltes empreses. No el rendiment. No el sistema de tipus. L’equip.

Incorporar un equip a Go

Un desenvolupador amb experiència en qualsevol llenguatge mainstream pot:

  • Llegir codi Go el primer dia.
  • Fer PRs funcionals la primera setmana.
  • Sentir-se còmode en 2-3 setmanes.

Go té poques formes de fer les coses. Això significa menys discussions de disseny, PRs més uniformes, i menys temps en code reviews discutint abstraccions.

Incorporar un equip a Rust

Un desenvolupador nou a Rust típicament:

  • Es baralla amb el borrow checker les primeres 2-4 setmanes.
  • Comença a ser productiu després d’1-2 mesos.
  • Es troba realment còmode després de 3-6 mesos.

Rust requereix entendre ownership, lifetimes, traits, macros, i l’ecosistema async. És molta superfície cognitiva. No és que sigui dolent — és que el ROI de l’aprenentatge tarda més a arribar. I cal ser honestos amb això a l’hora de planificar.

En un equip de 8 persones que necessita lliurar un backend en 3 mesos, la diferència entre “productius en una setmana” i “productius en dos mesos” pot definir el projecte. No és teoria — ho he vist ocórrer.


Quan triar Rust

Tria Rust quan:

  • Necessites rendiment màxim i latència predictible (trading, motors de jocs, processament de senyals).
  • Estàs escrivint programari de sistemes: bases de dades, compiladors, runtimes, drivers.
  • Treballes amb WASM i necessites codi que corri eficientment al navegador.
  • L’ús de memòria és una restricció dura (embedded, IoT, edge computing).
  • Necessites seguretat de memòria sense GC, per exemple en sistemes crítics.
  • El teu equip ja coneix Rust o té el temps per invertir en aprendre’l.

Projectes com Ripgrep, Alacritty, Deno, SurrealDB i Turbopack demostren el que Rust pot fer en mans expertes.


Quan triar Go

Tria Go quan:

  • Estàs construint serveis backend, APIs REST o gRPC.
  • Necessites CLIs i eines de línia de comandes que es distribueixin com a un binari.
  • El teu entorn és cloud-native: Kubernetes, contenidors, microserveis.
  • La velocitat d’entrega és més important que exprimir cada nanosegon.
  • El teu equip és divers i necessita un llenguatge que tothom pugui aprendre ràpid.
  • Vols binaris estàtics petits que es despleguin fàcilment.

Docker, Kubernetes, Terraform, Hugo, Prometheus, Grafana Loki, CockroachDB… la llista de projectes exitosos en Go és llarga i cobreix el nínxol d’eines cloud i infraestructura.

Si véns d’altres llenguatges i vols començar, a aprendre Go tens una guia completa.


Comparativa final

CriteriGoRust
Rendiment CPUBoExcel·lent
Rendiment I/OExcel·lentExcel·lent
Seguretat de memòriaGC (en runtime)Ownership (en compilació)
Corba d’aprenentatgeBaixaAlta
Velocitat de compilacióMolt ràpidaLenta
ConcurrènciaGoroutines (simple)Async/Tokio (potent)
Ecosistema backendMadurEn creixement
Ecosistema sistemesLimitatFort
Productivitat d’equipAltaMitjana (després del ramp-up)
Mida de binariPetit (~10 MB)Molt petit (~2-5 MB)
Cross-compilationTrivialPossible, més complex
ComunitatGran, pragmàticaApassionada, tècnica

Conclusió honesta

No et diré quin és millor perquè, i sé que sona a tòpic, depèn del que estiguis construint. Però aquesta vegada el tòpic és literal.

Si em preguntes què triaria per a un backend típic que necessita estar en producció en poques setmanes, amb un equip que ve de Java o Python, la resposta és Go. No perquè sigui superior, sinó perquè la relació entre productivitat, rendiment i simplicitat és difícil de batre per a aquell cas d’ús.

Si em preguntes què triaria per escriure un motor de base de dades, un parser d’alt rendiment o una eina que necessita exprimir cada cicle de CPU, la resposta és Rust. No hi ha res modern que s’hi acosti en aquell espai.

El que no faria — i aquí em permeto ser directe — és triar Rust per a un CRUD API “perquè és més ràpid” ni triar Go per a un sistema encastat “perquè és més fàcil”. Cada eina té el seu context, i triar bé el context és més important que triar bé l’eina. Sona obvi, però la quantitat de vegades que he vist el contrari em diu que no ho és tant.

El fanatisme per un llenguatge és perdre el temps. I crec que és una de les coses que més ens frena com a indústria. El que importa és lliurar programari que funcioni, que es mantingui i que resolgui el problema real. De vegades això és Go. De vegades és Rust. I de vegades — tot i que ens costi admetre-ho — és Python amb un parell de scripts i un cron job.

OshyTech

Enginyeria backend i de dades orientada a sistemes escalables, automatització i IA.

Navegació

Copyright 2026 OshyTech. Tots els drets reservats