- Go से Rust में बदलाव सिर्फ़ speed बढ़ाने का मामला नहीं है, बल्कि
nil, error handling, data race, और resource lifetime जैसी समस्याओं को compile-time guarantees में ले जाने के क़रीब है
- Go की ताकत तेज़ compilation, सरल goroutine, और मज़बूत backend ecosystem है, लेकिन Rust
Option, Result, Send/Sync के ज़रिए ज़्यादा गलतियों को type system में ही रोक देता है
- Rust का borrow checker और
async/await सीखने की कठिनाई और usability cost लाते हैं, और compile time को भी Go की तुलना में साफ़ गिरावट के रूप में स्वीकार करना पड़ता है
- बदलाव के लिए पूरे rewrite के बजाय hot path services, worker, या gateway के पीछे के कुछ endpoints जैसे साफ़ boundary वाले components से शुरुआत करने की रणनीति ज़्यादा उपयुक्त है
- अपेक्षित फ़ायदे का सार है: CPU में 20~60% कमी, memory में 30~50% कमी, ज़्यादा flat P99 latency, और
nil dereference तथा race-related failures में कमी
बदलाव का फोकस
- Go से Rust में बदलाव “क्या Rust ज़्यादा तेज़ है” से अधिक correctness guarantees, runtime trade-offs, और developer experience के फ़र्क का सवाल है
- तुलना का केंद्र backend services हैं, जहाँ Go के छोटे static binaries, networking-केंद्रित standard library, और HTTP server·gRPC·database ecosystem को आधार माना गया है
- कुछ बातें CLI tools, embedded firmware, और game engine पर भी लागू हो सकती हैं, लेकिन वे इसका optimized target नहीं हैं
- संबंधित पृष्ठभूमि सामग्री के रूप में 2017 का “Go vs Rust? Choose Go.” और Shuttle टीम का “Rust vs Go: A Hands-On Comparison” दिया गया है
- Go एक सफल language है, लेकिन
nil का व्यापक उपयोग, type system के बजाय discipline पर निर्भर error handling, और लंबे समय तक generics का न होना जैसे design choices, Rust से तुलना में मुख्य मुद्दे बनते हैं
- JetBrains Developer Ecosystem Survey में Go को 17~19% working developers द्वारा इस्तेमाल की जाने वाली language के रूप में दिखाया गया है, जबकि Rust लगातार बढ़ रही है लेकिन उसका हिस्सा अभी छोटा है
टूलिंग सिस्टम
- Go और Rust दोनों में build, test, format, lint, और dependency management के लिए एक समान interface देने वाला batteries-included tooling system है
cargo, Go tooling के समकक्ष features को primary tool के रूप में ज़्यादा व्यापक ढंग से देता है
go.mod / go.sum → Cargo.toml / Cargo.lock: project settings और dependency manifest
go get / go mod tidy → cargo add / cargo update: dependency जोड़ना और resolve करना
go build → cargo build: compile करना
go run . → cargo run: build के बाद run करना
go test ./... → cargo test: test
go vet ./... → cargo clippy: linter, और Clippy, vet की तुलना में काफ़ी ज़्यादा opinionated है
gofmt / goimports → cargo fmt: zero-config auto formatter
golangci-lint run → cargo clippy -- -D warnings: strict lint mode
go doc → cargo doc --open: API docs बनाना और खोलना
pprof → cargo flamegraph / samply: CPU profiling
govulncheck → cargo audit: advisory database आधारित vulnerability checks
- Go में अक्सर
golangci-lint, mockgen, air, goreleaser जैसे third-party tools से gaps भरे जाते हैं, जबकि Rust का primary ecosystem डिफ़ॉल्ट रूप से ज़्यादा features को कवर करता है
- अगर external crate की ज़रूरत हो भी, तो
cargo watch, cargo nextest जैसे tools cargo install cargo-nextest एक बार चलाकर install हो जाते हैं और cargo nextest की तरह native tool जैसा व्यवहार करते हैं
gofmt और rustfmt का फ़ायदा बारीक style preference से ज़्यादा code review में style debates को ख़त्म करना है
- Rob Pike के Go Proverbs का उद्धरण: “Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.”
Go और Rust के मुख्य अंतर
- दोनों languages compiled, statically typed, single-binary deployment, और मज़बूत concurrency model देती हैं, लेकिन अंतर इस बात में है कि compiler क्या guarantee देता है और runtime behavior पर आपका control कितना है
- मुख्य तुलना बिंदु इस प्रकार हैं
- stable release: Go 2012, Rust 2015
- type system: Go static·structural typing के साथ 1.18 से generics सपोर्ट करता है, Rust static·nominal typing के साथ generics·traits·lifetimes सपोर्ट करता है
- memory management: Go concurrent·low-latency garbage collection इस्तेमाल करता है, Rust ownership और borrowing पर आधारित है और उसमें GC नहीं है
- null safety: Go में
nil व्यापक है, Rust में null नहीं है और Option<T> type-level विकल्प है
- error handling: Go में
error interface और if err != nil { ... }, Rust में Result<T, E>, ? operator, और complete pattern matching
- concurrency: Go goroutine और channel-आधारित CSP इस्तेमाल करता है, Rust
tokio पर async/await, channel, और threads का उपयोग करता है
- cancellation: Go में convention-आधारित
context.Context, Rust में CancellationToken जैसे explicit और type-checked propagation
- data race: Go में
-race से runtime पर probabilistic detection, Rust में Send/Sync से compile time detection
- compile time: Go बहुत तेज़ है, Rust में खासकर clean build धीमे होते हैं
- runtime: Go में लगभग 2MB runtime और GC है, Rust में
libc के अलावा runtime नहीं होता या MUSL के साथ पूरी तरह static build संभव है
- ecosystem size: Go में लगभग 7.5 लाख+ modules, Rust में 2.5 लाख+ crates
- Go में
nil handling, error propagation, data races, resource lifetime, cancellation, और generics जैसी जाँचें अक्सर convention, tools, और runtime detection पर निर्भर रहती हैं, जबकि Rust में ये type system के अंदर आ जाती हैं
- Rust का
Mutex<T> internal value तक पहुँच सिर्फ़ .lock() से मिले guard के ज़रिए देता है, जिससे “lock लेना भूल जाने वाला path” type level पर ही हट जाता है
- यही पैटर्न
Option, Result, &mut T, Send/Sync, और RAII guards में बार-बार दिखता है, और एक बार आदत पड़ने पर compiler दिमाग़ में होने वाली manual checking की जगह ले लेता है
Rust पर विचार करने के लिए मजबूर करने वाली Go की सीमाएँ
- चूंकि ज़्यादातर backend workloads में Go पर्याप्त तेज़ है, इसलिए Rust पर विचार करने का मुख्य कारण speed से ज़्यादा error handling की verbosity,
nil pointer का जोखिम, और enum·trait जैसे परिष्कृत type system features की कमी है
- Go interfaces, Rust traits का पर्याप्त विकल्प नहीं हैं, और standard library में
Set type न होने से map[T]struct{} जैसे idiomatic workaround की ज़रूरत पड़ती है
-
production में nil panic
- Go service कई महीनों तक सामान्य रूप से चल सकती है, फिर किसी खास code path में pointer
nil check छूट जाने पर goroutine panic हो सकता है
- उदाहरण में
Find, (*User, error) लौटाता है और “not found” पर error nil होता है, लेकिन user की जांच caller की ज़िम्मेदारी रह जाती है
user.Account.Notify() तब crash हो सकता है जब user या Account nil हो
nilaway, staticcheck जैसे linter और IDE checks कुछ मामलों को पकड़ लेते हैं, लेकिन ये opt-in हैं, probabilistic हैं, और package boundaries के पार भरोसेमंद तरीके से काम नहीं करते
- Rust का
Option<T> इस तरह बनाया गया है कि None case को handle किए बिना dereference नहीं किया जा सकता, जिससे इस श्रेणी की failures खत्म हो जाती हैं
-
वह data race जिसे -race भी नहीं पकड़ पाया
go test -race बेहतरीन tool है, लेकिन चूंकि यह runtime detector है, इसलिए testing के दौरान वास्तव में चली हुई races ही पकड़ता है
- Go में ऐसा code भी compile हो जाता है जिसमें दो goroutine बिना lock के एक map को modify करते हैं, और यह production load में फट सकता है
- Rust में threads के बीच mutable state share करने के लिए
Send और Sync लागू करने वाले types चाहिए होते हैं, और अगर आप साधारण HashMap को threads के बीच share करना चाहें तो वह compile नहीं होगा
- आपको
Arc<Mutex<...>>, Arc<RwLock<...>>, या channel में से एक इस्तेमाल करने के लिए मजबूर किया जाता है, और race condition type error बन जाती है
- Paul Dix ने InfluxDB 3.0 rewrite की वजह के रूप में data races को हटाने का सीधे उल्लेख किया
- “[The main benefit is] fearless concurrency — eliminating data races essentially, which we had before. Really gnarly bugs in version 1 of Influx due to that.”
- स्रोत: Paul Dix, Founder & CTO, InfluxData, Rust in Production
-
composable error handling
- Go का
if err != nil { return err } function की असली logic को धुंधला कर सकता है, और fmt.Errorf("doing X: %w", err) से context wrap करना compiler rule नहीं बल्कि discipline पर निर्भर करता है
- Lobste.rs thread में अनुभवी Go developers ने तर्क दिया कि
errcheck और golangci-lint ज़्यादातर missing error handling पकड़ लेते हैं, और explicit if err != nil घने ? chains से ज़्यादा readable है
- Peter Bourgon, Go की explicit error handling को भाषा के एक जानबूझकर चुने गए सांस्कृतिक मूल्य के रूप में पेश करते हैं
- “I think that error handling should be explicit, this should be a core value of the language.”
- स्रोत: Peter Bourgon, GoTime #91, Dave Cheney के Zen of Go में उद्धृत
- Rust का
Result<T, E> खुद type signature का हिस्सा होता है, इसलिए इसे भुलाया नहीं जा सकता, और thiserror::Error से परिभाषित enum तथा #[from] के ज़रिए error conversion और exhaustiveness checking मिलती है
- अगर नया error variant जोड़ा जाए, तो compiler बता देता है कि किन
match स्थानों को अपडेट करने की ज़रूरत है
-
boxing के बिना generics
- Go 1.18 generics उपयोगी हैं, लेकिन type parameters वाले methods की अनुपस्थिति, GC shape stenciling, और कभी-कभी चौंकाने वाली performance characteristics जैसी सीमाएँ हैं
- Rust generics monomorphized होते हैं, इसलिए हर instantiation specialized code बनाती है और कोई runtime cost नहीं होती
- traits के साथ मिलकर यह zero-cost abstraction संभव बनाता है
- यह handler code की तुलना में middleware, generic repository, decoder, parser जैसे shared infrastructure में ज़्यादा महत्वपूर्ण है, और Go अक्सर इन क्षेत्रों में
interface{}/any और type assertions पर लौट आता है
-
अनुमानित latency
- Go का GC उत्कृष्ट, concurrent, low-latency है और सामान्य service workloads के लिए अच्छी तरह tuned है, लेकिन “low-pause” का मतलब “no-pause” नहीं है
- allocation-heavy स्थितियों में P99 latency tail, hot path में allocate न करने वाले Rust implementation से बदतर हो सकती है
- trading, real-time bidding, network proxy, high-throughput ingestion जैसे latency-sensitive systems में GC pause का न होना एक वास्तविक लाभ है
- Stephen Blum का कहना है कि PubNub के scale पर ज़रूरी price-per-dollar performance capacity पाने के लिए Rust की ज़रूरत है
- “Go is great at our scale, but we really need something that is going to give us the price-per-dollar performance capacity that we need, and Rust is going to get us there. That’s why basically everything is heading towards Rust these days.”
- स्रोत: Stephen Blum, CTO, PubNub, Rust in Production
Go पैटर्न के लिए Rust में समकक्ष
- Rust में जल्दी सहज होने का सबसे अच्छा तरीका है कि जिन Go पैटर्न को आप पहले से जानते हैं, उन्हें Rust के समकक्ष पैटर्न से मैप करें
- एक ही backend service को दोनों भाषाओं में लागू करने वाला लंबा उदाहरण Shuttle comparison में है
-
एरर हैंडलिंग: if err != nil vs Result<T, E>
- Go में
os.ReadFile(path) और json.Unmarshal के बाद if err != nil के साथ context जोड़कर एरर लौटाई जाती है
- Rust में यह
fs::read_to_string(path)?, serde_json::from_str(&data)?, Ok(cfg) से बनता है
? ऑपरेटर if err != nil { return err } पैटर्न की जगह लेता है, और अगर From<E1> for E2 implement किया गया हो तो type conversion भी संभाल लेता है
thiserror का #[from] इस conversion को idiomatic तरीके से support करता है
-
null: nil vs Option<T>
- Go में
GetUser(id string) *User यूज़र न मिलने पर nil लौटाता है, और अगर caller fmt.Println(u.Name) करे तो nil होने पर panic हो जाता है
- Rust में
get_user(id: &str) -> Option<User> Some(User) या None लौटाता है
let user = get_user("123"); println!("{}", user.name); compile error देता है क्योंकि user, User नहीं बल्कि Option<User> है
match get_user("123") के साथ Some(u) और None दोनों को handle करना पड़ता है
- safe Rust में
nil नहीं होता, और references null नहीं हो सकते
-
interface vs trait
- Go interface structural होते हैं, और type उन्हें implicitly satisfy कर देता है
- Rust trait nominal होते हैं, और उन्हें explicitly implement करना पड़ता है
- Go का तरीका तुरंत duck typing के लिए अच्छा है, जबकि Rust का तरीका refactoring और discoverability के लिए बेहतर है, और किसी खास trait implementation को grep से ढूँढा जा सकता है
fn handle<R: Reader>(r: R) जैसे trait bound वाले generic function ज़्यादातर cases को cover कर लेते हैं, और monomorphization की वजह से runtime dispatch नहीं होता
- runtime dispatch की ज़रूरत वाले heterogeneous implementations को store करने के लिए
Box<dyn Trait> या Arc<dyn Trait> का इस्तेमाल किया जाता है
-
Goroutine vs async task
- Go का concurrency model
go doWork(ctx, input) की तरह सरल है, goroutine हल्के होते हैं, और runtime उन्हें OS thread के ऊपर schedule करता है
- Go की बड़ी खासियत यह है कि sequential code और parallel code के बीच कोई syntactic फ़र्क नहीं होता
- Rust backend services में लगभग हमेशा
tokio executor के ऊपर async/await का इस्तेमाल करता है
- async function
Future लौटाते हैं, और जब तक उन्हें await या spawn नहीं किया जाता, वे चलना शुरू नहीं करते
- compiler
.await points के पहले और बाद में Send/Sync को track करता है, और अगर कोई non-Send value await के पार hold की जाती है तो compile error देता है
- goroutine जैसी built-in preemption न होने की वजह से अगर async task के अंदर CPU-bound काम लंबे समय तक चलता रहे तो executor starve हो सकता है, इसलिए उसे
tokio::task::spawn_blocking या rayon को सौंपना चाहिए
-
context.Context vs CancellationToken
- Go में हर blocking call को
context.Context दिया जाता है
- Rust में built-in
context.Context नहीं है, और cancellation के सबसे करीब tokio_util::sync::CancellationToken है
- timeout के लिए future को
tokio::time::timeout(dur, fut) से wrap किया जाता है
- deadline और values को एक context object की बजाय अक्सर explicit arguments या
tracing span के ज़रिए pass किया जाता है
- Dave Cheney के The Zen of Go से उद्धरण:
- “Go doesn’t have a way to tell a goroutine to exit. There is no stop or kill function, for good reason. If we cannot command a goroutine to stop, we must instead ask it, politely.”
- Go में यह “विनम्र अनुरोध” परंपरागत रूप से
context.Context के ज़रिए पहुँचता है, और Rust में CancellationToken या watch channel के ज़रिए, लेकिन compiler छूट जाने पर चेतावनी दे सकता है
-
स्ट्रिंग: string vs String और &str
- Go का
string एक UTF-8 byte slice है, जिसमें assignment पर header copy होता है और underlying bytes share होने वाली immutable structure होती है
- Rust इसे दो types में बाँटता है
String: owned, heap-allocated, और growable
&str: दूसरे string data पर borrowed view, और ज़्यादातर मामलों में Go के string parameter के समकक्ष
- अनुभवजन्य नियम यह है कि arguments में
&str लें, और नया data बनाते समय String लौटाएँ
&str और String का यह विभाजन Rust के “borrow vs own” मॉडल का संक्षिप्त रूप दिखाता है
Go generics का मूल्यांकन
- Go ने 1.18, मार्च 2022 में generics को पेश किया, यानी भाषा के लॉन्च के 13 साल बाद
- generics उपयोगी हैं, लेकिन Rust·Haskell·आधुनिक C++ में जिन फायदों की उम्मीद की जाती है वे पूरी तरह नहीं देते, और generic type system की कई कमियां भी साथ लाते हैं
-
standard library लगभग इसका इस्तेमाल नहीं करती
- generics आने के 3 साल बाद भी Go standard library ज्यादातर generics से बचती है
sort.Slice अब भी cmp.Ordered constraint की जगह func(i, j int) bool closure लेता है
sync.Map अब भी any/any के रूप में typed है
- मौजूद generic helper सिर्फ कुछ पैकेजों में हैं, जैसे
slices, maps, cmp, sync के तहत कुछ आइटम
- Go 1 compatibility promise की वजह से मौजूदा non-generic API को बदलना कठिन है, यह कुछ हद तक वजह समझाता है, लेकिन Rust की तरह generics को मुख्य औजार के रूप में नहीं अपनाया गया
- Rust में शुरुआत से ही
Option<T>, Result<T, E>, Vec<T>, HashMap<K, V>, Iterator, From/Into, और सभी collections व smart pointers में generics गहराई से शामिल हैं
-
trait system नहीं है, सिर्फ structural constraint हैं
- Rust generics, trait के साथ जुड़े होते हैं, जो ad-hoc polymorphism, supertrait, associated type, blanket impl, coherence को संभालते हैं
- Go constraint,
~ operator जोड़े गए interface के अधिक करीब हैं, जो type-set membership के लिए है
- Go में Rust के
trait Ord: Eq + PartialOrd जैसी supertrait hierarchy, Iterator के type Item; जैसी associated type, या impl<T: Display> ToString for T जैसी blanket impl नहीं हैं
- Go में type parameter वाले method नहीं लिखे जा सकते, इसलिए
func (s Set[T]) Map[U](<https://corrode.dev/learn/migration-guides/go-to-rust/f func(T>) U) Set[U] जैसे रूप संभव नहीं हैं
- जैसे ही abstraction “कुछ operations वाले किसी भी
T पर काम करने वाले function” से आगे जाता है, Go फिर any, type assertion, code generation, runtime reflection की ओर लौट जाता है
-
type inference और implementation strategy का अंतर
- Rust, closure, iterator chain, और
? operator सहित पूरे expression में type information propagate करता है
- Go का inference ज्यादा उथला है, और आमतौर पर function arguments से type parameter infer करता है, लेकिन return-position context में infer नहीं कर सकता, इसलिए call site पर अक्सर explicit type argument देने पड़ते हैं
- Go, GCShape stenciling and dictionaries नाम का एक middle path चुनता है ताकि compile time तेज रहे, लेकिन type parameter पर method call में indirection आ सकता है
- इसे दिखाने वाले संदर्भ के रूप में PlanetScale का लेख दिया गया है
- Rust,
Vec<i32> और Vec<String> के लिए अलग-अलग specialized machine code बनाता है और runtime dispatch नहीं होता
- monomorphization की कीमत compile time है, और दोनों भाषाएं अलग-अलग लक्ष्यों के लिए optimize करती हैं
-
type system की खामियों को नहीं भर पाता
- Rust में generics और trait,
Box<dyn Any> या runtime reflection की जरूरत वाली ज्यादातर स्थितियों को खत्म कर देते हैं
- Go generics,
any, reflect, और ORM·decoder·mock में हावी code generation patterns को खत्म नहीं कर पाते
encoding/json अब भी reflection का उपयोग करता है, database/sql अब भी any का उपयोग करता है, और mockgen अब भी code generate करता है
- Go के generics एक ऐसे नए tool जैसे लगते हैं जो सीमित मामलों में उपयोगी है, जबकि Rust के generics ऐसी बुनियाद की तरह काम करते हैं जिन्हें हटा दिया जाए तो भाषा ढह जाएगी
Rust backend ecosystem
- Rust ecosystem में भी सामान्य backend services के लिए “default choice” कुछ हद तक स्थिर हो चुकी है
- प्रमुख mapping इस प्रकार है:
- HTTP server: Go
net/http, chi, gin, echo, fiber → Rust axum on hyper
- HTTP client: Go
net/http, resty → Rust reqwest
- gRPC: Go
google.golang.org/grpc + protoc-gen-go → Rust tonic + prost
- SQL: Go
database/sql, sqlc, sqlx, gorm → Rust sqlx, sea-orm, diesel
- Migrations: Go
golang-migrate, goose → Rust sqlx migrate, refinery
- JSON: Go
encoding/json, sonic, goccy/go-json → Rust serde + serde_json
- Logging: Go
log/slog, zerolog, zap → Rust tracing + tracing-subscriber
- Metrics: Go
prometheus/client_golang → Rust metrics + metrics-exporter-prometheus
- Config: Go
viper, koanf → Rust config / config-rs, figment
- CLI: Go
cobra, urfave/cli → Rust clap derive
- Errors: Go
errors, pkg/errors → Rust libraries के लिए thiserror, binaries के लिए anyhow
- Testing: Go
testing, testify, gomega → Rust built-in #[test], rstest, assert_matches
- Mocking: Go
mockgen, moq → Rust में hand-written fakes प्रचलित हैं, और mockall भी उपयोग होता है
- Background tasks: Go goroutines +
errgroup → Rust tokio::spawn + JoinSet
- सामान्य backend service में
axum + sqlx + tokio + tracing + serde + clap का संयोजन आवश्यक चीजों के 90% को कवर करता है, ऐसा बताया गया है
Borrow checker और learning curve
- Go से Rust में आने पर यह मानकर चलना चाहिए कि एक दीवार से टकराना पड़ेगा
- Go runtime memory और aliasing को आपके लिए संभालता है, लेकिन Rust उन फैसलों को type system में ले जाता है, इसलिए शुरुआती कुछ हफ्तों में ऐसा code भी compiler द्वारा reject हो सकता है जो “स्पष्ट रूप से चलना चाहिए”
- वे patterns जिनसे Go developers अक्सर टकराते हैं:
- लंबे समय तक जीवित references: Go में map से निकाले गए
*User को लंबे समय तक पकड़े रखना स्वाभाविक है, लेकिन Rust में उस borrow के जीवित रहने तक map को modify नहीं किया जा सकता
- self-referential struct: Go में data और उस data पर iterator को एक ही struct में रखा जा सकता है, लेकिन Rust में
Pin, ouroboros, या redesign की ज़रूरत होती है
- goroutine के बीच mutable state share करना: Go का
mu sync.Mutex; data map[K]V pattern, Rust में Arc<Mutex<HashMap<K, V>>> जैसा बन जाता है
- function से reference return करना: lifetime annotations सामने आते हैं, और Go developers के लिए यह एक नया concept है
- Borrow checker को रुकावट डालने वाला “gatekeeper” नहीं, बल्कि वास्तविक bugs को उजागर करने वाले तंत्र के रूप में देखना चाहिए
- यह compile time पर उन स्थितियों को पकड़ लेता है जहाँ value move होने के बाद फिर से use की जाती है, कई threads एक ही data को एक साथ छूते हैं, null या dangling pointers को dereference किया जाता है, या references value से ज़्यादा समय तक जीवित रहते हैं
- जब borrow की अवधारणा भीतर बैठ जाती है, तो यह लड़ने की चीज़ नहीं बल्कि सहयोगी बन जाती है, और अनुभवी Rust developers आम तौर पर कहते हैं कि 4 से 12 हफ्तों के भीतर borrow checker सहायक लगने लगता है
- PubNub के CTO Stephen Blum ने Rustacean Station में अपने पहले महीने को “ऐसा जैसे पहली बार programming सीख रहा था” कहा, और बताया कि उन्हें borrow checker और lifetimes से मजबूरन जूझना पड़ा
clap maintainer Ed Page ने Rustacean Station: clap with Ed Page में कहा कि borrow checker ने उन्हें high-level समस्याओं पर ध्यान देने दिया और उन चीज़ों को भी पकड़ लिया जिन्हें उनका खुद का analysis नहीं पकड़ पाया था
Rust migration की मुख्य चुनौतियाँ
-
Compile time
- Rust compile time को Go की तुलना में साफ़ गिरावट के रूप में स्वीकार करना चाहिए, और medium-scale services के clean release builds, Go के लगभग तुरंत compile होने के विपरीत, कई मिनट ले सकते हैं
- incremental builds और
cargo check व्यावहारिक हैं, और compile time हर साल बेहतर हुआ है, लेकिन Go से अंतर महसूस होता है
- edit loop में
cargo check का उपयोग करें, फ़ायदा दिखने पर workspace में split करें, और जिन crates में procedural macros ज़्यादा हों उन्हें अलग crate में रखें ताकि वे केवल बदलाव होने पर ही दोबारा compile हों
- अधिक जानकारी के लिए Rust compile time कम करने के टिप्स देखे जा सकते हैं
-
async coloring समस्या
- Rust का
async fn / fn विभाजन, Go से आने वालों के लिए usability में सबसे बड़े regressions में से एक है
- async trait Rust 1.75 से stable हो चुका है, लेकिन dynamic dispatch के साथ मिलाने पर अब भी कुछ खुरदरे हिस्से हैं
- कुछ स्थितियों में इन समस्याओं को ढकने के लिए
async-trait crate का उपयोग करना पड़ता है
-
छोटा ecosystem
- Rust crate ecosystem बढ़ रहा है और libraries की quality भी कुल मिलाकर ऊँची है, लेकिन backend से जुड़े कुछ क्षेत्रों में Go आगे है
- वे क्षेत्र जहाँ Go आगे है में Kubernetes operators, cloud provider SDKs, और कुछ niche storage systems के database drivers शामिल हैं
- migration को final करने से पहले लगभग एक दिन निकालकर यह देख लेना चाहिए कि जिन libraries पर आप निर्भर हैं, उनके उपयोग योग्य Rust alternatives मौजूद हैं या नहीं
- कुछ teams को छोड़े गए XML schema validation crates को update करना पड़ सकता है या कम-ज्ञात protocols के clients खुद लिखने पड़ सकते हैं
Integration strategy
- Go से Rust की सफल transition, सब कुछ एक साथ rewrite करने के बजाय, tactical choices का मामला है
- Microsoft Principal Engineer Victor Ciura ने Rust in Production में कहा, “हम मज़े के लिए सब कुछ Rust में rewrite नहीं करते, बल्कि जहाँ नया component Rust के लिए बेहतर fit हो, वहाँ Rust चुनते हैं”
-
1. Hot path को service के रूप में अलग करना
- अगर कोई खास service लगातार समस्या पैदा कर रही है, तो उसी API contract के पीछे रखते हुए केवल उस service को Rust में rewrite करना सबसे कम जोखिम वाला migration है
- target वे services हो सकती हैं जिनका CPU usage अधिक हो, जो latency-sensitive हों, या जहाँ reliability की समस्याएँ बार-बार आती हों
- दूसरी Go services HTTP/gRPC के ज़रिए वैसे ही communicate करती रहेंगी, इसलिए उन्हें internal implementation language जानने की ज़रूरत नहीं होगी
- Radar के CTO Jeff Kao ने Rust in Production में कहा कि Discord के Go से Rust पर जाने वाले लेख ने Radar को भी वही कोशिश करने का विचार दिया
-
2. Sidecar या worker process बदलना
- background workers, queue consumers, ingestion pipelines, और CPU-bound batch jobs अच्छे शुरुआती targets हैं
- इनके पास आम तौर पर queue या topic जैसे स्पष्ट input/output boundaries होते हैं, और बाकी system के साथ in-process shared state नहीं होती
-
3. cgo संभव है, लेकिन कष्टदायक
- Go से cgo के ज़रिए Rust को call किया जा सकता है, और इसे संभालने के लिए अच्छे guides भी हैं
- लेकिन backend services में यह आम तौर पर recommended नहीं है
- build complexity और FFI overhead, “Rust service खड़ी करके उसे network call के पीछे रखना” वाले तरीके की तुलना में, अक्सर फ़ायदे को खत्म कर देते हैं
- libraries और CLI tools में यह ज़्यादा व्यावहारिक हो सकता है
-
4. Gateway के पीछे Strangler Pattern लागू करना
- अगर API gateway या reverse proxy है, तो सिर्फ़ कुछ endpoints को नई Rust service की तरफ route किया जा सकता है और बाकी को Go में छोड़ा जा सकता है
- यह खास तौर पर तब अच्छा काम करता है जब authentication, search, या payments जैसी एक bounded context migration unit के रूप में उपयुक्त हो
- इस pattern को “strangler fig” इसलिए कहा जाता है क्योंकि नई service पुरानी service के आसपास बढ़ती है और अंततः उसे पूरी तरह replace कर देती है
व्यावहारिक migration tips
- शुरुआत स्पष्ट boundaries वाली services से करनी चाहिए, और सबसे केंद्रीय या सबसे ज़्यादा deploy होने वाली service नहीं चुननी चाहिए
- ऐसी service चुनें जिसका बाकी system के साथ contract अच्छी तरह defined हो और जिसका blast radius छोटा हो
-
वही API contract बनाए रखें
- अगर Go service REST API expose करती है, तो Rust service में भी वही paths, वही JSON shape, और वही error wrappers होने चाहिए
- clients को migration दिखाई नहीं देना चाहिए, और gateway के ज़रिए traffic को धीरे-धीरे shift किया जा सकता है
-
idioms को शब्दशः translate न करें
if err != nil { return err } का रूप ? बन जाता है
- per-request goroutine pattern को केवल तब
tokio::spawn में ले जाएँ जब उसकी सच में ज़रूरत हो
axum पहले से ही requests को concurrently handle करता है
- एक method वाले interfaces अक्सर
Box<dyn Trait> नहीं बल्कि generics के trait bounds बन जाते हैं
-
Compiler को pair programmer की तरह इस्तेमाल करें
- Rust compiler errors आम तौर पर उच्च quality के होते हैं, और अगर उन्हें धीरे-धीरे पढ़ा जाए तो वे लगभग हमेशा सही जवाब की ओर ले जाते हैं
- जो team members सबसे ज़्यादा संघर्ष करते हैं, वे अक्सर compiler को सहयोगी की तरह नहीं बल्कि प्रतिद्वंद्वी की तरह देखते हैं
-
शुरुआत में training में निवेश करें
- Rust migration को “साइड में” सीखते हुए करना अक्सर अच्छे नतीजे नहीं देता
- workshops, online course, और वास्तविक codebase पर pair sessions जैसी learning time को सचमुच schedule करना चाहिए
- जब team सक्षम हो जाती है, तो शुरुआती निवेश कई गुना वापस मिलता है
वे क्षेत्र जहाँ Go अब भी उपयुक्त है
- सब कुछ Rust में ले जाने की ज़रूरत नहीं है, और कुछ क्षेत्रों में Go खास तौर पर अच्छा है
-
Kubernetes नैटिव टूल्स
- operator, controller, और CRD का क्षेत्र ecosystem के लिहाज़ से बहुत ज़्यादा Go-केंद्रित है
-
CLI utility और developer tools
- तेज़ compile, आसान cross-compilation, और सरल deployment इसकी बड़ी ताकत हैं
-
Glue services
- पतली API layer, proxy, और format converter जैसे मामलों में Rust के boilerplate का अनुपात उतना लाभदायक नहीं हो सकता
-
जहाँ टीम की गति, पूर्ण शुद्धता की गारंटी से ज़्यादा महत्वपूर्ण है
- जिन क्षेत्रों में तेज़ी से आगे बढ़ना ज़रूरी है, वहाँ Go अब भी उपयुक्त हो सकता है
- Canonical के VP of Engineering Jon Seager ने Rust in Production में कहा कि Go networking services के लिए बहुत अच्छा विकल्प है, Canonical में Go का काफ़ी इस्तेमाल है, और Juju भी एक बहुत बड़ा Go codebase है
- hybrid strategy आम है, और कई टीमें आख़िरकार multi-language backend अपनाती हैं, जहाँ “boring” services के लिए Go और उन services के लिए Rust इस्तेमाल होता है जहाँ reliability और performance अतिरिक्त मेहनत की भरपाई कर देते हैं
अपेक्षित सुधार
- आँकड़े workload के अनुसार काफ़ी बदलते हैं, इसलिए इन्हें वादे की तरह नहीं बल्कि एक मोटे guide की तरह देखना चाहिए
- Go से Rust migration में देखी गई अनुमानित सुधार-सीमा:
- CPU उपयोग: 20~60% कमी
- Go पहले से ही efficient है, इसलिए Python से Rust में जाने जितना नाटकीय बदलाव नहीं होता
- GC की अनुपस्थिति और ज़्यादा tight loops से लाभ मिलता है
- मेमोरी: 30~50% कमी
- मुख्य कारण GC overhead का न होना और छोटा runtime है
- P99 latency: काफ़ी अधिक consistent
- Rust services में Go services की तुलना में GC-प्रेरित jitter कम होता है और latency profile ज़्यादा flat होती है
- low-latency GC आने के बाद Go में भी काफ़ी सुधार हुआ है, लेकिन high load पर अंतर अब भी रहता है
- production failures: यह वह क्षेत्र है जहाँ टीमें सबसे सक्रिय रूप से सुधार रिपोर्ट करती हैं
- data race, nil dereference, और missing error path जैसे वे bug, जो
go test -race पास करके production तक पहुँच जाते हैं, Rust में compile ही नहीं होते
- Rust migration के बाद on-call rotation आम तौर पर बहुत ज़्यादा शांत और नीरस हो जाती है
- InfluxData के Staff Engineer Andrew Lamb ने Rustacean Station: Rebuilding InfluxDB with Rust में कहा कि InfluxDB को दोबारा लिखने के बाद crashes, अजीब multi-threaded race conditions, और उन समस्याओं का पीछा करने की ज़रूरत नहीं रही जो पहले बहुत समय खा जाती थीं
- Go से Rust में जाने पर throughput in 10 गुना सुधार मिलने की संभावना, Python से Rust migration जितनी कम होती है
- वास्तविक लाभ हैं “बेहूदा errors” में कमी, latency tail का अधिक flat होना, और embedded development या systems programming जैसे दूसरे क्षेत्रों तक उसी भाषा के साथ विस्तार करने की क्षमता
अतिरिक्त सावधानियाँ
- Rust का type system synchronization logic की हर bug को ख़त्म नहीं करता, लेकिन जिन types को synchronization के बिना threads के बीच share नहीं किया जा सकता, वे compile नहीं होते
- “lock लगाना भूल गए” जैसी समस्याएँ, जो चुपचाप data corruption तक ले जाती हैं, Rust type system रोक सकता है
- Go
string एक immutable byte sequence है और प्रचलन के अनुसार UTF-8 होता है, लेकिन यह type level पर guaranteed नहीं है
- सबसे क़रीबी mapping, read-only view के आधार पर Go
string ↔ Rust &str और mutable buffer के आधार पर Go []byte ↔ Rust Vec<u8> है
- Rust
String, &str का owned और growable version है, जिसमें यह अतिरिक्त guarantee होती है कि content वैध UTF-8 है
- अधिक जानकारी के लिए Strings, bytes, runes and characters in Go देखा जा सकता है
- Go 1.18 से generic functions और generic types संभव हैं, लेकिन methods के अपने type parameters अब भी नहीं जोड़े गए हैं
- Rust में
(0..100).filter(|n| ...).collect() जैसी iterator chain, Go developers को अजीब लग सकती है, लेकिन Rust में for loop भी इस्तेमाल किया जा सकता है और one-off code में यह अक्सर सही विकल्प होता है
निष्कर्ष
- Go से Rust में जाना, Python या TypeScript से Rust में जाने जैसा नहीं है
- Go background वाले developers पहले से ही static typing और compiled language के फ़ायदे जानते हैं, इसलिए यह dynamic typing या धीमे runtime को छोड़ने वाला बदलाव नहीं है
- मुख्य trade-off यह है कि
nil छोड़कर ज़्यादा robust codebase, कम pitfalls, और ऐसा सख़्त compiler मिलता है जो compile time पर ज़्यादा गलतियाँ पकड़ता है
- बदले में learning curve ज़्यादा steep है
- foundational services जैसी वे services, जिन पर संगठन निर्भर करता है, जिनमें high uptime चाहिए, और जो business-critical हैं, वहाँ यह trade-off साफ़ तौर पर सार्थक है
- दूसरी services में Go अब भी सही जवाब हो सकता है
- migration का उद्देश्य हर समस्या को उस भाषा में रखना है जो उसे सबसे बेहतर ढंग से हल करती है
1 टिप्पणियां
Hacker News की राय
C/C++ या Python से Rust पर जाना कई वजहों से समझ में आता है, लेकिन web backend के लिए Go एक बेहतर फिट लगता है
मैं लगभग सिर्फ Rust ही इस्तेमाल करता हूँ, लेकिन आख़िरी बार जब मैंने Rust में web server साइड का काम किया था, तब लगा था कि Go इस्तेमाल करना चाहिए था
मूल लेख में कहा गया है कि Go की error handling syntax बहुत verbose है, और यह सही बात है। Rust में भी यही समस्या थी, फिर उसने
?syntax जोड़ा जो error होने पर error value return कर देता है। Go की error handling ज़्यादातर इसी का खुला हुआ रूप हैRust में कोई एकीकृत error type नहीं है, और
io::Error,thiserror,anyhowजैसी प्रमुख error systems हैं, इसलिए call chain के ऊपर errors propagate करना झंझट बन जाता हैकुछ चीज़ें ऐसी होती हैं जिन्हें नई language में शुरू में छोड़ दिया जाए तो बाद में जोड़ना मुश्किल होता है। जैसे constant types, boolean types, error types, multidimensional array types, 2/3/4-size vector·matrix types और standard operations। अगर इन्हें शुरुआत में standardize न किया जाए, तो एक ही concept की कई representations को मिलाने में बहुत समय जाता है
error handling को छोड़ दें तो web development में इसका असर कम है, लेकिन numerical computing, graphics, modeling में यह बहुत बड़ा दर्द बन जाता है क्योंकि वहाँ number arrays पर standard operations चाहिए होते हैं
web services में Go के दो बड़े फ़ायदे हैं। पहला है, जैसा मूल लेख कहता है, goroutines, और दूसरा है libraries, जिस पर मूल लेख ने कम बात की। Go में web service के लिए ज़रूरी लगभग सभी libraries मौजूद हैं, और उनमें से कई Google के अंदर भी इस्तेमाल होती हैं, इसलिए वे बेहद कठिन माहौल झेल चुकी हैं। इसके उलट Rust crates अक्सर कम mature होते हैं और कई बार उनमें official quality assurance नहीं होती
और Rust अब भी Go की तुलना में बहुत-सी C/C++ libraries पर निर्भर करता है, इसलिए cross-compilation, reproducible builds, और static binary बनाना अक्सर समस्या बन जाता है
Go की कमी यह है कि उसका garbage collector बहुत सरल है। अगर latency spikes आने लगें, तो दर्दनाक rewrite के अलावा ज़्यादा उपाय नहीं बचते
जिन चीज़ों का नाम लिया गया है, वे बस उसे इस्तेमाल करने के आम तरीके हैं, और सिर्फ
Boxइस्तेमाल करने पर भी कोई समस्या नहीं होती। यह काफ़ी हद तकanyhow::Errorजैसा ही हैफिर भी standard library के मामले में मुझे लगता है कि Go ने Rust से कहीं बेहतर काम किया है
मुझे Rust language पसंद है और मैं इसे embedded firmware और PC applications में इस्तेमाल करता हूँ, लेकिन web backend के लिए अब भी Python इस्तेमाल करता हूँ। वजह यह है कि Rust में Django या Rails स्तर का tool bundle नहीं है
Flask जैसा कुछ है, लेकिन Flask जैसा मज़बूत ecosystem नहीं है। Go का अनुभव कम है, लेकिन web backend के लिए मैं Rust की बजाय Go चुनूँगा। कारण है library और framework ecosystem
और आम तौर पर बताए जाने वाले कारणों से मुझे Async Rust भी ज़्यादा पसंद नहीं है। Rust का web ecosystem लगभग पूरी तरह async usage पर निर्भर है
io::Errorबस उन कई types में से एक है जो इसे implement करते हैं, इसमें कुछ खास नहीं।thiserrorसे define किए गए errors भी इसी trait को implement करते हैंanyhowबस तब सुविधा देता है जब आप API contract में यह विस्तार से नहीं लिखना चाहते कि function कौन-से errors फेंक सकता है, और बस “कोई भी Error” कहना चाहते हैंRust में Go की तुलना में code को deterministic बनाना आसान है, इसलिए deterministic simulation tests और property-based tests की ज़रूरत हो तो यह बहुत उपयोगी है
मैंने हाल में Go में Postgres-to-Iceberg data mirroring tool https://github.com/polynya-dev/pg2iceberg लिखा, लेकिन Go runtime से लड़े बिना deterministic simulation tests करना चाहता था, इसलिए उसे Rust में port किया
लेकिन अगर वह domain इतने भारी testing को justify करने जितना महत्वपूर्ण न हो, तो मैं हमेशा Rust के बजाय Go चुनूँगा
संबंधित लेख: https://www.polarsignals.com/blog/posts/2024/05/28/mostly-ds...
यह बात घिसी-पिटी और दोहराई हुई लग सकती है, लेकिन Rust को लेकर मेरी सबसे बड़ी शिकायत package management की स्थिति है, और मेरे हिसाब से यह पूरी तरह developer mindset का नतीजा है
Rust की usability मुझे पसंद है। data types के लिए functional approach बहुत सुंदर है। लेकिन अभी मैं एक Rust project और एक Go project साथ-साथ कर रहा हूँ, और dependency tree पूरी तरह अलग किस्म का जानवर है
Go project ज़्यादातर standard library से काम चला लेता है, लेकिन Rust project में सिर्फ
rusqlite(sqlite),clap(CLI),ratatui(TUI),tauri(GUI) माँगे, फिर भी dependencies 400 से ज़्यादा लगती हैं। खास तौर परtauriसबसे बड़ा कारण है, और उसे हटा दें तो भी लगभग 100 रहती हैं, जो पागलपन जैसा लगता हैअगर dependencies को समझदारी से संभालने वाले, अच्छी तरह maintained Rust crate alternatives मिल जाएँ तो काफ़ी बेहतर हो, लेकिन अभी तक नहीं मिले। मैं बस अपने system में shai hulud नहीं लाना चाहता, लेकिन Rust web दुनिया के लोग इस मामले में
cargoकोnpmजैसा बनाना चाहते हैंइसलिए dependency count असलियत से बड़ा दिखता है। अलग crates होने पर भी अक्सर maintainer वही होता है और वे उसी upstream Git repository का हिस्सा होते हैं
फिर भी कुल मिलाकर भावना से सहमत हूँ। Rust में 0.x versions पर आधे-अधूरे छोड़े गए crates भी बहुत हैं, और कई बार बेहतर alternatives नहीं मिलते
फिर उसके बाद
httplib3के बादhttplib4आ जाता हैयानी मैं Rust का approach कहीं ज़्यादा पसंद करता हूँ। standard library पर निर्भर रहो या किसी और dependency पर, मेरे लिए बड़ा फ़र्क नहीं है। आख़िर वह dependency ही है
यह सोचना कि सिर्फ standard library में होने से quality बेहतर होगी या maintenance बेहतर होगा, एक अलग बात है
आख़िर में सब resources पर निर्भर करता है। हाँ, standard library को ज़्यादा resources मिल सकते हैं, लेकिन उल्टा वह bloated और unmaintainable भी हो सकती है
rusqlite,clap,ratatui,tauriजैसी चीज़ें standard library में देने वाली language शायद Java को छोड़कर और कोई नहीं हैऔर यह भी देखना चाहिए कि Tauri खुद 14 crates से बना है, और उनमें से हर एक build tree में दिखाई देता है
https://github.com/tauri-apps/tauri/blob/dev/Cargo.toml
Ratatui भी 6 हैं
https://github.com/ratatui/ratatui/blob/main/Cargo.toml
किसी ने भी इसे “solve” नहीं किया है, और मुझे नहीं लगता कि आगे भी एक ही समाधान निकलेगा
Go में आपको भरोसा करना पड़ता है कि library developer semantic versioning सही ढंग से मानेगा, और version pin नहीं किया जा सकता। यह बात मुझे निजी तौर पर काफ़ी खटकती है
कुछ workaround हैं। Git commit hash जैसे SHA का इस्तेमाल करके pseudo-version बना सकते हैं, या known dependency cache यानी vendoring इस्तेमाल कर सकते हैं। लेकिन vendoring के साथ cache management की समस्या आती है
पिछले weekend मुझे Python virtual environment इस्तेमाल करना पड़ा, और अनुभव अच्छा नहीं रहा; इससे फिर याद आया कि मैं Python से दूर क्यों गया था
Perl का CPAN, Java का Maven/Gradle, Ruby gems, Go का dep/glide/vgo/modules, Rust का Cargo, Node का npm/yarn — सब में मिलती-जुलती समस्याएँ हैं
operating systems भी Redhat का yum/rpm, Debian का apt, Ubuntu का snap जैसे ही हैं। खासकर snap आखिर ऐसा क्यों है, समझ नहीं आता
शायद इस use case में frontend को Go में ही रहने देना और backend को Rust में करना समझदारी हो सकती है
यह दस्तावेज़ एक migration guide भी बनना चाहता है और साथ ही Rust advocacy document भी, इसलिए थोड़ा अजीब लगता है
आख़िर में अगर आप Rust और Go के बीच सोच रहे हैं, तो मूल सवाल लगभग पूरी तरह इस पर आ टिकता है: “क्या आप managed runtime चाहते हैं?” Rust programmers की एक पीढ़ी ने खुद को यह समझाया है कि managed runtime बुरी चीज़ है, और उसका न होना एक ज़रूरी feature है
लेकिन यह साफ़ तौर पर ग़लत है। programming के ज़्यादा क्षेत्र ऐसे हैं जहाँ managed runtime चाहिए, उन क्षेत्रों से ज़्यादा जहाँ वह नहीं चाहिए
इसका मतलब यह नहीं कि हर ऐसे मामले में Go default choice हो। Rust को पसंद करने की subjective वजहें भी बहुत हैं। Go इस्तेमाल करते समय मुझे
matchकी कमी महसूस होती है, लेकिनtokioऔर Async Rust की नहींदोनों ही लगभग हर उस स्थिति में वैध विकल्प हैं जहाँ problem space को जबरन मोड़ने की ज़रूरत न पड़े। जैसे Go में Linux kernel module लिखना अजीब चुनाव होगा
Rust बनाम Go की लड़ाई हमारे क्षेत्र का थोड़ा अजीब और शर्मनाक किनारा लगती है। industry का बड़ा हिस्सा Python या Node से पूरे systems ठीक-ठाक बना रहा है, और यह देखकर हँस रहा है कि कुछ nerds इस पर झगड़ रहे हैं कि कौन-सी statically typed compiled language चुननी चाहिए। असली सवाल Python बनाम Rust/Go है, Rust बनाम Go नहीं
लेकिन कुल मिलाकर Rust और Go वालों को dynamic typing की बुराई के ख़िलाफ़ साथ आना चाहिए। अगर type hints अब best practice माने जाते हैं, तो क्या यह असल में यह स्वीकार नहीं कि पहले इसमें कमी थी?
अच्छे type hints भी type inference से कमतर हैं। type inference आपको type बदलने पर बहुत-सा code जस का तस रहने देता है, और साथ ही अनचाहे type changes को रोकता है
काश TS में थोड़ा और runtime भी होता। Python की जो एक चीज़ मुझे ईर्ष्या देती है, वह यह है कि HTTP endpoints पर JSON schema validation बहुत सहजता से हो जाती है
Zod से होकर गुजरने वाली प्रक्रिया लगातार झुंझलाहट का स्रोत है, और मेरे हिसाब से यह TS team के dogmatic रवैये से पैदा हुई समस्या है
LLM writing के निशान अब ज़्यादा subtle हो गए हैं, लेकिन अभी भी साफ़ दिख जाते हैं। खास तौर पर genuine शब्द ऐसा है
जैसे “This is the area where Go genuinely shines, and it’s worth being precise about why”, “the lack of GC pauses is a genuine selling point”, “Humans are genuinely bad at reasoning about memory”, “There are cases where the borrow checker is genuinely too strict”
मुझे नहीं लगता कि पूरा लेख AI-generated है, लेकिन शायद AI-assisted है। अगर ऐसा है, तो लेखक ने genuinely अच्छा काम किया है
यह देखकर कि दूसरे लोग इसका ज़िक्र नहीं कर रहे, लगता है कि इससे सामग्री को बहुत नुकसान नहीं हुआ, लेकिन इसका धीरे-धीरे आम और detect करना कठिन होना अजीब लगता है
“Go is clearly working for a lot of people,” तक पढ़ते-पढ़ते मुझे AI assistance का शक होने लगा था। बेशक ऐसा न भी हो सकता है, और मैं पहचानने में बहुत अच्छा नहीं हूँ
किसी ठोस संकेत से ज़्यादा यह विडंबना से feeling जैसा है। अगर कोई लेख AI-assisted “लगता” है, तो लेख अच्छा होने पर भी मेरी दिलचस्पी तुरंत घट जाती है
काश लोग अपने विचार उसी तरह सीधे लिखने में ज़्यादा सहज हों, जैसे वे उनके मन में आते हैं
it's worth being precise about ...genuine के इस्तेमाल से कहीं ज़्यादा AI-जैसा वाक्यांश हैउदाहरण के लिए यह paragraph देखिए: “Go got generics in 1.18, and they’re useful, but the implementation has constraints (no methods with type parameters, GC shape stenciling, occasional surprising performance characteristics). Rust generics monomorphize, each instantiation produces specialized code with zero runtime cost. Combined with traits, this gives you real zero-cost abstractions.”
हर वाक्य कुछ कह रहा है, हर वाक्य महत्वपूर्ण है, और हर वाक्य अपना हिस्सा निभा रहा है। ऐसा लेखन blog post की बजाय किसी बहुत professional किताब या paper में मिलने की उम्मीद होती है
और इसी वजह से इसे पढ़ना और मुश्किल और उबाऊ हो जाता है
मुझे उम्मीद नहीं कि LLM-generated text घिसे-पिटे phrases से भरा नहीं होगा। बस इच्छा है कि हम सब थोड़ी बेहतर editing sense दिखाएँ ताकि बार-बार वही आवाज़ न पढ़नी पड़े
अगर नया project है, तो आराम से Rust में लिखिए
लेकिन अगर existing code मौजूद है, वह काम कर रहा है, और revenue ला रहा है, तो सिर्फ उन्हीं हिस्सों को उसी मूल language में फिर से लिखकर आगे बढ़ना चाहिए जिन्हें सच में rewrite की ज़रूरत है
जिस language को आप जानते हैं और जिस team पर भरोसा है, उसके साथ system को छोटे और measurable तरीक़े से बेहतर बनाइए। इसके अलावा बाकी सब फ़िज़ूल धार्मिक बहस है
benchmark चलाने से पहले भी मुझे Rust पसंद था, लेकिन ज़्यादातर LLMs के लिए Rust और Go लिखने की efficiency का फ़र्क मेरी उम्मीद से कहीं बड़ा निकला। खासकर agentic harness में, जहाँ शुरुआती environment issues ठीक किए जा सकते हैं, यह और भी ज़्यादा दिखा
यह देखकर मैं काफ़ी मज़बूत Rust समर्थक बन गया। मैंने existing codebase से call होने वाले batch processing tools Rust में लिखकर अच्छे नतीजे पाए, लेकिन पूरी production migration अभी नहीं की
लेख में बताए Go के issues, खासकर
nilhandling से जुड़े, मुझे लगता है Codex से thorough code review करवाने पर धीरे-धीरे कम हो रहे हैं। शुरुआत से समस्या न हो तो और अच्छा है, लेकिन जो developers design और implementation जितनी मेहनत review और understanding पर भी लगाते हैं, उनके लिए ऐसे security bugs अब optional होते जा रहे हैंlanguage data यहाँ है: https://gertlabs.com/rankings?mode=agentic_coding
Rust उपयोगकर्ता को काफ़ी मजबूती से एक तय पथ पर रखता है। Codex किसी-न-किसी तरह हमेशा कुछ compile होने लायक बना देता है
downside यह है कि कभी-कभी जहाँ failure होना चाहिए क्योंकि idiomatic approach संभव नहीं, वहाँ भी यह compile होने वाला और request पूरा करने वाला कोई बेवकूफ़ implementation बना सकता है
LLM इंसानों से तेज़ code लिखते हैं, इसलिए उनकी तुलना में compile का इंतज़ार proportionally और बड़ा हो जाता है। 100k lines से ऊपर जैसे कुछ बड़े projects में Rust का लगभग 10x धीमा compile bottleneck बनना शुरू हो जाता है
अगर आप core infrastructure लिख रहे हैं, तो शायद यह लागत वाजिब हो, लेकिन अगर आप ऐसा internal service बना रहे हैं जो internet पर public नहीं है, तो development speed बड़ी चिंता हो सकती है
मेरा मानना है कि slow compile इंसानी development speed को भी प्रभावित करता है, लेकिन अजीब बात है कि developers बहुत कम ही इसे quantify करने की कोशिश करते हैं
अगर verbosity मुख्य अड़चन है, तो Go 1.28 में आने वाली यह चीज़ उसे काफ़ी कम कर सकती है
https://github.com/golang/go/issues/12854#issue-110104883
“वह सेवा जिस पर संगठन निर्भर करता है, जिसे high uptime चाहिए, और जो business-critical है” — यह अभिव्यक्ति मज़ेदार लगी
खासकर तब जब वह Rust service Kubernetes पर चल रही हो
मैं पहले से Rust इस्तेमाल करता हूँ और Go का अनुभव नहीं है, इसलिए हो सकता है यह लेख मेरे लिए पूरी तरह उपयुक्त न हो
लेकिन एक बात खटकती है। यह कहना कि Rust में data races “compile time पर पकड़ लिए जाते हैं”, कम-से-कम थोड़ा बढ़ा-चढ़ाकर कहना लगता है
यह अभिव्यक्ति ऐसा प्रभाव दे सकती है कि Rust lock starvation जैसी चीज़ों या दूसरे concurrency issues को भी संभाल सकता है। वास्तव में ऐसा नहीं है
मैं जानता हूँ कि data race एक औपचारिक शब्द है जिसका दायरा सीमित है, लेकिन फिर भी मुझे लगता है इसे और स्पष्ट लिखा जा सकता है