Eierskap, eksplisitt tilstand — og hvorfor det å lære Rust kan gjøre deg til en bedre JavaScript-arkitekt, selv om ingen .rs-fil noen gang havner i produksjon.

Det finnes en åpenbar grunn til at en JavaScript-utvikler bør lære Rust: noen ganger er Rust rett og slett det bedre språket.

Det svaret er riktig. Det er også lite interessant.

Den viktigere grunnen er at Rust endrer hvordan du tenker om programmer skrevet i andre språk. Etter nok tid med Rust begynner en JavaScript-kodebase å se litt annerledes ut. Objekter får en historie. Referanser får konsekvenser. En funksjon som returnerer undefined, begynner å virke mistenkelig. Et Promise er ikke lenger synonymt med samtidighet. En global cache virker mindre selvfølgelig praktisk. En uskyldig closure har plutselig en levetid. Og et API som kan gi fem logisk forskjellige utfall, men representerer fire av dem ved å kaste exceptions, begynner å føles strukturelt uærlig.

Ingenting av dette betyr at JavaScript bør etterligne Rust syntaktisk. Det bør det ikke.

JavaScript har en garbage collector. Det kjøres dynamisk. TypeScript bruker strukturell typing. Node er bygget rundt en event loop og asynkron I/O. Hono bruker bevisst Web-API-er som Request, Response, Headers og fetch. Nuxt legger Vue, server-rendering og Nitro rundt en bemerkelsesverdig portabel JavaScript-serverarkitektur. Disse systemene løser andre problemer enn Rust.

De konseptuelle lærdommene lar seg likevel overføre. I JavaScript-arkitekturen som vokser fram i 2026, overføres de kanskje bedre enn de gjorde for fem år siden.

Hono beskriver seg selv som et rammeverk bygget på Web Standards framfor Node-spesifikke abstraksjoner, og kjører på Node, Bun, Deno, Cloudflare Workers, Fastly, Lambda og andre miljøer. Nuxt 4 bygger tilsvarende serveren sin gjennom Nitro til et system som kan rulles ut mot Node, serverless og edge. Runtime er i ferd med å bli en implementasjonsdetalj framfor arkitekturens tyngdepunkt.

Det endrer hva god JavaScript-arkitektur er.

Og merkelig nok gjør det Rust stadig mer relevant.

Rust er kjent for eierskap og lån.

Den vanlige forklaringen er teknisk: Rust holder styr på hvilken del av programmet som eier en verdi, kontrollerer hvordan referanser til verdien kan eksistere, og bruker disse reglene til å gi minnesikkerhet uten en garbage collector.

For en som skriver TypeScript, kan det først virke irrelevant. V8 håndterer allerede minne. Vi kaller ikke free(). Vi resonnerer sjelden om stack kontra heap. Vi sender objekter mellom funksjoner uten å forhandle om levetider med kompilatoren.

Denne tolkningen bommer på den dypere idéen bak eierskap.

Eierskap spør:

Hvem har ansvaret for denne tingen nå?

Det spørsmålet gjelder nesten alle objekter av betydning i en webapplikasjon.

Hvem eier:

  • en databasetransaksjon?
  • et autentiseringsobjekt knyttet til requesten?
  • en åpen strøm?
  • en cache-oppføring?
  • et muterbart konfigurasjonsobjekt?
  • en AbortController?
  • en WebSocket?
  • et stort parset dokument?
  • en midlertidig fil?
  • et abonnement?
  • en connection fra en pool?
  • et reaktivt Vue-objekt?
  • resultatet av et HTTP-kall?

JavaScripts garbage collector løser én smal versjon av dette problemet:

Er dette minnet fortsatt nåbart?
Den svarer ikke på:
Bør denne ressursen fortsatt eksistere?
V8s collector er svært avansert. Moderne V8 bruker generasjonsbasert innsamling, samtidige og parallelle teknikker og omfattende runtime-optimalisering. Disse mekanismene handler om nåbarhet i minnet og ytelse, ikke om applikasjonssemantikk.

Et lekket JavaScript-objekt er derfor sjelden et objekt garbage collectoren ikke forsto. Ofte forstår den det utmerket. Programmet ditt refererer rett og slett fortsatt til noe det burde ha sluttet å referere til.

Dette skillet er dyptgripende.

Rust gjør levetidsproblemer synlige fordi kompilatoren tvinger dem inn i språket. JavaScript lar dem forbli arkitektoniske.

Å lære Rust trener deg til å se dem uansett.

Tenk på en helt vanlig Node-applikasjon.

En request kommer inn. Middleware etablerer autentisering. Et annet lag legger på en correlation-ID. Et repository henter data fra databasen. Flere tjenester kaller eksterne API-er. Noe data havner i en cache. Loggkontekst propagerer gjennom asynkrone kall. Kanskje åpnes en strøm. Kanskje kobles en event listener på.

Requesten fullføres.

Hva tilhører prosessen?

Hva tilhørte bare requesten?

Hva kan overleve?

Hva må forsvinne?

Hva kan trygt deles med neste request?

JavaScript tvinger deg ikke til å svare på disse spørsmålene. Rust gjør det. Det gjør Rust-programmereren uvanlig mistenksom overfor tilfeldig delt tilstand.

Den mistenksomheten er sunn i Node.

Node tilbyr AsyncLocalStorage nettopp for å propagere tilstand gjennom asynkrone operasjoner i en asynkron levetid, som en webrequest. Node-dokumentasjonen beskriver det som analogt til thread-local storage i andre miljøer.

At et slikt API i det hele tatt finnes, illustrerer problemet presist. Request-tilstand har en levetid. Den bør ikke ved et uhell bli prosesstilstand.

En nyttig Rust-informert regel for JavaScript blir dermed:

La levetiden til tilstanden svare til levetiden til det den beskriver.

Konfigurasjon kan være prosess-scopet. Autentisering er normalt request-scopet. En databasetransaksjon hører til én operasjon. Tilstanden i et skjema hører til én interaksjon. En cache-oppføring kan overleve mange requester, men bør likevel ha en eksplisitt eviction-policy.

Dette høres banalt ut helt til man begynner å inspisere reelle JavaScript-systemer.

Da blir det arkitektur.

Det kanskje mest nyttige Rust-begrepet for TypeScript-programmerere er enumen.

Rust-enumer lar en verdi være nøyaktig ett medlem av et definert sett alternativer. Option representerer nærvær eller fravær. Result representerer suksess eller gjenopprettelig feil. Pattern matching tvinger deretter programmereren til å forholde seg eksplisitt til disse tilstandene.

JavaScript har tradisjonelt representert slike situasjoner ved konvensjon.

En funksjon kan returnere:

User | null

med mindre den kaster. Bortsett fra at den noen ganger returnerer undefined. Og autentiseringsfeil kaster en UnauthorizedError. Med mindre brukeren ikke finnes, og da returneres null. Og databasefeil kaster noe databasedriveren har funnet på.

Funksjonen har dermed en reell returtype omtrent slik:

User
| NotFound
| Unauthorized
| DatabaseFailure
| ProgrammingError

men bare den første tilstanden finnes i den deklarerte modellen.

Rust lærer bort en bedre vane:

Modellér tilstandene som faktisk finnes.

TypeScript er overraskende god til dette.

type FindUserResult =
  | { kind: 'found'; user: User }
  | { kind: 'not-found' }
  | { kind: 'forbidden' }
  | { kind: 'unavailable'; reason: string }

Nå ser kalleren det faktiske domenet.

function respond(result: FindUserResult): Response {
  switch (result.kind) {
    case 'found':
      return Response.json(result.user)

case 'not-found': return new Response(null, { status: 404 })

case 'forbidden': return new Response(null, { status: 403 })

case 'unavailable': return new Response(result.reason, { status: 503 })

default: return assertNever(result) } }

function assertNever(value: never): never { throw new Error(Uventet tilstand: ${JSON.stringify(value)}) }

Dette er ikke å late som TypeScript er Rust. Det utnytter en innebygd styrke i TypeScript: diskriminerte unioner. TypeScript kan snevre inn slike unioner og bruke never til uttømmende sjekk.

Forbedringen er arkitektonisk. Uønskede kombinasjoner blir vanskelige å representere.

I stedet for:

{
  loading: boolean
  error?: Error
  data?: User
}

der loading, error og data i teorien kan opptre samtidig i meningsløse kombinasjoner, skriv:

type UserState =
  | { state: 'loading' }
  | { state: 'ready'; user: User }
  | { state: 'failed'; error: Error }

Dette gjelder like godt inne i Vue-komponenter, Pinia-stores, Hono-tjenester og Nuxt-serverendepunkter.

Rust har lært oss noe viktig:

Boolske felter er ofte dårlige erstatninger for tilstander.

JavaScript-utviklere deler ofte feil i to kategorier: noe som returneres, og noe som kastes.

Det skillet er for primitivt.

Rust skiller eksplisitt mellom gjenopprettelige feil representert gjennom Result og uopprettelige situasjoner representert gjennom panic. JavaScript kan ikke kopiere mekanismen direkte, og bør ikke forsøke. Men skillet bør kopieres.

Se på:

const user = await repository.getUser(id)

Flere ting kan skje. Brukeren finnes. Brukeren finnes ikke. Kalleren mangler tilgang. Databasen er midlertidig utilgjengelig. Repositoryet støter på korrupte data. Programmereren sendte inn en umulig verdi på grunn av en bug.

Dette er ikke likeverdige feil.

En manglende bruker kan være helt ordinær domeneatferd. En databasetimeout er en infrastrukturfeil. Ugyldig intern tilstand er sannsynligvis en programmeringsfeil.

Når alle tre blir throw new Error(), mister arkitekturen informasjon.

Rust oppmuntrer til en nyttig designdisiplin:

Forventet feil hører hjemme i typesystemet. Uventet feil hører hjemme i feilmekanismen.

Det krever ikke et moteriktig Result-bibliotek i ethvert TypeScript-prosjekt. Noen ganger holder en diskriminert union. Noen ganger er HTTP-status den riktige eksterne representasjonen. Noen ganger er det helt på sin plass å kaste. Det viktige er å ta valget bevisst.

Poenget er å slutte å bruke exceptions som en udokumentert andre returkanal.

Node presenterer en pussig illusjon.

Fordi JavaScript-kode bruker async og await, er det lett å begynne å tenke på systemet som massivt samtidig. Det er det — men bare i en bestemt forstand.

Nodes arkitektur hviler tungt på en event loop og et relativt lite antall worker-tråder. Node-prosjektet advarer eksplisitt serverutviklere om at kostbare callbacks kan blokkere event loopen og straffe andre klienter. Anbefalingen handler i bunn og grunn om beregningsmessig rettferdighet: arbeidet som utføres for én enkelt klient på et gitt tidspunkt, bør være lite.

Dette er nøyaktig den typen problem Rust gjør vanskelig å ignorere. Rusts samtidighetsmodell skiller på om verdier trygt kan flyttes mellom eller deles på tvers av tråder, gjennom begreper som Send og Sync.

JavaScript har ingen tilsvarende kontrakt på kompileringstidspunktet. De arkitektoniske spørsmålene består:

Kan dette arbeidet utføres samtidig?

Bør det det?

Hvilken tilstand deler det?

Hva skjer ved kansellering?

Hvor mye CPU kan én request bruke?

Hva skjer når 2 000 requester gjør denne operasjonen samtidig?

Hva utøver backpressure?

Hvem eier arbeidet etter at HTTP-klienten er borte?

Disse spørsmålene betyr langt mer enn om koden inneholder Promise.all().

Anta at en handler utfører 200 ms synkront CPU-arbeid. Å skrive:

async function calculate() {
  return expensiveCalculation()
}

gjør ikke beregningen asynkron i den forstanden som betyr noe. JavaScript-koden bruker fortsatt CPU på tråden som kjører.

Node anbefaler spesifikt worker threads for CPU-intensivt JavaScript-arbeid, og påpeker at de gir lite gevinst for vanlige I/O-tunge arbeidslaster, der Nodes eksisterende asynkrone I/O-mekanismer stort sett er bedre.

Det fører til en av de sterkeste Rust-lærdommene for Node-utviklere:

Samtidighet er et ressursstyringsproblem, ikke et async-syntaksproblem.

Når du først har forstått det, endrer applikasjonsdesignet seg. Køer får grenser. Parallellitet blir begrenset. Kansellering blir en del av API-designet. Timeouts propageres. Store CPU-jobber får sin egen kjøringsgrense. Backpressure slutter å være en implementasjonsdetalj.

Hono er særlig interessant her.

Betydningen ligger ikke i at routeren er rask, selv om ytelse var en del av den opprinnelige appellen. Den viktigere arkitektoniske avgjørelsen er at rammeverket setter selve webplattformen i sentrum.

Hono opererer med standardabstraksjoner som Request, Response, Headers, URL og fetch, og muliggjør dermed den samme applikasjonsmodellen på tvers av flere JavaScript-runtimes.

Det oppmuntrer til en form for arkitektonisk nøkternhet. HTTP-laget kan bli svært tynt:

Request
   ↓
parse / valider
   ↓
domeneoperasjon
   ↓
eksplisitt resultat
   ↓
Response

Den formen ligner gode Rust-tjenester — ikke fordi Rust bruker de samme API-ene, men fordi arkitekturen har tydelige grenser.

Requesten eier request-spesifikk kontekst. Input valideres på grensen. Domenekoden mottar gyldige verdier framfor vilkårlige transportobjekter. Infrastruktur injiseres gjennom eksplisitte avhengigheter. Domeneresultater går tilbake til transportlaget. Transporten gjør dem om til HTTP.

Dette er mye bedre enn å la Honos Context-objekt vandre gjennom tjue lag med applikasjonskode.

Et kontekstobjekt er praktisk. Et kontekstobjekt som sendes overalt, ender til slutt opp som en service locator. Da kan enhver funksjon i prinsippet nå HTTP-headere, autentisering, runtime-miljø, bindings, cookies, responstilstand, logging og request-metadata. Avhengighetene blir usynlige.

Rust ville gjort slik uhemmet implisitt tilgang vanskelig å bygge naturlig. Kulturen der oppmuntrer i stedet til smale funksjonsinnganger og eksplisitt eierskap.

Hono nyter godt av den samme disiplinen.

Bruk rammeverkskonteksten på kanten. Hent ut det applikasjonen trenger. Legg så HTTP bak deg.

Det ligger en større arkitektonisk endring bak Hono.

Node implementerer nå selv mange webplattform-API-er. fetch() er browserkompatibel, og Web Streams gir implementasjoner av WHATWG-strømmemodellen ved siden av Nodes historiske strømabstraksjoner.

Hono tar dette betydelig lenger ved å gjøre standardene til selve fundamentet i rammeverksmodellen.

Konsekvensen er subtil. Gjennom store deler av Nodes historie skrev utviklere i praksis:

en Node-applikasjon.
I økende grad er det mulig å skrive:
en webserverapplikasjon som for tiden kjører på Node.
Den forskjellen betyr noe. Det andre systemet har en portabel kjerne og en runtime-adapter. Konseptuelt ligger dette nær måten et godt Rust-program skiller domenelogikk fra OS, nettverk eller rammeverksinfrastruktur.

Den dypere lærdommen er derfor ikke «bruk færre Node-API-er». Den er:

Skill mellom kapabiliteter og implementasjoner.

Applikasjonen din trenger:

KeyValueStore

ikke nødvendigvis Redis.

Den trenger:

Clock

ikke Date.now() spredt utover tretti filer.

Den trenger:

UserRepository

ikke Prisma eksponert gjennom hele domenet.

Den trenger:

HttpClient

eller kanskje rett og slett standard fetch.

Den trenger en kilde til konfigurasjon, ikke direkte process.env-tilgang fra vilkårlige moduler.

Denne arkitekturen gjør testing enklere. Viktigere er det at den gjør utskifting mulig. Det blir stadig mer verdifullt når den samme Hono-applikasjonen kan kjøre på Node i dag, Cloudflare Workers i morgen og Bun i en tredje utrulling. Hono tilbyr til og med adaptere for runtime-forskjeller, som miljøvariabler på tvers av plattformer.

Rust-programmerere ville kjent igjen filosofien umiddelbart: hold kjernemodellen stabil, og skyv plattformspesifikk atferd ut mot grensen.

Nuxt kompliserer bildet. Det er ikke bare et serverrammeverk. Det kombinerer klientapplikasjon, Vue-reaktivitet, server-rendering, ruting, byggeverktøy og serverinfrastruktur.

Moderne Nuxt bruker Nitro som servermotor, og Nuxt 4 kan sikte mot tradisjonelle Node-servere så vel som serverless- og edge-miljøer.

Dette skillet har nå også operasjonell betydning: Nuxt 4 er gjeldende hovedversjon, og oppgraderingsguiden der er utgangspunktet for nytt arbeid, framfor å behandle Nuxt 3 som plattformen som gjelder.

Rust-tenkning blir nyttig i Nuxt på fire steder spesielt.

Eierskap til requesten

Serverkode må skille applikasjonsglobal tilstand fra request-tilstand.

En singleton opprettet under prosessinitialisering kan leve langt lenger enn requesten som først rørte den. Alt som inneholder autentisering, språk, tenant, transaksjon eller brukerspesifikke data fortjener eksplisitt resonnement om levetid.

Reaktivt eierskap

Vue gjør det svært praktisk å dele reaktive referanser. Bekvemmelighet kan skjule eierskap.

Hvem har lov til å mutere dette objektet? Er det kanonisk servertilstand? En klientside-kopi? En cachet verdi? Skjematilstand? Optimistisk tilstand? Avledet tilstand?

En av de enkleste måtene å skape vanskelige Vue-systemer på, er å la det samme muterbare objektet fylle flere konseptuelle roller.

Rusts instinkt ville vært å skille dem. En DTO fra serveren kan være immutable applikasjonsdata. Et skjema kan eie en muterbar arbeidskopi. En computed-verdi kan avledes. En lagringsoperasjon skaper en ny serverrepresentasjon.

Implementasjonen er JavaScript. Modellen er eierskap.

Grensen mellom server og klient

En Nuxt-applikasjon krysser en uvanlig viktig grense under SSR og hydrering. Data beveger seg fra database og tjenester inn i server-rendering, blir serialisert, går gjennom HTML eller payloads, og blir klienttilstand.

Rust lærer utviklere å ta slike overganger på alvor.

Behandle serialiseringsgrensen nesten som en protokoll. Ikke send interne databaseentiteter til nettleseren uten videre. Lag transportmodeller med hensikt. Valider ekstern input. Gjør server-bare kapabiliteter umulige å nå fra delte klientmoduler.

Runtime-portabilitet

Nitros utrullingsmodell skaper det samme arkitektoniske presset som Hono.

Kode som forutsetter lokal filsystemtilgang, langlevde prosesser, Node-spesifikke globaler eller ubegrensede TCP-forbindelser, kan fungere perfekt under én Nitro-preset og være konseptuelt feil under en annen.

En Rust-påvirket arkitekt spør om kapabiliteter før API-er. Det er nøyaktig riktig spørsmål.

Rust belønner datadrevet tenkning sterkt. En struct representerer normalt noe. En enum representerer normalt én av flere mulige ting.

JavaScript-objekter er langt mer tillatende. Det samme objektet kan gradvis samle på seg egenskaper:

user.authorized = true
user.permissions = permissions
user.profile = profile
user.loaded = true

Når det når den femte funksjonen, vet ingen helt hva en user er lenger.

Mønsteret er vanlig fordi JavaScript gjør inkrementell mutasjon lett. Rust oppmuntrer til det motsatte instinktet: konstruer verdier som representerer meningsfulle tilstander, og send dem videre.

Dette er verdifullt langt utover typesikkerhet. Det reduserer temporal kobling.

Se forskjellen mellom:

opprett bruker
→ berik bruker
→ autoriser bruker
→ dekorer bruker
→ muter bruker igjen

og:

Credentials
    ↓
AuthenticatedIdentity
    ↓
AuthorizedIdentity
    ↓
UserView

Det andre designet gjør overgangene synlige. Det gir gjerne litt flere typer og dramatisk mindre tvetydighet.

For store TypeScript-systemer er dette kanskje den lærdommen fra Rust som gir høyest avkastning.

JavaScript-utviklere trenger ingen borrow checker. De trenger ofte å stille borrow checker-spørsmål.

Anta at en funksjon mottar et objekt. Gjør den følgende:

  • leser det?
  • beholder det?
  • muterer det?
  • overfører det et sted?
  • kobler callbacks på det?
  • legger det i en cache?
  • eksponerer det for en annen request?
  • lukker noe som ligger inni det?

En TypeScript-signatur forteller deg som regel ikke.

process(config: Config): Promise<Result>

kan bety nesten hva som helst.

Rust gjør mutasjon og eierskapsoverføring mye vanskeligere å skjule. JavaScript-arkitektur bør kompensere ved å gjøre intensjoner synlige.

Bruk readonly der det passer. Foretrekk funksjoner uten skjult mutasjon. Hold muterbare strukturer lokalt eid. Unngå å sende store muterbare sekker med applikasjonstilstand gjennom hvert lag. Kopiér når isolasjon er verdifullt. Del når delingen er tilsiktet. Og dokumentér funksjoner hvis semantikk omfatter lagring eller mutasjon.

Det er ingen dyd i å rekonstruere Rusts eierskapssystem for hånd inne i TypeScript. Det er stor dyd i å adoptere mistenksomheten mot usynlige sideeffekter.

Det finnes en annen felle.

En utvikler lærer Rust, blir fascinert av allokering, kopiering og minnelayout, og begynner å behandle JavaScript som dårlig implementert Rust.

Det er en feil.

JavaScript-motorer er designet rundt JavaScript-arbeidslaster. V8s garbage collector utnytter eksplisitt den generasjonsbaserte observasjonen at mange nyallokerte objekter dør raskt. Å allokere et kortlevd JavaScript-objekt er derfor ikke automatisk en kostbar arkitektonisk feil.

V8 gjør dessuten omfattende representasjons- og optimaliseringsarbeid internt. Arbeidet med pointer compression alene viser hvor langt vanlig JavaScript-kildekode ligger fra en naiv mental modell der «hvert objekt er et kostbart heap-objekt».

Rust-kunnskap bør derfor forbedre JavaScript-arkitektur framfor å produsere overtro.

Ikke unngå å opprette objekter bare fordi Rust lærte deg at allokeringer koster. Ikke muter delte strukturer bare for å unngå kopiering. Ikke erstatt lesbar TypeScript med typed arrays med mindre målinger forsvarer det. Ikke bygg forseggjorte Result>>-abstraksjoner bare fordi algebraiske typer er vakre. Ikke gjør ethvert grensesnitt generisk. Og ikke optimalisér kode hvis begrensende faktor er et nettverkskall som tar 80 millisekunder.

Lærdommen fra Rust som er verdt å importere, er ikke:

allokeringer er ille.
Den er:
ressurser har kostnader; forstå kostnadene som faktisk betyr noe.
Det er en langt kraftigere idé.

En av de mest underutviklede idéene i vanlig applikasjons-JavaScript er backpressure.

Tenk deg at et API mottar 10 000 jobber. Den naive JavaScript-løsningen er forlokkende:

await Promise.all(jobs.map(processJob))

Den er også ofte feil. Du kan nå ha 10 000 databaseoperasjoner, 10 000 utgående HTTP-kall, 10 000 store objekter, 10 000 ventende promises — eller en uheldig kombinasjon av alle fire.

Promise.all beskriver aggregering. Den gir ingen kapasitetsstyring.

En systemspråk-mentalitet stiller et annet spørsmål:

Hvor mange av disse operasjonene har lov til å eksistere samtidig?

Kanskje er det riktige svaret 8. Eller 50. Eller én per databaseforbindelse. Eller én per CPU. Eller så mange som rate-grensen til et eksternt API tillater.

Dette er Rust-tenkning på sitt mest nyttige — ikke fordi Rust automatisk løser backpressure i applikasjoner, for det gjør det ikke, men fordi systemprogrammering skaper et instinkt om at ressurser er endelige.

JavaScript-abstraksjoner får dem noen ganger til å framstå uendelige. Det er de ikke.

Det finnes enda en idé som blir tydeligere gjennom denne linsen.

Anta at en nettleser ber om en kostbar rapport. Brukeren navigerer bort etter 300 millisekunder.

Serveren fortsetter: databasespørring, eksternt kall, beregning, rendering, logging, kanskje enda et HTTP-kall.

Hvem eier den beregningen nå?

Ingen vil ha resultatet. Likevel fortsetter arbeidet.

Dette er et levetidsproblem.

Moderne JavaScript tilbyr kanselleringsmekanismer som AbortController og AbortSignal; Node eksponerer disse webkompatible primitivene for Promise-orienterte API-er.

Den arkitektoniske lærdommen er å propagere kansellering gjennom lagene framfor å stoppe den ved HTTP-handleren. Tenk på signalet som en del av operasjonen.

HTTP-request
   │
   ├── levetid
   ├── frist
   ├── identitet
   └── kansellering
           ↓
       applikasjon
           ↓
       repository
           ↓
       eksternt API

Rust-programmerere ville beskrevet problemet med levetider og task-eierskap. JavaScript-utviklere bør tenke likedan selv om kompilatoren ikke krever det.

En oppgave som overlever sin egen nytte, er fortsatt en lekkasje. Det er kanskje ikke en minnelekkasje. Det er en arbeidslekkasje.

Så langt har vi diskutert Rust uten å bruke Rust. Det finnes steder der en moderne JavaScript-arkitektur faktisk bør innføre det.

Feil kriterium er:

Rust er raskere.
Riktig kriterium er:
Har denne komponenten tilstrekkelig annerledes beregningsegenskaper til å forsvare en egen kjøringsmodell?
Gode kandidater omfatter visse former for storskala parsing, komprimering, bilde- eller signalbehandling, kryptografisk arbeid som ikke allerede er delegert til native implementasjoner, søke- og indekstransformasjoner, deterministisk inntak i store volumer, binære protokoller, simulering, CPU-intensiv rangering, spesialiserte datastrukturer, og andre arbeidslaster der forutsigbar CPU- og minneatferd har reell betydning.

Node-dokumentasjonen trekker i praksis den samme arkitektoniske grensen ved å anbefale worker threads for CPU-intensivt JavaScript framfor vanlig I/O.

Rust gir et annet alternativ. Komponenten kan bli en native modul, en WebAssembly-modul, en kommandolinjekomponent, en køarbeider eller en egen tjeneste.

Avgjørelsen bør ta utgangspunkt i grenser.

Hvis 97 prosent av en applikasjon utfører databasespørringer, HTTP-orkestrering og templating, oppnår en omskriving til Rust kanskje lite. Hvis tre prosent står for 70 prosent av CPU-arbeidet, er de tre prosentene atskillig mer interessante.

Denne arkitekturen er bedre enn ideologiske omskrivinger. JavaScript blir værende der JavaScript er utmerket. Rust dukker opp der egenskapene endrer systemets økonomi.

Her ligger en naturlig arkitektur.

Hono håndterer HTTP. Det validerer input. Det utfører autentisering. Det snakker med vanlig webinfrastruktur. Mesteparten av domenelogikken forblir TypeScript. Kostbar eller spesialisert beregning krysser en smal grense inn i Rust.

Den grensen kan i første omgang være:

Hono
  ↓
Rust-tjeneste

over HTTP. Senere kan den bli:

Hono
  ↓
Wasm

eller en native binding der latens forsvarer den ekstra koblingen.

Honos portabilitet gjør dette særlig attraktivt, fordi HTTP-applikasjonen i seg selv ikke trenger å være bundet til Node. Dokumentasjonen dekker allerede runtimes fra tradisjonelle JavaScript-motorer til WebAssembly- og WASI-miljøer.

Dette begynner å ligne en genuint moderne fullstack-arkitektur:

Nettleser
                    │
                 Nuxt 4
                    │
          ┌─────────┴─────────┐
          │                   │
      Nitro-API-er        Hono-API-er
          │                   │
          └─────────┬─────────┘
                    │
           TypeScript-domene
                    │
        ┌───────────┼────────────┐
        │           │            │
      SQL        eksterne       kø
                  API-er
                                │
                          Rust-arbeider
                                │
                        CPU-tungt arbeid

Det som betyr noe, er at hver grense i diagrammet har en begrunnelse, framfor at stacken tilfeldigvis inneholder to språk.

Mange dårlige programvaresystemer er uærlige. Ikke ondsinnet. Strukturelt.

En funksjon ser synkron ut, men utfører skjult nettverksaktivitet. Et objekt ser immutable ut, men muteres et annet sted. En verdi ser valgfri ut, men er egentlig påkrevd. En returtype beskriver suksess, men ikke forventet feil. En tjeneste framstår tilstandsløs, men beholder tilstand mellom requester. En asynkron funksjon framstår billig, men legger beslag på CPU. Et delt objekt framstår praktisk, men har ingen tydelig eier. En rammeverksabstraksjon framstår portabel, men skjuler Node-spesifikke avhengigheter.

Rusts største bidrag til en JavaScript-arkitekt er ubehag ved denne tvetydigheten.

Rust krever gjentatte ganger at programmereren anerkjenner virkeligheten: denne verdien finnes kanskje ikke; denne operasjonen kan feile; denne referansen kan ikke leve evig; denne tilstanden kan ikke deles trygt; denne verdien ble flyttet; dette tilfellet er ikke håndtert.

TypeScript kan ikke håndheve alle disse påstandene. Arkitektur kan.

For et seriøst Node-, Hono- eller Nuxt-system ville jeg oversatt Rust-tenkning til tolv praktiske regler.

  1. Modellér tilstander, ikke boolske kombinasjoner. Foretrekk diskriminerte unioner når en entitet faktisk finnes i gjensidig utelukkende tilstander.
  1. Skill forventede feil fra programmeringsfeil. Ikke alt hører hjemme i en exception.
  1. Gi muterbar tilstand en tydelig eier. Delt muterbar tilstand bør være sjelden og bevisst.
  1. Behandle request-tilstand som request-tilstand. La aldri request-scopet informasjon ved et uhell bli prosess-scopet.
  1. Hold rammeverksobjekter nær rammeverksgrensene. Honos Context, Nuxts event-objekter og databasespesifikke rader bør ikke bli universelle domenetyper.
  1. Parse én gang, på grensen. Konvertér ekstern usikkerhet til betrodde interne typer så tidlig som mulig.
  1. Begrens samtidighet. Promise.all() er ingen kapasitetsplanleggingsstrategi.
  1. Propagér kansellering og frister. Operasjoner bør ikke uten videre overleve requesten eller jobben som skapte dem.
  1. Skill beregningsarbeid fra orkestrering. CPU-tunge operasjoner kan trenge workers, Rust eller en annen kjøringsgrense.
  1. Foretrekk plattformkapabiliteter framfor plattformimplementasjoner. Web Standards, smale porter og adaptere gjør Hono og Nitro genuint portable.
  1. Ikke importér Rusts lavnivå-optimaliseringsbekymringer ukritisk til JavaScript. Mål først.
  1. Gjør eierskap synlig selv når kompilatoren ikke krever det. Fraværet av en borrow checker innebærer ikke fraværet av levetider.

Disse reglene gir TypeScript som fortsatt ser ut som TypeScript. Det er viktig. Målet er et JavaScript-system hvis atferd er lettere å resonnere om.

Dette leder til en interessant konklusjon. En JavaScript-arkitekt kan hente enorm verdi fra Rust uten å legge en eneste .rs-fil inn i produksjonsapplikasjonen.

Å lære Rust endrer spørsmålene.

Før Rust:

Kan jeg sende dette objektet hit?
Etter Rust:
Hvem eier det etter at jeg har gjort det?
Før:
Kan denne returnere null?
Etter:
Hvilke tilstander har denne operasjonen faktisk?
Før:
Kan disse promisene kjøre samtidig?
Etter:
Hvilken ressurs begrenser samtidigheten deres?
Før:
Vil garbage collection rydde opp i dette?
Etter:
Hvorfor beholder applikasjonen det fortsatt?
Før:
Kan denne tjenesten kalle databasen?
Etter:
Bør dette laget vite at databasen finnes?
Dette er arkitektoniske spørsmål. De betyr atskillig mer enn syntaks.

Det ligger en historisk ironi her.

JavaScript levde en gang nesten utelukkende inne i nettlesersider. Så gjorde Node det om til et serverspråk. Rammeverk bygget deretter stadig mer forseggjorte abstraksjoner rundt Node-miljøet.

Nå skjer noe annet. Abstraksjonslaget beveger seg nedover igjen.

Request. Response. fetch. Strømmer. URL-er. Abort-signaler. Portable utrullingsmål. Edge-runtimes. Workers. WebAssembly.

Hono legemliggjør denne overgangen særlig tydelig. Nitro og Nuxt deltar i den fra fullstack-siden. Node implementerer selv stadig flere webplattform-primitiver.

Økosystemet som vokser fram, blir samtidig mer høynivå og mer systemorientert. Mer høynivå fordi utviklere kan rulle ut den samme applikasjonen på radikalt ulik infrastruktur. Mer systemorientert fordi de forskjellene gjør spørsmål om kapabilitet, levetid, samtidighet, minne, strømming og portabilitet umulige å ignorere.

Dette er nøyaktig terrenget Rust trener utviklere til å tenke i.

For en JavaScript-utvikler i 2026 bør Rust ikke nødvendigvis ses som språket som erstatter JavaScript. Det er kanskje mer verdifullt som språket som klargjør JavaScript.

Det lærer bort at tilstand fortjener struktur. Feil fortjener representasjon. Mutasjon fortjener en eier. Samtidighet fortjener grenser. Ressurser fortjener levetider. Infrastruktur fortjener grenser. Og ytelse fortjener måling framfor folklore.

Disse prinsippene passer Node usedvanlig godt, fordi event loop-arkitekturen er avhengig av disiplinert ressursbruk. De passer Hono kanskje enda bedre, fordi Honos Web Standards-første design oppmuntrer til et lite transportlag rundt portabel applikasjonslogikk. Og de passer Nuxt, fordi en moderne Nuxt-applikasjon spenner over server, nettleser, SSR, reaktiv tilstand og flere mulige utrullingsmiljøer — alle steder der dårlig definerte eierskaps- og tilstandsgrenser til slutt blir dyre.

Lærdommen er derfor ikke:

skriv JavaScript som om det var Rust.

Det ville gitt dårlig JavaScript.

Den bedre lærdommen er:

tenk om JavaScript med noen av spørsmålene Rust nekter å la deg slippe unna.

La så JavaScript forbli JavaScript. Bruk garbage collectoren. Bruk den uttrykksfulle objektmodellen. Bruk TypeScripts strukturelle typing. Bruk promises. Bruk Vues reaktivitet. Bruk Honos minimalisme. Bruk Nitros portabilitet. Og når en genuint CPU-intensiv, minnesensitiv eller deterministisk komponent dukker opp, bruk kanskje Rust der også.

Den modne arkitekturen er et system der hver abstraksjon har en forståelig levetid, hver viktig tilstand har et navn, hver kostbar ressurs har en grense, og hvert språk brukes til den delen av problemet det er godt til å løse. Det er en annen bragd enn at ett språk beseirer et annet.

Rusts bidrag til et slikt system kan være flere tusen linjer produksjonskode. Eller ingen i det hele tatt.

Uansett kan det å lære det endre arkitekturen permanent.