# tsrs vs tsgo: Cloudflare dice 8×, ma il benchmark arriva a 16,87×. Perché?

> Canonical: https://vxi.me/it/posts/tsrs-vs-tsgo-typescript-rust-compiler/
> Locale: it-it
> Published: 2026-10-22
> Updated: 2026-10-22
> Markdown: https://vxi.me/it/posts/tsrs-vs-tsgo-typescript-rust-compiler.md

<figure class="tsrs-parity-hero not-prose">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/tsrs-vs-tsgo-typescript-rust-compiler/tsrs-diagnostic-parity.mobile.still.it-it.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/tsrs-vs-tsgo-typescript-rust-compiler/tsrs-diagnostic-parity.still.it-it.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/tsrs-vs-tsgo-typescript-rust-compiler/tsrs-diagnostic-parity.mobile.it-it.svg" type="image/svg+xml"><img src="/images/posts/tsrs-vs-tsgo-typescript-rust-compiler/tsrs-diagnostic-parity.it-it.svg" alt="tsgo e tsrs eseguono quattro casi sintetici TypeScript con la stessa revisione sorgente; diagnosi e codici di uscita coincidono in tutti i casi." width="1200" height="700" loading="eager" fetchpriority="high" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption>Figura: confronto diagnostico su quattro casi sintetici a file singolo. Tutti i risultati coincidono; non è un benchmark di compatibilità o prestazioni di un progetto.</figcaption>
</figure>

Cloudflare ha introdotto tsrs parlando di una velocità fino a otto volte superiore a TypeScript 7.0 e di circa il 70% di memoria in meno. Eppure, aprendo il vecchio benchmark fissato a un commit di tsrs, ho trovato 16,87× per mui-docs e 13,55× per vscode con le impostazioni predefinite. Mi sono chiesto come si conciliassero con gli 8× dell'annuncio.

Non ho rieseguito il benchmark prestazionale su grandi progetti. Ho fatto una prova più piccola: ho fissato versioni corrispondenti di tsrs e tsgo (l'implementazione Go di TypeScript), eseguendole in un ambiente GitHub temporaneo e isolato su quattro semplici esempi TypeScript. Tutti e quattro i diagnostici coincidevano. È un buon punto di partenza, non una ragione per sostituire subito il type checker nella CI di produzione.

## Perché conta il numero di checker nel benchmark

L'[annuncio di Cloudflare](https://blog.cloudflare.com/typescript-rust-port/) riassume i primi risultati come fino a 8× più veloce e circa il 70% di memoria in meno. Il [report storico di tsrs](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/bench/RESULTS.md) fissato a un commit mostra invece questi valori:

| Benchmark storico | tsgo | tsrs | checker | Fattore riportato |
| --- | ---: | ---: | --- | ---: |
| mui-docs, predefinito | 12.66 s | 0.75 s | 4 vs 16 | 16.87× |
| mui-docs, entrambi `--checkers 4` | 12.69 s | 1.07 s | 4 vs 4 | 11.81× |
| mui-docs, entrambi `--checkers 16` | 12.36 s | 0.77 s | 16 vs 16 | 16.07× |
| vscode, predefinito | 11.10 s | 0.82 s | 4 vs 16 | 13.55× |
| vscode, entrambi `--checkers 16` | 6.73 s | 0.81 s | 16 vs 16 | 8.32× |

Le righe predefinite non usano neanche lo stesso numero di checker: quattro per tsgo e sedici per tsrs. Anche quando entrambi ne usano quattro o sedici, però, i fattori superano 8×. Bisogna distinguere due affermazioni: l'annuncio sintetizza risultati iniziali; `RESULTS.md` è un'altra fotografia storica con versioni e condizioni precise. Otto non è il massimo della tabella e sedici non è una promessa per ogni progetto.

Non voglio inventare una causa guardando solo i numeri. Il report documenta una macchina Depot CI `depot-ubuntu-24.04-16` (16 vCPU, 63 GB RAM, Linux x86_64), misure alternate e mediane per durata e picco di memoria. tsgo usa come base `typescript@7.0.2`, mentre tsrs porta un commit TypeScript 7.1-dev più recente ed è una build release ottimizzata con PGO. Lo stesso numero di checker non equivale alle stesse revisioni o modalità di compilazione.

Anche la memoria cambia in base a progetto e configurazione: per mui-docs il report indica 4,61× meno memoria con i valori predefiniti e 8,23× meno con sedici checker per entrambi. Il «70% in meno» è un riepilogo dei primi test, non una garanzia generale. Ho verificato i dati nella fonte upstream, senza ripetere le misure sui progetti grandi.

## Cosa hanno davvero verificato i quattro casi

Ho confrontato `tsrs@0.18.0-ts7.1.0-dev.20260929` con l'implementazione Go distribuita in `typescript@7.1.0-dev.20260930.4`. Entrambi corrispondono allo stesso [commit sorgente b85298b6](https://github.com/microsoft/TypeScript/commit/b85298b6a81f772d080b0455de0ca9d744cd6fd6).

Nel preparare le dipendenze ho commesso un errore concreto: ho scambiato `@typescript/typescript`, il nome di workspace del repository sorgente, per un pacchetto pubblico npm. npm ha restituito E404 e il compilatore non era ancora partito. Il pacchetto root distribuito è `typescript`. In questa nightly la CLI si chiama ancora `tsc`, e `@typescript/typescript-linux-x64` fornisce il compilatore Go nativo per Linux x64. Corretto il nome, sono arrivato all'esecuzione effettiva.

I quattro esempi condividono un unico `tsconfig.json`. Ho impostato `types: []` per evitare l'inclusione automatica di pacchetti globali `@types` dall'ambiente, e `skipLibCheck: false` per mantenere il controllo dei file di dichiarazione. Ecco la configurazione realmente usata, invece di dire soltanto «strict mode»:

~~~json
{
  "compilerOptions": {
    "strict": true,
    "target": "es2022",
    "module": "esnext",
    "noEmit": true,
    "skipLibCheck": false,
    "types": []
  },
  "include": ["src/*.ts"]
}
~~~

In ogni directory c'è un solo file `src/test.ts`:

| Caso | `src/test.ts` completo | Risultato tsgo / tsrs |
| --- | --- | --- |
| `valid` (Tipi corretti) | `export const answer: number = 42;` | 0 diagnostici, uscite 0 / 0 |
| `type-error` (Assegnazione errata) | `export const answer: number = "wrong";` | TS2322 · uscite 2 / 2 |
| `module-missing` (Modulo inesistente) | `import {missing} from "./unavailable.js"; export const answer = missing;` | TS2307 · uscite 2 / 2 |
| `generic-constraint` (Vincolo generico) | `type OnlyText<T extends string> = {value:T}; export type Bad = OnlyText<number>;` | TS2344 · uscite 2 / 2 |

Ho usato `--noEmit --pretty false --checkers 4` con entrambi i compilatori, confrontando percorso, riga e colonna, codice e messaggio, ordine dei diagnostici e codice d'uscita. Tutti e quattro i casi erano identici. Ma un solo file sorgente per caso non mette realmente alla prova quattro checker in parallelo; qui l'opzione serve soprattutto a fissare una condizione.

## Lo stesso numero di errori non significa lo stesso diagnostico

La [tabella di compatibilità](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/docs/COMPATIBILITY.md) di questa versione indica 13.458 baseline degli errori coincidenti su 13.462 e tutte le 12.779 baseline di `.types` e `.symbols`. Tuttavia il totale può trarre in inganno.

Nel report, `drizzle-orm` è un controesempio: con sedici checker su entrambi i lati si ottengono 10.846 errori per ciascuno, ma le posizioni diagnostiche differiscono. Il report lo segnala come bug di tsrs. Non ho riprodotto quel caso in prima persona: è un dato della fonte fissata. In CI e nella code review serve sapere se l'errore indica il file e la riga corretti, non soltanto se i conteggi coincidono.

Ci sono poi limiti pratici: questa versione non supporta ancora `--init` o `--showConfig`, e `--locale` non traduce i messaggi. I content mapper per `.astro`, `.vue` e `.svelte` funzionano in CLI/build ma non nel language server. I pacchetti precompilati non coprono Windows o macOS Intel. Ho testato solo la CLI su Linux in un ambiente GitHub isolato, non quelle piattaforme, i plugin dell'editor o LSP. Se usi `astro check` o i diagnostici in tempo reale, leggi prima la [tabella di compatibilità per la tua versione](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/docs/COMPATIBILITY.md). Ho anche una [nota su Astro e TypeScript 7](/posts/astro-check-typescript-7-compatibility/) per chi sta gestendo un aggiornamento.

## Come li confronterei in un mio progetto

Inizierei da un worktree usa e getta o da un branch separato, senza toccare direttamente la directory di lavoro quotidiana. Se la prova non serve, posso scartarla.

Il seguente script evita un errore facile: due programmi possono terminare con lo stesso codice ma produrre diagnostici differenti. `set +e` è intenzionale. Alcuni esempi contengono errori e devono uscire con un codice non zero; voglio far terminare entrambi i compilatori, registrare gli stati e confrontare solo dopo gli output. Qui `tsc` è il comando Go della nightly fissata e `/tmp/tsgo.out` è semplicemente il nome del log.

~~~bash
pnpm add -D --save-exact tsrs@0.18.0-ts7.1.0-dev.20260929 typescript@7.1.0-dev.20260930.4

set +e
pnpm exec tsc -p . --noEmit --pretty false --checkers 4 > /tmp/tsgo.out 2>&1
tsgo_status=$?
pnpm exec tsrs -p . --noEmit --pretty false --checkers 4 > /tmp/tsrs.out 2>&1
tsrs_status=$?
diff -u /tmp/tsgo.out /tmp/tsrs.out
diff_status=$?
set -e
printf 'tsgo_exit=%s tsrs_exit=%s diff_exit=%s\n' "$tsgo_status" "$tsrs_status" "$diff_status"
if [ "$tsgo_status" -ne "$tsrs_status" ] || [ "$diff_status" -ne 0 ]; then
  echo 'DIAGNOSTIC_DIFF_OR_EXIT_MISMATCH: inspect /tmp/tsgo.out and /tmp/tsrs.out'
  exit 1
fi
~~~

Se il primo confronto dà risultati diversi, salvo prima gli output originali. Poi provo separatamente `--checkerAssignment go` (assegnazione dei file e cronologia checker come tsgo) oppure `--noLazyMembers` (disattivazione dell'ottimizzazione lazy-member di tsrs). Abilitarli fin dall'inizio potrebbe nascondere le differenze del comportamento predefinito. Le opzioni sono spiegate nella [guida](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/docs/USAGE.md) e nella [documentazione sulle impostazioni](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/docs/DEFAULTS.md).

Questo script confronta i diagnostici, non misura tempo o memoria. Stavolta non ho completato una campagna di misurazione delle prestazioni. Per annotare un primo valore su Linux si può usare GNU `time` per durata e picco RSS; su macOS installa `gnu-time` e sostituisci la prima riga con `TIME_BIN=gtime`.

~~~bash
TIME_BIN=/usr/bin/time

set +e
"$TIME_BIN" -f 'elapsed_s=%e max_rss=%M' -o /tmp/tsgo-bench.metrics \
  pnpm exec tsc -p . --noEmit --pretty false --checkers 4 \
  > /tmp/tsgo-bench.out 2>&1
tsgo_status=$?
"$TIME_BIN" -f 'elapsed_s=%e max_rss=%M' -o /tmp/tsrs-bench.metrics \
  pnpm exec tsrs -p . --noEmit --pretty false --checkers 4 \
  > /tmp/tsrs-bench.out 2>&1
tsrs_status=$?
set -e

printf 'tsgo_exit=%s ' "$tsgo_status"; cat /tmp/tsgo-bench.metrics
printf 'tsrs_exit=%s ' "$tsrs_status"; cat /tmp/tsrs-bench.metrics
~~~

La coppia di risultati ricavata da quei comandi non basta per un benchmark: il carico in background può alterarla. Su uno stesso runner farei almeno cinque coppie alternando l'ordine, con lockfile, versioni, `tsconfig` e checker fissi, e confronterei le mediane e i massimi RSS. Separerei avvio a freddo e cache calda. Non ho svuotato la page cache del sistema operativo, quindi non lo chiamerei un test con cache di sistema completamente fredda. GNU `time` mostra di solito `%M` in KiB: verifica le unità prima di confrontare sistemi diversi.

All'inizio aggiungerei tsrs alla CI come lavoro supplementare che non blocca le modifiche, conservando `tsc` e i controlli del framework come verifiche obbligatorie. Solo dopo avere chiarito le divergenze, ottenuto misure stabili, attraversato un vero ciclo PR/rilascio e verificato il rollback valuterei la sostituzione. Questi quattro piccoli esempi dimostrano soltanto che, con quegli input, i due compilatori hanno emesso gli stessi diagnostici.

Data del test: 22 ottobre 2026
