SOFTWARE / TOOLCHAIN / COMPATIBILITÀ
Riscrivere da zero un componente di infrastruttura è una delle cose che gli sviluppatori amano raccontare e che gli utenti, di solito, hanno ottime ragioni per temere. Cambia il linguaggio, cambia il build system, cambia una quantità enorme di codice. Poi arriva la domanda meno spettacolare e più importante: per chi lo usa, che cosa è cambiato?
Nel caso di mold 3.0, la risposta dichiarata dal progetto è quasi provocatoria: idealmente, nulla. È la prima release del linker riscritta da C++ a Rust, ma nasce per essere un sostituto diretto della 2.42.1. Stesse opzioni da riga di comando, stesse architetture target, stesso output salvo i bug corretti e prestazioni di linking dichiarate sullo stesso livello.
Una riscrittura riuscita non si misura da quanto codice nuovo produce. Si misura da quante promesse vecchie riesce a non rompere.
Il linguaggio è la notizia più visibile, non la più importante
Il passaggio a Rust è inevitabilmente ciò che finisce nel titolo. Ha anche conseguenze tecniche concrete. Le note di rilascio spiegano che la versione C++ poteva effettuare letture fuori dai limiti con input corrotti e terminare con un segmentation fault; nella nuova implementazione quelle letture sono bounds-checked e l’accesso errato porta a un panic invece che a comportamento indefinito. È un miglioramento reale, soprattutto per uno strumento che deve elaborare file oggetto prodotti da toolchain diverse.
Ma ridurre Mold 3.0 a “hanno usato Rust quindi ora è più sicuro” sarebbe perdere la parte più interessante. Un linker non vive isolato. Sta in mezzo a compilatori, build system, script, distribuzioni, CI, pacchetti e assunzioni sedimentate negli anni. Puoi scrivere il linker più elegante del mondo e renderlo comunque inutilizzabile se costringi tutto ciò che gli sta intorno a cambiare.
Il vero contratto è la compatibilità
Il progetto dichiara esplicitamente che Mold 3.0 accetta le stesse opzioni della 2.42.1, supporta gli stessi target e produce lo stesso output, fatta eccezione per le correzioni elencate nel changelog. Non si è limitato a eseguire la propria test suite: gli sviluppatori dicono di averla eseguita su tutti i target supportati, di aver confrontato l’output su una gamma ampia di workload reali e combinazioni di opzioni e di aver compilato tutti i pacchetti Gentoo, senza trovare regressioni.
Questa è la parte che trovo più utile come lezione di ingegneria. Quando riscrivi un componente maturo, la specifica non è soltanto il documento che descrive come dovrebbe comportarsi. La specifica reale comprende anche anni di comportamento osservabile, opzioni usate da altri strumenti, casi limite e compatibilità che magari nessuno aveva mai pensato di formalizzare.
Il rewrite diventa quindi un esercizio di equivalenza, non di creatività. Prima devi dimostrare che il nuovo sistema sa fare ciò che faceva il vecchio. Solo dopo puoi permetterti di usare la nuova base per cambiare davvero qualcosa.
La velocità non era più il collo di bottiglia
Mold nasce come linker ad alte prestazioni e proprio la velocità gli ha dato visibilità. Eppure l’obiettivo dichiarato della serie 3.x non è diventare ancora più veloce: è chiudere i gap di compatibilità con GNU ld, in particolare sul supporto ai linker script, per rendere possibile un giorno l’adozione di mold come linker predefinito nelle distribuzioni Linux.
È un passaggio interessante perché mostra una dinamica che si vede spesso nei prodotti tecnici. All’inizio vinci con una caratteristica evidente: sei molto più veloce. Quando però vuoi passare da “strumento scelto dagli appassionati” a componente infrastrutturale predefinito, il criterio cambia. Non basta essere migliore nel benchmark. Devi essere compatibile con il mondo reale.
Kernel, firmware e toolchain non hanno bisogno di una demo impressionante. Hanno bisogno che opzioni strane, relocation, linker script e casi accumulati in decenni continuino a funzionare. È il genere di lavoro poco fotogenico che decide se una tecnologia resta un’alternativa o diventa infrastruttura.
Anche il build system fa parte del prodotto
La riscrittura cambia anche il modo in cui mold viene costruito. Cargo sostituisce CMake, serve Rust 1.95 o successivo e scompare la dipendenza da Intel oneTBB. La selezione dei target passa dalle vecchie opzioni CMake alle feature Cargo, mentre i test vengono eseguiti con cargo test.
Per chi installa un binario precompilato può sembrare un dettaglio. Per chi mantiene una distribuzione non lo è affatto. Dipendenze, tool richiesti, modalità di packaging e riproducibilità del build sono parte del costo operativo di un software. Se l’ambizione è diventare /usr/bin/ld, il progetto non deve convincere soltanto chi lo esegue: deve rendere ragionevole la vita di chi lo compila, lo pacchettizza, lo testa e lo aggiorna.
Riscrivere non cancella i bug: cambia il terreno su cui li cerchi
Le release note di 3.0 sono lunghe proprio perché il rewrite non viene presentato come una purificazione magica del codice. Ci sono correzioni per output non deterministico, relocazioni, linking rilocabile, simboli, version script e architetture differenti. Rust elimina intere classi di errori di memoria, ma non elimina gli errori logici, le incompatibilità o una semantica sbagliata.
È una distinzione importante anche fuori da questo progetto. Cambiare linguaggio può spostare il profilo di rischio, rendere alcune proprietà più facili da garantire e altre più esplicite. Non sostituisce però test, workload reali e confronto di comportamento. La sicurezza del linguaggio e la correttezza del prodotto sono due livelli che si aiutano, non due sinonimi.
Il benchmark più interessante è quello che non cambia
Phoronix sottolinea che le prestazioni della nuova versione sono dichiarate in linea con la precedente. Per una riscrittura completa è quasi più interessante di un grafico con un altro 20% di miglioramento. Il progetto ha cambiato il linguaggio e una parte rilevante dell’infrastruttura interna senza usare il rewrite come scusa per una regressione prestazionale.
Questa idea vale anche per sistemi meno estremi di un linker. Quando sostituiamo una tecnologia interna, tendiamo a celebrare ciò che il nuovo stack ci permette di aggiungere. Dovremmo misurare con la stessa attenzione ciò che ci permette di preservare: latenza, compatibilità, output, contratti API, procedure operative. Il nuovo non parte da zero. Parte da tutto quello che il vecchio sistema aveva già promesso.
È lo stesso motivo per cui, quando si parla di performance, preferisco guardare al sistema e non alla moda del momento. In Più CSS, meno runtime: perché GitHub ha abbandonato CSS-in-JS il punto non era scegliere una tecnologia “più moderna”, ma spostare il costo nel punto giusto. Mold 3.0 racconta una storia simile da un’altra angolazione: cambiare radicalmente l’implementazione ha senso se il contratto esterno resta sotto controllo.
La parte difficile di una riscrittura è meritarsi la sostituzione
Le riscritture hanno una pessima reputazione per una ragione: è facile sottovalutare quanta conoscenza sia nascosta nel software esistente. Mold 3.0 è interessante perché non finge che il problema sparisca scegliendo un linguaggio migliore. Al contrario, accompagna il cambio di linguaggio con un obiettivo di compatibilità esplicito e con verifiche che cercano di dimostrare equivalenza sul campo.
La vera ambizione, infatti, non è poter dire che mold è scritto in Rust. È arrivare al punto in cui una distribuzione Linux possa considerarlo abbastanza compatibile, prevedibile e noioso da metterlo al posto di GNU ld senza trasformare ogni build in un esperimento.
Ed è forse il complimento migliore che si possa fare a un componente infrastrutturale: essere stato riscritto completamente e riuscire, per chi dipende da lui, a sembrare quasi lo stesso software.
Domande frequenti
Che cos'è mold?
mold è un linker ad alte prestazioni per sistemi Unix-like. Interviene nella fase finale della build, combinando file oggetto e librerie per produrre eseguibili o altri artefatti linkati.
Mold 3.0 è più veloce della versione 2.42.1?
Il progetto dichiara prestazioni di linking sostanzialmente allo stesso livello della 2.42.1. Il punto della release 3.0 è la riscrittura in Rust e il lavoro di compatibilità, non un nuovo salto prestazionale.
Perché mold è stato riscritto da C++ a Rust?
Tra le motivazioni dichiarate c’è una gestione più sicura degli input corrotti grazie ai controlli sui limiti della memoria. La riscrittura cambia anche il sistema di build, che passa da CMake a Cargo.
Mold 3.0 può sostituire direttamente la 2.42.1?
È questo l’obiettivo dichiarato dal progetto: stesse opzioni, stessi target e stesso output salvo le correzioni di bug. Come per ogni componente di toolchain, resta prudente validarlo sui propri workload e test.
