Usare Rust in una startup: un ammonimento

Nov 25 2022
La ruggine è fantastica, per certe cose. Ma pensaci due volte prima di prenderlo per una startup che deve muoversi velocemente.

La ruggine è fantastica, per certe cose. Ma pensaci due volte prima di prenderlo per una startup che deve muoversi velocemente.

Tutta la grafica per questo post è stata generata utilizzando DALL-E.

Ho esitato a scrivere questo post, perché non voglio iniziare o entrare in una guerra santa sui linguaggi di programmazione. (Solo per togliere di mezzo l'esca alla fiamma, Visual Basic è il miglior linguaggio di sempre!) Ma ho avuto un certo numero di persone che mi hanno chiesto della mia esperienza con Rust e se dovrebbero prendere Rust per i loro progetti. Quindi, vorrei condividere alcuni dei pro e dei contro che vedo nell'utilizzo di Rust in un ambiente di avvio, in cui il rapido spostamento e la scalabilità dei team è davvero importante.

Voglio essere chiaro che sono un fan di Rust per certe cose . Questo post non riguarda quanto Rust sia cattivo come lingua o qualcosa del genere. Quello di cui voglio parlare, tuttavia, è come l'utilizzo di Rust comporterà quasi certamente un impatto sulla produttività non banale che potrebbe essere un fattore importante se stai cercando di muoverti velocemente. Valuta attentamente se l'impatto della velocità vale i vantaggi della lingua per la tua azienda e il tuo prodotto.

In anticipo, dovrei dire che Rust è molto bravo in quello per cui è progettato e se il tuo progetto necessita dei vantaggi specifici di Rust (un linguaggio di sistema con alte prestazioni, tipizzazione super forte, nessuna necessità di raccolta dei rifiuti, ecc.) allora Rust è un'ottima scelta. Ma penso che Rust sia spesso utilizzato in situazioni in cui non è adatto e i team pagano il prezzo della complessità e dell'overhead di Rust senza trarne molti vantaggi.

La mia esperienza principale con Rust deriva dal lavorarci per poco più di 2 anni in una precedente startup. Questo progetto era un prodotto SaaS basato su cloud che è, più o meno, un'app CRUD convenzionale: è un insieme di microservizi che forniscono un endpoint API REST e gRPC davanti a un database, oltre ad alcuni altri back- end microservizi (essi stessi implementati in una combinazione di Rust e Python). La ruggine è stata utilizzata principalmente perché un paio dei fondatori dell'azienda erano esperti di ruggine. Nel corso del tempo, il team è cresciuto considerevolmente (aumentando il numero di ingegneri di quasi 10 volte) e anche le dimensioni e la complessità della base di codice sono cresciute notevolmente.

Man mano che il team e la base di codice crescevano, ho sentito che, nel tempo, stavamo pagando una tassa sempre più pesante per continuare a utilizzare Rust. Lo sviluppo a volte è stato lento, il lancio di nuove funzionalità ha richiesto più tempo di quanto mi sarei aspettato e il team ha sentito un vero calo di produttività da quella decisione iniziale di utilizzare Rust. Riscrivere il codice in un'altra lingua avrebbe, a lungo termine, reso lo sviluppo molto più agile e accelerato i tempi di consegna, ma trovare il tempo per il lavoro di riscrittura principale sarebbe stato estremamente difficile. Quindi eravamo un po' bloccati con Rust a meno che non decidessimo di stringere i denti e riscrivere gran parte del codice.

La ruggine dovrebbe essere la cosa migliore dopo il pane a fette, quindi perché non ha funzionato così bene per noi?

Rust ha un'enorme curva di apprendimento.

Ho lavorato in dozzine di lingue nella mia carriera e, con poche eccezioni, nella maggior parte dei linguaggi procedurali moderni (C++, Go, Python, Java, ecc.) tutti molto simili in termini di concetti di base. Ogni lingua ha le sue differenze, ma di solito si tratta di imparare alcuni modelli chiave che differiscono tra le lingue e quindi si può essere produttivi abbastanza rapidamente. Con Rust, tuttavia, è necessario apprendere idee completamente nuove : cose come la vita, la proprietà e il controllo dei prestiti. Questi non sono concetti familiari alla maggior parte delle persone che lavorano in altri linguaggi comuni e c'è una curva di apprendimento piuttosto ripida, anche per programmatori esperti.

Alcune di queste "nuove" idee sono, ovviamente, presenti in altre lingue, specialmente quelle funzionali, ma Rust le porta in un ambiente linguistico "mainstream", e quindi saranno nuove per molti nuovi arrivati ​​in Rust.

Nonostante fossi uno degli sviluppatori più intelligenti ed esperti con cui avevo lavorato, molte persone del team (me compreso) hanno faticato a capire i modi canonici per fare certe cose in Rust, come grok i messaggi di errore spesso arcani dal compilatore, o come capire come funzionavano le librerie di chiavi (più su questo sotto). Abbiamo iniziato a organizzare sessioni settimanali di "apprendimento di Rust" per consentire al team di condividere conoscenze e competenze. Tutto questo è stato un notevole drenaggio della produttività e del morale del team poiché tutti hanno sentito il lento ritmo di sviluppo.

Come punto di confronto di cosa significhi adottare un nuovo linguaggio in un team di software, uno dei miei team di Google è stato uno dei primi a passare interamente da C++ a Go e non ci sono volute più di due settimane prima che l'intero Il team di 15 persone dispari stava programmando abbastanza comodamente in Go per la prima volta. Con Rust, anche dopo mesi di lavoro quotidiano nella lingua, la maggior parte dei membri del team non si è mai sentita pienamente competente. Un certo numero di sviluppatori mi ha detto che erano spesso imbarazzati dal fatto che ci volesse più tempo del previsto per far atterrare le loro funzionalità e che stavano impiegando così tanto tempo a cercare di capire Rust.

Ci sono altri modi per risolvere i problemi che Rust sta cercando di risolvere.

Come accennato in precedenza, il servizio che stavamo costruendo era un'app CRUD abbastanza semplice. Il carico previsto su questo servizio sarebbe stato dell'ordine di non più di poche query al secondo, al massimo, per tutta la durata di questo particolare sistema. Il servizio era un frontend per una pipeline di elaborazione dati abbastanza elaborata che poteva richiedere molte ore per essere eseguita, quindi non ci si aspettava che il servizio stesso fosse un collo di bottiglia delle prestazioni. Non c'era particolare preoccupazione che un linguaggio convenzionale come Python avrebbe avuto problemi a fornire buone prestazioni. Non c'erano particolari esigenze di sicurezza o concorrenza al di là di ciò che qualsiasi servizio rivolto al web deve affrontare. L'unico motivo per cui stavamo usando Rust era perché gli autori originali del sistema erano esperti di Rust, non perché fosse particolarmente adatto per costruire questo tipo di servizio.

Rust ha deciso che la sicurezza è più importante della produttività degli sviluppatori. Questo è il giusto compromesso da fare in molte situazioni, come la creazione di codice in un kernel del sistema operativo o per sistemi embedded con limiti di memoria, ma non penso che sia il giusto compromesso in tutti i casi, specialmente non nelle startup in cui la velocità è cruciale. Sono un pragmatico. Preferirei di gran lunga che il mio team dedicasse del tempo al debug della perdita di memoria occasionale o dell'errore di tipo per il codice scritto, ad esempio, in Python o Go, piuttosto che tutti i membri del team subissero un calo di produttività 4 volte superiore per l'utilizzo di un linguaggio progettato per evitare del tutto questi problemi .

Come accennato in precedenza, il mio team di Google ha creato un servizio, interamente in Go, che nel tempo è cresciuto fino a supportare più di 800 milioni di utenti e qualcosa come 4 volte il QPS di Ricerca Google al suo apice. Posso contare da una parte il numero di volte in cui abbiamo riscontrato un problema causato dal sistema di tipi di Go o dal Garbage Collector negli anni di costruzione e gestione di questo servizio. Fondamentalmente, i problemi che Rust è progettato per evitare possono essere risolti in altri modi : con un buon test, un buon linting, una buona revisione del codice e un buon monitoraggio. Naturalmente, non tutti i progetti software hanno questo lusso, quindi posso immaginare che Rust possa essere una buona scelta in quelle altre situazioni.

Avrai difficoltà ad assumere sviluppatori Rust.

Abbiamo assunto un sacco di persone durante la mia permanenza in questa azienda, ma solo circa due o tre delle oltre 60 persone che si sono unite al team di ingegneri avevano precedenti esperienze con Rust. Questo non è stato per mancanza di tentativi di trovare sviluppatori di Rust: semplicemente non sono là fuori. (Per lo stesso motivo, eravamo riluttanti ad assumere persone che volevano solo programmare in Rust, poiché penso che sia una cattiva aspettativa ambientarsi in un ambiente di avvio in cui le scelte linguistiche e di altra tecnologia devono essere fatte in modo agile.) Questa scarsità del talento degli sviluppatori di Rust cambierà nel tempo, man mano che Rust diventa più mainstream, ma costruire attorno a Rust partendo dal presupposto che sarai in grado di assumere persone che già lo sanno sembra rischioso.

Un altro fattore secondario è che l'uso di Rust porterà quasi sicuramente a uno scisma tra le persone del team che conoscono Rust e quelle che non lo conoscono. Poiché avevamo scelto un linguaggio di programmazione "esoterico" per questo servizio, gli altri ingegneri dell'azienda che altrimenti avrebbero potuto essere utili nella creazione di funzionalità, nel debug di problemi di produzione e così via non sono stati in gran parte incapaci di aiutare perché non potevano fare capi o code del codice di Rust. Questa mancanza di fungibilità nel team di ingegneri può essere una vera responsabilità quando stai cercando di muoverti velocemente e sfruttare i punti di forza combinati di tutti i membri del team. Nella mia esperienza, le persone generalmente hanno poche difficoltà a spostarsi tra linguaggi come C++ e Python, ma Rust è abbastanza nuovo e abbastanza complesso da rappresentare un ostacolo per le persone che lavorano insieme.

Le biblioteche e la documentazione sono immature.

Questo è un problema che (spero!) verrà risolto nel tempo, ma rispetto, ad esempio, a Go, la libreria e l'ecosistema di documentazione di Rust sono incredibilmente immaturi. Ora, Go ha avuto il vantaggio di essere stato sviluppato e supportato da un intero team dedicato di Google prima di essere rilasciato al mondo, quindi i documenti e le librerie erano abbastanza raffinati. La ruggine, al confronto, è stata a lungo percepita come un work in progress. I documenti per molte librerie popolari sono piuttosto scarsi e spesso è necessario leggere il codice sorgente di una determinata libreria per capire come usarla. Questo non va bene.

Gli apologeti di Rust nel team dicevano spesso cose come "async/await sono ancora davvero nuovi" e "sì, mancano i documenti per quella libreria", ma queste carenze hanno avuto un impatto abbastanza significativo sul team. Abbiamo commesso un grosso errore all'inizio adottando Actix come framework web per il nostro servizio, una decisione che ha portato a enormi quantità di dolore e sofferenza quando ci siamo imbattuti in bug e problemi sepolti in profondità nella libreria che nessuno riusciva a capire come risolvere. (Per essere onesti, questo è stato qualche anno fa e forse le cose sono migliorate ormai.)

Naturalmente, questo tipo di immaturità non è propriamente specifico di Rust, ma equivale a una tassa che il tuo team deve pagare. Non importa quanto siano grandi la documentazione e i tutorial del linguaggio di base, se non riesci a capire come usare le librerie, non ha molta importanza (a meno che tu non abbia intenzione di scrivere tutto da zero, ovviamente).

La ruggine rende molto difficile sgrossare nuove funzionalità.

Non conosco nessun altro, ma almeno per me, quando creo una nuova funzionalità di solito non ho tutti i tipi di dati, le API e altri dettagli precisi elaborati in anticipo. Spesso mi limito a scoreggiare il codice cercando di far funzionare un'idea di base e controllando se le mie supposizioni su come dovrebbero funzionare le cose sono più o meno corrette. Fare questo, ad esempio, in Python è estremamente facile, perché puoi giocare velocemente e liberamente con cose come la digitazione e non preoccuparti se alcuni percorsi di codice vengono interrotti mentre abbozzi la tua idea. Puoi tornare più tardi e mettere tutto in ordine e correggere tutti gli errori di tipo e scrivere tutti i test.

In Rust, questo tipo di "codifica in bozza" è molto difficile, perché il compilatore può lamentarsi e si lamenterà di ogni dannata cosa che non supera il controllo del tipo e della durata , come è esplicitamente progettato per fare. Questo ha perfettamente senso quando devi costruire la tua implementazione finale pronta per la produzione, ma fa assolutamente schifo quando stai cercando di mettere insieme qualcosa per testare un'idea o ottenere una base di base. La unimplemented!macro è utile fino a un certo punto, ma richiede comunque che tutto controlli i tipi su e giù per lo stack prima ancora che tu possa compilare.

Ciò che morde davvero è quando devi cambiare la firma del tipo di un'interfaccia portante e ti ritrovi a passare ore a cambiare ogni luogo in cui il tipo viene utilizzato solo per vedere se la tua pugnalata iniziale a qualcosa è fattibile. E poi rifare tutto quel lavoro quando ti rendi conto che devi cambiarlo di nuovo.

In cosa è bravo Rust?

Ci sono sicuramente cose che mi piacciono di Rust e caratteristiche di Rust che mi piacerebbe avere in altre lingue. La matchsintassi è ottima. I tratti Option, Result, e sono davvero potenti e l' operatore è un modo elegante di gestire gli errori. Molte di queste idee hanno controparti in altre lingue, ma l'approccio di Rust con esse è particolarmente elegante.Error?

Userei assolutamente Rust per progetti che richiedono un alto livello di prestazioni e sicurezza e per i quali non ero particolarmente preoccupato della necessità di far evolvere rapidamente parti importanti del codice con un intero team che sta crescendo rapidamente. Per progetti individuali o team molto piccoli (diciamo 2-3 persone), Rust andrebbe probabilmente bene. Rust è un'ottima scelta per cose come moduli del kernel, firmware, motori di gioco, ecc. dove le prestazioni e la sicurezza sono fondamentali e in situazioni in cui può essere difficile eseguire test davvero approfonditi prima della spedizione.

Ok, ora che ho fatto incazzare a sufficienza metà dei lettori di Hacker News, immagino sia il momento giusto per annunciare l'argomento del mio prossimo articolo: perché nanol'editor di testo è superiore. Arrivederci alla prossima!