1 puan yazan GN⁺ 3 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Go’dan Rust’a geçiş, hız artışından çok nil, hata işleme, veri yarışı ve kaynak ömrü sorunlarını derleme zamanı güvencelerine taşıma tercihi olarak görülebilir
  • Go; hızlı derleme, basit goroutine yapısı ve güçlü backend ekosistemiyle öne çıkarken, Rust Option, Result, Send/Sync ile daha fazla hatayı tür sistemi düzeyinde engeller
  • Rust’ın borrow checker’ı ile async/await, öğrenme eğrisi ve kullanılabilirlik maliyeti yaratır; derleme süreleri de Go’ya kıyasla açık bir gerileme olarak kabul edilmelidir
  • Geçişte, tüm sistemi baştan yazmak yerine hot path servisleri, worker’lar ve gateway arkasındaki belirli endpoint’ler gibi sınırları net bileşenlerden başlamak daha uygun bir stratejidir
  • Beklenen kazanımlar; CPU kullanımında %20–60 düşüş, bellekte %30–50 azalma, daha düz bir P99 gecikme eğrisi ve nil dereference ile race kaynaklı arızalarda azalma olarak özetlenebilir

Geçişin odağı

  • Go’dan Rust’a geçiş, “Rust daha mı hızlı?” sorusundan çok doğruluk garantileri, çalışma zamanı tavizleri ve geliştirici deneyimi farklarını değerlendirme meselesidir
  • Karşılaştırmanın odağında backend servisleri yer alır; temel referans olarak da Go’nun güçlü olduğu küçük statik binary’ler, ağ odaklı standart kütüphane ve HTTP sunucusu, gRPC, veritabanı ekosistemi alınır
  • Bazı noktalar CLI araçları, gömülü firmware ve oyun motorları için de geçerli olabilir, ancak bunlar optimize edilmiş hedefler değildir
  • İlgili arka plan kaynakları olarak 2017 tarihli “Go vs Rust? Choose Go.” ve Shuttle ekibinin “Rust vs Go: A Hands-On Comparison” yazıları sunuluyor
  • Go başarılı bir dil olsa da, nil’in yaygın kullanımı, türler yerine disipline dayanan hata işleme yaklaşımı ve uzun süre generics içermemesi gibi tasarım tercihleri, Rust ile karşılaştırmada başlıca tartışma noktalarıdır
  • JetBrains Developer Ecosystem Survey’e göre Go, çalışan geliştiriciler arasında %17–19 bandını koruyan bir dil olarak öne çıkarken, Rust istikrarlı biçimde büyüse de daha küçük bir paya sahiptir

Araç zinciri

  • Hem Go hem de Rust; build, test, formatlama, lint ve bağımlılık yönetimini tutarlı bir arayüzle sunan batteries-included araç zincirlerine sahiptir
  • cargo, Go araçlarıyla eşleşen işlevleri birinci sınıf araç olarak daha geniş kapsamda sunar
    • go.mod / go.sumCargo.toml / Cargo.lock: proje yapılandırması ve bağımlılık manifestosu
    • go get / go mod tidycargo add / cargo update: bağımlılık ekleme ve çözümleme
    • go buildcargo build: derleme
    • go run .cargo run: derleyip çalıştırma
    • go test ./...cargo test: test
    • go vet ./...cargo clippy: linter; Clippy, vet’e göre çok daha güçlü görüşlere sahiptir
    • gofmt / goimportscargo fmt: yapılandırmasız otomatik biçimlendirici
    • golangci-lint runcargo clippy -- -D warnings: katı lint modu
    • go doccargo doc --open: API dokümantasyonu üretme ve görüntüleme
    • pprofcargo flamegraph / samply: CPU profilleme
    • govulncheckcargo audit: tavsiye veritabanı tabanlı güvenlik açığı taraması
  • Go tarafında boşluklar çoğu zaman golangci-lint, mockgen, air, goreleaser gibi üçüncü taraf araçlarla kapatılırken, Rust birinci taraf ekosisteminde daha fazla işlevi varsayılan olarak kapsar
  • Harici crate gerektiğinde bile, cargo watch ve cargo nextest gibi araçlar cargo install cargo-nextest ile tek seferde kurulup cargo nextest gibi yerel araç hissiyle çalışır
  • gofmt ile rustfmt’in en büyük avantajı, ince stil tercihlerinden çok kod incelemelerinde stil tartışmalarını ortadan kaldırmalarıdır
    • Rob Pike’ın Go Proverbs alıntısı: “Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.”

Go ve Rust arasındaki temel farklar

  • Her iki dil de derlenen dil, statik türler, tek binary olarak dağıtım ve güçlü eşzamanlılık modelleri sunar; ancak fark, derleyicinin garanti ettiği kapsam ve çalışma zamanı davranışı üzerindeki kontrol düzeyindedir
  • Başlıca karşılaştırma başlıkları şunlardır
    • Kararlı sürüm: Go 2012, Rust 2015
    • Tür sistemi: Go statik ve yapısal türlemeye sahiptir, 1.18’den beri generics destekler; Rust statik ve nominal türlemeye sahiptir, generics, trait ve lifetime desteği sunar
    • Bellek yönetimi: Go eşzamanlı ve düşük gecikmeli garbage collector kullanır; Rust sahiplik ve borrowing temellidir, GC yoktur
    • Null güvenliği: Go’da nil yaygındır; Rust’ta null yoktur ve tür düzeyindeki karşılığı Option<T>’dir
    • Hata işleme: Go error arayüzü ve if err != nil { ... } kullanır; Rust Result<T, E>, ? operatörü ve tam desen eşleme sunar
    • Eşzamanlılık: Go goroutine ve kanal tabanlı CSP yaklaşımını kullanır; Rust tokio üzerinde async/await, kanallar ve thread’ler kullanır
    • İptal: Go’da gelenek temelli context.Context kullanılır; Rust’ta CancellationToken gibi açık ve tür denetimli aktarım yöntemleri vardır
    • Veri yarışı: Go -race ile çalışma zamanında olasılıksal tespit yapar; Rust Send/Sync ile derleme zamanında tespit eder
    • Derleme süresi: Go çok hızlıdır; Rust ise özellikle temiz build’lerde yavaştır
    • Çalışma zamanı: Go yaklaşık 2MB’lik runtime ve GC içerir; Rust’ta libc dışında runtime olmayabilir veya MUSL ile tamamen statik build mümkündür
    • Ekosistem ölçeği: Go yaklaşık 750 bin+ modül, Rust ise 250 bin+ crate düzeyindedir
  • Go’da geleneklere, araçlara ve çalışma zamanı tespitine bırakılan nil işleme, hata yayılımı, veri yarışı, kaynak ömrü, iptal ve generics gibi denetimler, Rust’ta tür sisteminin içine alınır
  • Rust’ın Mutex<T> yapısı, iç değere yalnızca .lock() ile alınan guard üzerinden erişilmesini sağlayarak “kilidin unutulduğu yol” ihtimalini tür düzeyinde ortadan kaldırır
  • Aynı yaklaşım Option, Result, &mut T, Send/Sync ve RAII guard kalıplarında da tekrar eder; buna alışıldığında derleyici, zihinsel kontrol listesinin bir kısmını geliştiricinin yerine üstlenir

Rust’ı değerlendirmeye yönelten Go sınırlamaları

  • Go, çoğu backend iş yükü için yeterince hızlı olduğu için, Rust’ı değerlendirmenin başlıca nedeni hızdan çok hata işlemenin ayrıntılı olması, nil pointer riski ve enum·trait gibi gelişmiş tür sistemi özelliklerinin eksikliğine daha yakındır
  • Go interface’leri, Rust trait’lerinin yeterli bir karşılığı değildir ve standart kütüphanede Set tipi olmadığından map[T]struct{} gibi deyimsel dolaylı çözümler gerekir
  • Prodüksiyonda nil panic’i

    • Go servisleri aylarca normal çalıştıktan sonra belirli bir kod yolunda pointer için nil kontrolü atlandığı için goroutine panic’i üretebilir
    • Örnekte Find, (*User, error) döndürür ve “not found” durumunda error nil olsa da user kontrolü çağırana bırakılır
    • user.Account.Notify(), user veya Account nil olduğunda çökebilir
    • nilaway, staticcheck gibi linter’lar ve IDE kontrolleri bazılarını yakalasa da, bunlar opt-in’dir, olasılıksaldır ve paket sınırlarını güvenilir biçimde aşamaz
    • Rust’taki Option<T>, None durumunu ele almadan dereference etmeyi engelleyerek bu arıza kategorisini ortadan kaldırır
  • -race’in yakalayamadığı data race

    • go test -race harika bir araçtır, ancak bir runtime dedektörü olduğu için yalnızca test sırasında gerçekten yürütülen race’leri bulur
    • Go’da iki goroutine’in lock olmadan bir map’i değiştirdiği kod da derlenir ve yük altındaki prodüksiyonda patlayabilir
    • Rust’ta thread’ler arasında mutable state paylaşımı için Send ve Sync implement eden tiplere ihtiyaç vardır; sıradan bir HashMap’i thread’ler arasında paylaşmaya çalışırsanız kod derlenmez
    • Arc<Mutex<...>>, Arc<RwLock<...>> veya channel kullanmanız zorunlu hale gelir ve race condition bir tür hatasına dönüşür
    • Paul Dix, InfluxDB 3.0 yeniden yazımının gerekçesi olarak data race’lerin ortadan kaldırılmasını doğrudan anıyor
      • “[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.”
      • Kaynak: Paul Dix, Founder & CTO, InfluxData, Rust in Production
  • Birleştirilebilir hata işleme

    • Go’daki if err != nil { return err }, fonksiyonun gerçek mantığını sulandırabilir ve bağlamı fmt.Errorf("doing X: %w", err) ile sarmak, derleyici kuralından çok disipline dayanır
    • Lobste.rs başlığında deneyimli Go geliştiricileri, errcheck ve golangci-lint’in eksik hata işlemenin büyük kısmını yakaladığını ve açık if err != nil yapısının yoğun ? zincirlerinden daha okunabilir olduğunu söyleyerek karşı çıkıyor
    • Peter Bourgon, Go’nun açık hata işlemesini dilin kasıtlı bir kültürel değeri olarak sunuyor
      • “I think that error handling should be explicit, this should be a core value of the language.”
      • Kaynak: Peter Bourgon, GoTime #91, Dave Cheney’nin Zen of Go yazısında alıntılanıyor
    • Rust’ın Result<T, E> yapısı tür imzasının kendisidir, bu yüzden gözden kaçırılamaz; thiserror::Error ile tanımlanan enum ve #[from] sayesinde hata dönüşümü ve tamlık kontrolü sağlanır
    • Yeni bir hata variant’ı eklediğinizde derleyici, güncellenmesi gereken match konumlarını size bildirir
  • Box’lama yapmayan generics

    • Go 1.18 generics yararlıdır, ancak tür parametreli method eksikliği, GC shape stenciling ve zaman zaman şaşırtıcı performans özellikleri gibi kısıtları vardır
    • Rust generics monomorphized edilir; böylece her somutlama özelleştirilmiş kod üretir ve runtime maliyeti oluşmaz
    • Trait’lerle birleştiğinde sıfır maliyetli soyutlama mümkün olur
    • Bu, handler kodundan çok middleware, generic repository, decoder, parser gibi paylaşılan altyapıda daha önemlidir; Go ise bu alanlarda sık sık interface{}/any ve type assertion’a geri döner
  • Öngörülebilir gecikme süresi

    • Go’nun GC’si mükemmeldir; eşzamanlıdır, düşük gecikmelidir ve genel servis iş yükleri için iyi ayarlanmıştır, ancak “low-pause”, “no-pause” anlamına gelmez
    • Tahsisin yoğun olduğu durumlarda, sıcak yolda tahsis yapmayan Rust implementasyonuna kıyasla P99 gecikme kuyruğu daha kötü olabilir
    • Trading, gerçek zamanlı teklif verme, ağ proxy’leri ve yüksek throughput’lu veri toplama gibi gecikmeye duyarlı sistemlerde GC pause olmaması gerçek bir avantajdır
    • Stephen Blum, PubNub ölçeğinde ihtiyaç duydukları fiyat/performans kapasitesini elde etmek için Rust’a ihtiyaçları olduğunu söylüyor
      • “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.”
      • Kaynak: Stephen Blum, CTO, PubNub, Rust in Production

Rust’ta Go kalıplarının karşılıkları

  • Rust’a alışmanın en hızlı yolu, zaten bildiğiniz Go kalıplarını Rust’taki karşılık gelen kalıplarla eşleştirmektir
  • Aynı backend servisini iki dilde uygulayan daha uzun bir örnek için Shuttle comparison yazısına bakabilirsiniz
  • Hata işleme: if err != nil vs Result<T, E>

    • Go, os.ReadFile(path) ve json.Unmarshal sonrasında if err != nil ile bağlam eklenmiş hatayı döndürür
    • Rust tarafı fs::read_to_string(path)?, serde_json::from_str(&data)?, Ok(cfg) yapısından oluşur
    • ? operatörü if err != nil { return err } kalıbının yerini alır ve From<E1> for E2 uygulanmışsa tip dönüşümünü de yapar
    • thiserror içindeki #[from], bu dönüşümü idiomatik şekilde destekler
  • Null: nil vs Option<T>

    • Go’daki GetUser(id string) *User, kullanıcı bulunamazsa nil döndürür; çağıran taraf fmt.Println(u.Name) yaparsa nil durumunda panic oluşur
    • Rust’taki get_user(id: &str) -> Option<User>, Some(User) veya None döndürür
    • let user = get_user("123"); println!("{}", user.name); ifadesi, user bir User değil Option<User> olduğu için derleme hatası verir
    • match get_user("123") ile hem Some(u) hem de None durumlarını ele almanız gerekir
    • Güvenli Rust’ta nil yoktur ve referanslar null olamaz
  • Arayüzler vs trait’ler

    • Go arayüzleri yapısaldır; bir tip, arayüzü örtük olarak karşılar
    • Rust trait’leri nominaldir; açıkça implemente edilmeleri gerekir
    • Go yaklaşımı anlık duck typing için iyidir; Rust yaklaşımı ise refactoring ve discoverability için daha iyidir, ayrıca belirli bir trait implementasyonunu grep ile bulabilirsiniz
    • fn handle<R: Reader>(r: R) gibi trait bound eklenmiş generic function’lar çoğu durumu kapsar ve monomorphization sayesinde runtime dispatch yoktur
    • Runtime dispatch gerektiren heterojen implementasyonları saklamak için Box<dyn Trait> veya Arc<dyn Trait> kullanılır
  • Goroutine vs async task

    • Go’nun eşzamanlılık modeli go doWork(ctx, input) örneğinde olduğu gibi basittir; goroutine’ler hafiftir ve runtime bunları OS thread’leri üzerinde zamanlar
    • Go’nun büyük avantajlarından biri, sıralı kod ile paralel kod arasında sözdizimsel bir ayrım olmamasıdır
    • Rust, backend servislerinde neredeyse her zaman tokio executor üzerindeki async/await yapısını kullanır
    • async fonksiyonlar Future döndürür ve await edilmeden ya da spawn edilmeden çalışmaz
    • Derleyici, .await noktalarının öncesi ve sonrasında Send/Sync durumunu izler; non-Send bir değeri await sonrasına taşırsanız derleme hatası verir
    • Yerleşik goroutine tarzı preemption olmadığından, CPU-bound işleri async task içinde uzun süre çalıştırmak executor’ü aç bırakabilir; bunları tokio::task::spawn_blocking veya rayon ile devretmeniz gerekir
  • context.Context vs CancellationToken

    • Go’da her blocking call’a context.Context geçirilir
    • Rust’ta yerleşik bir context.Context yoktur; iptal için en yakın karşılık tokio_util::sync::CancellationToken’dır
    • timeout için future, tokio::time::timeout(dur, fut) ile sarılır
    • deadline ve değerler, tek bir context nesnesi yerine çoğu zaman açık parametrelerle veya tracing span’ları üzerinden aktarılır
    • Dave Cheney’nin The Zen of Go yazısından alıntı:
      • “Go’nun bir goroutine’e çıkmasını söyleyen bir yolu yoktur. İyi bir nedenle, stop veya kill fonksiyonu yoktur. Bir goroutine’e durmasını emredemiyorsak, onun yerine kibarca istememiz gerekir.”
    • Go’da bu “kibar rica” genellikle context.Context ile taşınır; Rust’ta ise CancellationToken veya watch kanalıyla yapılır, ancak derleyici eksiklikleri size bildirebilir
  • Dizgeler: string vs String ve &str

    • Go’daki string, UTF-8 byte slice’tır; atama sırasında header kopyalanır ve alttaki byte’lar paylaşılan, değiştirilemez bir yapıda kalır
    • Rust bunu iki tipe ayırır
      • String: sahip olunan, heap üzerinde ayrılmış ve büyüyebilen tür
      • &str: başka bir string verisine ödünç alınmış bir görünüm; çoğu durumda Go’daki string parametresine karşılık gelir
    • Pratik kural, argüman olarak &str almak ve yeni veri üretiliyorsa String döndürmektir
    • &str ve String ayrımı, Rust’ın “borrow vs own” modelinin küçültülmüş bir yansımasıdır

Go jenerikleri üzerine değerlendirme

  • Go, 1.18 ile Mart 2022’de jenerikleri kullanıma sundu; bu da dilin çıkışından 13 yıl sonra oldu
  • Jenerikler faydalı olsa da Rust, Haskell ve modern C++’ta beklenen avantajları tam olarak sunmadığı, buna karşılık jenerik tip sistemlerinin önemli bir kısmındaki dezavantajları da beraberinde getirdiği değerlendiriliyor
  • Standart kütüphane neredeyse hiç kullanmıyor

    • Jeneriklerin gelmesinden 3 yıl sonra bile Go standart kütüphanesi büyük ölçüde jenerikleri kullanmaktan kaçınıyor
    • sort.Slice, hâlâ cmp.Ordered constraint yerine func(i, j int) bool closure alıyor
    • sync.Map, hâlâ any/any olarak tiplendiriliyor
    • Mevcut generic helper’lar, slices, maps, cmp, sync altındaki bazı öğeler gibi az sayıdaki pakette bulunuyor
    • Go 1 uyumluluk sözü nedeniyle mevcut non-generic API’leri dönüştürmenin zor olması bunu kısmen açıklasa da, Rust’taki gibi jenerikler ana araç olarak kullanılmıyor
    • Rust’ta ise en baştan beri Option<T>, Result<T, E>, Vec<T>, HashMap<K, V>, Iterator, From/Into, tüm koleksiyonlar ve akıllı işaretçiler jeneriklerle iç içe
  • Trait sistemi yok ve yalnızca yapısal constraint’ler var

    • Rust jenerikleri; ad-hoc çok biçimlilik, supertrait, associated type, blanket impl ve coherence sağlayan trait’lerle birlikte geliyor
    • Go constraint’leri, type-set membership için ~ operatörü eklenmiş interface’lere daha yakın
    • Go’da Rust’taki trait Ord: Eq + PartialOrd gibi bir supertrait hiyerarşisi, Iterator içindeki type Item; benzeri associated type ya da impl<T: Display> ToString for T türü blanket impl yok
    • Go’da tip parametreli metotlar kullanılamadığı için func (s Set[T]) Map[U](<https://corrode.dev/learn/migration-guides/go-to-rust/f func(T>) U) Set[U] gibi bir biçim mümkün değil
    • Soyutlama, “birkaç işlemi olan rastgele bir T üzerinde çalışan fonksiyon” seviyesini aştığında, Go yeniden any, tip doğrulaması, kod üretimi ve çalışma zamanı reflection’a dönüyor
  • Tip çıkarımı ve uygulama stratejisindeki farklar

    • Rust, closure, iterator chain ve ? operatörü dahil tüm expression boyunca tip bilgisini yayabiliyor
    • Go’nun tip çıkarımı daha sığ; genelde fonksiyon argümanlarından type parameter çıkarıyor ama return-position context içinde çıkarım yapamıyor ve çağrı noktasında sık sık açık type argument gerektiriyor
    • Go, hızlı derleme sürelerini korumak için GCShape stenciling and dictionaries adlı orta yolu seçiyor, ancak type parameter üzerindeki her metot çağrısında dolaylı erişim eklenebiliyor
    • Bunu gösteren kaynak olarak PlanetScale yazısı sunuluyor
    • Rust ise Vec<i32> ve Vec<String> için ayrı ayrı özelleştirilmiş makine kodu üretiyor ve çalışma zamanında dispatch yapmıyor
    • Monomorfizasyonun bedeli derleme süresi; iki dil farklı hedefler için optimizasyon yapıyor
  • Tip sistemindeki boşlukları kapatmıyor

    • Rust’ta jenerikler ve trait’ler, Box<dyn Any> veya çalışma zamanı reflection gerektiren durumların büyük bölümünü ortadan kaldırıyor
    • Go jenerikleri ise any, reflect, ORM, decoder ve mock’larda baskın olan kod üretim kalıplarını ortadan kaldıramıyor
    • encoding/json hâlâ reflection kullanıyor, database/sql hâlâ any kullanıyor ve mockgen hâlâ kod üretiyor
    • Go’daki jenerikler, dar bazı durumlarda yararlı yeni bir araç gibi hissettirirken; Rust’taki jenerikler, çıkarıldığında dilin çökeceği bir temel gibi işliyor

Rust backend ekosistemi

  • Rust ekosisteminde de genel backend servisleri için bir ölçüde “varsayılan seçenekler” üzerinde uzlaşılmış durumda
  • Başlıca eşleştirmeler:
    • 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 thiserror for libraries, anyhow for binaries
    • Testing: Go testing, testify, gomega → Rust yerleşik #[test], rstest, assert_matches
    • Mocking: Go mockgen, moq → Rust’ta elle yazılmış fake’ler yaygın kabul edilir; mockall da kullanılır
    • Background tasks: Go goroutine’ler + errgroup → Rust tokio::spawn + JoinSet
  • Tipik bir backend servisinde axum + sqlx + tokio + tracing + serde + clap kombinasyonunun gerekenlerin %90’ını karşıladığı ifade ediliyor

Ödünç alma denetleyicisi ve öğrenme eğrisi

  • Go’dan Rust’a gelindiğinde duvara çarpmanın kaçınılmaz olduğu varsayılmalı
  • Go runtime’ı bellek ve aliasing’i sizin yerinize yönetir; Rust ise bu kararları tür sistemine taşıdığı için ilk birkaç haftada “kesin çalışması gereken” kodlar derleyici tarafından reddedilebilir
  • Go geliştiricilerinin sık takıldığı kalıplar:
    • Uzun ömürlü referanslar: Go’da map’ten alınan *User’ı uzun süre elde tutmak doğalken, Rust’ta bu ödünç alma yaşadığı sürece map’in değiştirilmesi engellenir
    • Kendi kendine referans veren struct’lar: Go’da veriyi ve o veri üzerindeki iterator’ü aynı struct’ta tutabilirsiniz; Rust’ta ise Pin, ouroboros veya yeniden tasarım gerekir
    • goroutine’ler arasında değiştirilebilir durum paylaşımı: Go’daki mu sync.Mutex; data map[K]V kalıbı Rust’ta Arc<Mutex<HashMap<K, V>>> biçimine dönüşür
    • Fonksiyondan referans döndürme: ömür etiketi devreye girer ve bu, Go geliştiricileri için yeni bir kavramdır
  • Ödünç alma denetleyicisi, işi zorlaştıran bir “kapı bekçisi” değil, gerçekten var olan hataları ortaya çıkaran bir mekanizma olarak görülmeli
  • Bir değerin taşındıktan sonra yeniden kullanılması, birden çok thread’in aynı veriye aynı anda dokunması, null veya dangling pointer’ların dereference edilmesi ya da referansın değerden daha uzun yaşaması gibi durumları derleme zamanında eler
  • Ödünç alma kavramı içselleştirildiğinde karşı çıkılan bir şey olmaktan çıkıp işbirlikçiye dönüşür; deneyimli Rust geliştiricileri genellikle 4 ila 12 hafta arasında ödünç alma denetleyicisinin yardımcı hale geldiğini söyler
  • PubNub CTO’su Stephen Blum, Rustacean Station programında ilk ayı “ilk kez programlama öğreniyormuş gibi” diye anlatıyor ve ödünç alma denetleyicisiyle ömürleri zorunlu olarak ele almak gerektiğini söylüyor
  • clap maintainer’ı Ed Page, Rustacean Station: clap with Ed Page bölümünde ödünç alma denetleyicisinin yüksek seviyeli sorunlara odaklanmasını sağladığını ve kendi analizinin yakalayamadığı noktaları da bulduğunu söylüyor

Rust’a geçişte başlıca zorluklar

  • Derleme süresi

    • Rust derleme süresi, Go’ya kıyasla açık bir gerileme olarak kabul edilmeli; orta ölçekli bir servisin temiz release build’i, Go’nun neredeyse anında derlemesine karşılık birkaç dakika sürebilir
    • Incremental build ve cargo check makul araçlardır ve derleme süreleri her yıl iyileşti, ancak Go ile fark hissedilir
    • Düzenleme döngüsünde cargo check kullanın, fayda sağlamaya başladığı noktada workspace’e bölün ve çok sayıda procedural macro içeren crate’leri ayrı crate’lerde tutarak yalnızca değiştiklerinde yeniden derlenmelerini sağlayın
    • Daha fazlası için Rust derleme süresini azaltma ipuçları yazısına bakılabilir
  • Async renklendirme problemi

    • Rust’taki async fn / fn ayrımı, Go’dan geçerken kullanılabilirlik açısından en büyük gerilemelerden biridir
    • async trait, Rust 1.75’ten beri stabil olsa da dinamik dispatch ile karıştırıldığında hâlâ pürüzlü yanları vardır
    • Bazı durumlarda bu pürüzleri örtmek için async-trait crate’i kullanılır
  • Daha küçük ekosistem

    • Rust crate ekosistemi büyüyor ve kütüphane kalitesi genel olarak yüksek, ancak Go bazı backend’e yakın alanlarda önde
    • Go’nun önde olduğu alanlar arasında Kubernetes operatörleri, bulut sağlayıcı SDK’ları ve belirli niş depolama sistemlerine yönelik veritabanı sürücüleri yer alıyor
    • Geçiş kararını kesinleştirmeden önce, bağımlı olduğunuz kütüphaneler için kullanılabilir Rust alternatifleri olup olmadığını kontrol etmeye yaklaşık bir gün ayırmak gerekir
    • Bazı ekipler sahipsiz kalmış XML şema doğrulama crate’lerini güncellemek veya daha az bilinen protokoller için istemciyi kendileri yazmak zorunda kalabilir

Entegrasyon stratejisi

  • Go’dan Rust’a başarılı geçiş, her şeyi tek seferde yeniden yazmaktan çok taktiksel seçimlerle ilgilidir
  • Microsoft Principal Engineer Victor Ciura, Rust in Production programında bunu “her şeyi eğlencesine Rust ile yeniden yazmak değil; yeni bileşen Rust’a daha uygunsa Rust’ı seçmeye yönelik taktiksel bir karar” diye tanımlıyor
  • 1. Sıcak yolu ayrı bir servis olarak çıkarmak

    • Belirli bir servis sürekli sorun çıkarıyorsa, aynı API sözleşmesinin arkasında tutup yalnızca o servisi Rust ile yeniden yazmak en düşük riskli geçiş yöntemidir
    • Hedef; CPU kullanımı yüksek, gecikmeye duyarlı veya istikrar sorunları tekrar eden bir servis olabilir
    • Diğer Go servisleri HTTP/gRPC üzerinden iletişim kurmaya devam edeceği için iç uygulama dilini bilmeleri gerekmez
    • Radar CTO’su Jeff Kao, Rust in Production programında Discord’un Go’dan Rust’a geçişini anlattığı yazının Radar’da da aynı yaklaşımı düşünmelerini sağladığını söylüyor
  • 2. Sidecar veya worker process’i değiştirmek

    • Arka plan worker’ları, kuyruk tüketicileri, toplama pipeline’ları ve CPU-bound batch işleri iyi ilk hedeflerdir
    • Bunlar genellikle kuyruk veya topic gibi net giriş/çıkış sınırlarına sahiptir ve sistemin geri kalanıyla process içi paylaşılan durum barındırmaz
  • 3. cgo mümkün ama sancılı

    • Go’dan cgo aracılığıyla Rust çağrılabilir ve bunu ele alan iyi rehberler de vardır
    • Ancak backend servislerinde genellikle önerilmez
    • Build karmaşıklığı ve FFI ek yükü, çoğu zaman “bir Rust servisi kurup onu ağ çağrısının arkasına koyma” yaklaşımına göre sağlayacağı avantajları dengeler
    • Kütüphaneler ve CLI araçlarında daha pratik olabilir
  • 4. Gateway arkasında Strangler Pattern uygulamak

    • Bir API gateway veya reverse proxy varsa, yalnızca belirli endpoint’leri yeni Rust servisine yönlendirip geri kalanını Go’da bırakabilirsiniz
    • Kimlik doğrulama, arama veya ödeme gibi tek bir sınırlandırılmış bağlam geçiş birimi olarak uygunsa bu yöntem özellikle iyi çalışır
    • Bu kalıba, yeni servisin mevcut servisin etrafında büyüyüp sonunda onu tamamen değiştirmesi anlamında “strangler fig” denir

Pratik geçiş ipuçları

  • Sınırları net olan servislerle başlanmalı; en merkezi ve en sık deploy edilen servis seçilmemeli
  • Sistem geri kalanıyla sözleşmesi iyi tanımlanmış ve etki alanı küçük bir servis seçilmeli
  • Aynı API sözleşmesini korumak

    • Go servisi bir REST API sunuyorsa, Rust servisi de aynı yolları, aynı JSON biçimini ve aynı hata sarmalayıcısını korumalı
    • İstemciler açısından geçiş görünmez olur ve gateway üzerinden trafik kademeli olarak taşınabilir
  • Deyimleri bire bir taşımamak

    • if err != nil { return err } ifadesi ? olur
    • İstek başına goroutine kalıbı yalnızca gerçekten gerektiğinde tokio::spawn ile taşınır
    • axum zaten istekleri eşzamanlı olarak işler
    • Tek metotlu interface’ler çoğu durumda Box<dyn Trait> değil, generics üzerindeki trait bound’a dönüşür
  • Derleyiciyi pair programmer gibi kullanmak

    • Rust derleyici hataları genelde yüksek kalitelidir ve yavaşça okunduğunda neredeyse her zaman doğru cevabı verir
    • En uzun süre zorlanan ekip üyeleri, derleyiciyi işbirlikçi olarak görmek yerine onunla savaşanlardır
  • Başta eğitime yatırım yapmak

    • Rust geçişini işi yürütürken “yandan” öğrenmeye çalışmak çoğu zaman iyi sonuç vermez
    • Workshop’lar, çevrimiçi kurslar ve gerçek kod tabanı üzerinde pair session’lar gibi öğrenme zamanı gerçekten ayrılmalı
    • Ekip yetkin hale geldiğinde bu başlangıç yatırımı kat kat geri döner

Go’nun uygun kalmaya devam ettiği alanlar

  • Her şeyi Rust’a taşımak gerekmez; Go’nun özellikle iyi olduğu alanlar vardır
  • Kubernetes yerel araçları

    • Operatör, kontrolcü ve CRD alanlarında ekosistem ezici biçimde Go merkezlidir
  • CLI yardımcı programları ve geliştirme araçları

    • Hızlı derleme, kolay çapraz derleme ve basit dağıtım güçlü yanlarıdır
  • Glue servisleri

    • İnce API katmanları, proxy’ler ve biçim dönüştürücülerde Rust’ın boilerplate oranı buna değmeyebilir
  • Ekip hızının mutlak doğruluk garantisinden daha önemli olduğu yerler

    • Hızlı hareket edilmesi gereken alanlarda Go hâlâ uygun olabilir
    • Canonical VP of Engineering Jon Seager, Rust in Production programında Go’nun ağ servisleri için çok iyi bir seçenek olduğunu, Canonical’da çokça Go kullanıldığını ve Juju’nun da devasa bir Go kod tabanı olduğunu söylüyor
    • Hibrit strateji yaygındır; birçok ekip, “sıkıcı” servisler için Go’yu, ek çabanın istikrar ve performansla geri kazanıldığı servisler içinse Rust’ı kullanarak çok dilli bir backend’e yönelir

Beklenebilecek iyileştirmeler

  • Rakamlar iş yüküne göre büyük ölçüde değiştiğinden bunlara bir vaat değil, kabaca bir kılavuz olarak bakılmalıdır
  • Go’dan Rust’a geçişte gözlemlenen yaklaşık iyileşme aralıkları:
    • CPU kullanımı: %20~60 azalma
      • Go zaten verimli olduğu için Python’dan Rust’a geçişteki kadar dramatik değildir
      • Kazanç, GC’nin olmamasından ve daha sıkı döngülerden gelir
    • Bellek: %30~50 azalma
      • Bunun başlıca nedeni GC ek yükünün olmaması ve runtime’ın daha küçük olmasıdır
    • P99 gecikmesi: çok daha tutarlı
      • Rust servisleri, Go servislerinde görülen GC kaynaklı jitter’ın azalması ve düzleşmesi eğilimindedir
      • Go tarafı da düşük gecikmeli GC’nin gelmesinden sonra çok gelişti, ancak yüksek yük altında fark sürüyor
    • Prodüksiyon arızaları: ekiplerin en güçlü şekilde bildirdiği iyileşme alanıdır
      • go test -race testini geçip prodüksiyona kadar ulaşabilen veri race’leri, nil dereference’ları ve kaçırılmış hata yolları gibi hata türleri Rust’ta derlenmez
      • Rust’a geçişten sonra nöbetçi on-call vardiyaları genellikle çok sıkıcı hâle gelir
  • InfluxData Staff Engineer Andrew Lamb, Rustacean Station: Rebuilding InfluxDB with Rust programında InfluxDB yeniden yazımından sonra çöküşler, garip çok iş parçacıklı race condition’lar ve eskiden çok zaman alan sorunların izini sürme ihtiyacının ortadan kalktığını söylüyor
  • Go’dan Rust’a geçmenin, Python’dan Rust’a geçişte olduğu gibi throughput’u 10 kat artırması pek olası değildir
  • Gerçek fayda; “saçma hataların” azalması, gecikme kuyruğunun daha düz hâle gelmesi ve aynı dille gömülü geliştirme ya da sistem programlama gibi başka alanlara da genişleyebilme yeteneğidir

Ek dikkat noktaları

  • Rust’ın tip sistemi tüm senkronizasyon mantığı hatalarını ortadan kaldırmaz, ancak senkronizasyon olmadan iş parçacıkları arasında paylaşılamayan tipler derlenmez
  • “Kilidi almayı unuttum” türündeki, sessiz veri bozulmasına yol açabilecek sorunlar Rust tip sistemi tarafından engellenebilir
  • Go string, değiştirilemez bir bayt dizisidir ve geleneksel olarak UTF-8 kabul edilir, ancak bu tip düzeyinde garanti edilmez
  • En yakın eşleşme, salt okunur görünüm için Go string ↔ Rust &str, değiştirilebilir tampon için Go []byte ↔ Rust Vec<u8> şeklindedir
  • Rust String, &str’nin sahipli ve büyütülebilir sürümüdür; ayrıca içeriğin geçerli UTF-8 olduğuna dair ek garanti sunar
  • Ayrıntılar için Strings, bytes, runes and characters in Go yazısına bakabilirsiniz
  • Go 1.18’den itibaren jenerik fonksiyonlar ve jenerik tipler mümkündür, ancak metodların kendilerine ait tip parametreleri eklenmemiştir
  • Rust’taki (0..100).filter(|n| ...).collect() gibi iterator zincirleri Go geliştiricilerine yabancı gelebilir, ancak Rust’ta da for döngüleri kullanılabilir ve tek seferlik kodlarda bu çoğu zaman doğru tercihtir

Sonuç

  • Go’dan Rust’a geçiş, Python veya TypeScript dünyasından Rust’a geçişten farklıdır
  • Go kökenli geliştiriciler statik tiplerin ve derlenen dillerin avantajlarını zaten bildiğinden, bu dinamik tipleri ya da yavaş runtime’ı bırakma türünde bir geçiş değildir
  • Temel takas, nil’den vazgeçip daha sağlam bir kod tabanı, daha az tuzak ve daha fazla hatayı derleme zamanında yakalayan daha katı bir derleyici elde etmektir
  • Bunun karşılığında öğrenme eğrisi daha diktir
  • Kuruluşun bağımlı olduğu, yüksek erişilebilirlik gerektiren ve işletme açısından kritik servislerde — örneğin temel servisler — bu takas açıkça değerlidir
  • Diğer servislerde ise doğru cevap hâlâ Go olabilir
  • Migrasyonun amacı, her problemi onu en iyi çözen dile yerleştirmektir

1 yorum

 
GN⁺ 3 시간 전
Hacker News yorumları
  • C/C++ ya da Python’dan Rust’a geçmek çeşitli nedenlerle anlaşılır, ancak web backend için Go daha uygun bir seçim gibi görünüyor
    Neredeyse sadece Rust kullanıyorum ama en son Rust ile web sunucusu tarafında çalıştığımda keşke Go kullansaydım diye düşündüm
    Asıl yazı Go’nun hata işleme sözdiziminin ayrıntılı ve uzun olduğunu söylüyor; bu doğru bir tespit. Rust’ta da aynı sorun vardı, sonra hata varsa hata değerini döndüren ? sözdizimi eklendi. Go’nun hata işlemesi çoğunlukla bunun açık açık yazılmış hali
    Rust’ta birleşik bir hata tipi yok; io::Error, thiserror, anyhow gibi başlıca hata düzenleri var ve bunları çağrı zinciri boyunca yukarı taşımak uğraştırıcı
    Yeni bir dilde başta eksik olup sonradan eklemesi zor olan şeyler var. Sabit tipleri, boolean tipi, hata tipi, çok boyutlu dizi tipi, 2/3/4 boyutlu vektör ve matris tipleri ile standart işlemler buna örnek. Bunlar başta standartlaşmazsa aynı kavramın farklı gösterimlerini uzlaştırmakla çok zaman harcanıyor
    Hata işleme dışında web geliştirmede etkisi daha az olabilir, ama sayısal hesaplama, grafik ve modellemede sayı dizilerine standart işlemler uygulamak gerektiği için büyük acı yaratıyor
    Go’nun web servislerinde iki büyük avantajı var. Birincisi yazının da söylediği goroutine’ler, ikincisi ise yazıda çok ele alınmayan kütüphaneler. Go’da web servisleri için gereken kütüphanelerin çoğu var ve bunlar Google içinde de kullanıldığı için çok sert ortamlardan geçti. Buna karşılık Rust crate’leri daha az olgun ve çoğunda resmî kalite güvencesi yok

    • Rust’a karşı Go’nun en büyük avantajı bence derleme hızı
      Ayrıca Rust, Go’ya kıyasla hâlâ birçok C/C++ kütüphanesine bağımlı; bu yüzden çapraz derleme, yeniden üretilebilir build’ler ve statik binary üretimi kolayca sorun olabiliyor
      Go’nun dezavantajı ise çöp toplayıcısının fazla basit olması. Gecikme sıçramaları yaşanırsa, acı verici bir yeniden yazım dışında elde pek çözüm kalmıyor
    • Rust’ta fiilen tek bir hata yapısı var: Error trait
      Listelenenler sadece bunu kullanmanın yaygın yolları ve isterseniz sadece Box kullanıp gayet rahat edebilirsiniz. Bu da büyük ölçüde anyhow::Errorın yaptığı şeye benziyor
    • Bir dönem Go’yu epey seviyordum ama son zamanlarda daha çok Swift ve Rust kullandıkça, null pointer dereference’ı engellemeyen ve eşzamanlılık güvenliği garantisi vermeyen bir derleyici biraz tarih öncesinden kalmış gibi geliyor
      Yine de standart kütüphane tarafında Go’nun Rust’tan çok daha iyi iş çıkardığını düşünüyorum
    • Katılıyorum. Yazının başında bunun backend servisleri için olduğunun söylenmesi özellikle dikkatimi çekmişti
      Rust dilini seviyorum ve onu embedded firmware ile PC uygulamalarında kullanıyorum, ama web backend için hâlâ Python kullanıyorum. Çünkü Rust’ta Django ya da Rails ayarında bir araç seti yok
      Flask benzeri şeyler var ama Flask’ın sağlam ekosistemi yok. Go deneyimim az ama web backend için Rust yerine muhtemelen Go seçerdim. Nedeni kütüphane ve framework ekosistemi
      Ayrıca genel olarak söylenen nedenlerden ötürü Async Rust’ı pek sevmiyorum. Rust web ekosisteminde neredeyse her şey asenkron kullanım gerektiriyor denecek kadar yakın
    • Rust’ta üç hata sistemi yok, bir tane var: Error trait
      io::Error, bunu uygulayan birçok tipten sadece biri; özel bir konumu yok. thiserror ile tanımlanan hatalar da bu trait’i uygular
      anyhow, bir fonksiyonun döndürebileceği hata tipini API sözleşmesinde ayrıntılı yazmak istemediğinizde rahatça “herhangi bir Error” diyebilmenizi sağlıyor
  • Rust ile Go’ya kıyasla deterministik kod yazmak daha kolay; bu yüzden deterministik simülasyon testleri ve özellik tabanlı testler gerektiğinde çok faydalı
    Yakın zamanda Go ile Postgres-to-Iceberg veri aynalama aracı https://github.com/polynya-dev/pg2iceberg yazdım ama Go runtime’ı ile boğuşmadan deterministik simülasyon testleri yapmak istediğim için bunu Rust’a port ettim
    Yine de ilgili alan böyle bir test seviyesini haklı çıkaracak kadar kritik değilse, her zaman Rust yerine Go’yu seçerim
    İlgili yazı: https://www.polarsignals.com/blog/posts/2024/05/28/mostly-ds...

  • Klişe ve tekrar gibi gelebilir ama Rust’la ilgili en büyük şikâyetim paket yönetimi durumu ve bunun tamamen geliştirici zihniyetinin bir sonucu olduğunu düşünüyorum
    Rust tarafındaki kullanılabilirliği seviyorum. Veri tiplerine yönelik fonksiyonel yaklaşım çok zarif. Ancak şu anda bir Rust projesi ile bir Go projesi üzerinde yan yana çalışıyorum ve bağımlılık ağacı tamamen başka bir canavar
    Go projesi büyük ölçüde standart kütüphane ile çözülüyor ama Rust projesinde sadece rusqlite (sqlite), clap (CLI), ratatui (TUI), tauri (GUI) istedim ve buna rağmen bağımlılık sayısı 400’ü aşıyor gibi görünüyor. Özellikle tauri açık ara en büyük suçlu; onu çıkarsam bile neredeyse 100 bağımlılık var, bu da bana çılgınca geliyor
    Bağımlılıkları makul biçimde ele alan, iyi yönetilen Rust crate alternatifleri olsa çok daha iyi olurdu ama henüz bulamadım. Sisteme shai hulud sokmak istemiyorum sadece; ama Rust web tarafındaki insanlar bu açıdan cargoyu npme benzetmek istiyor gibi görünüyor

    • Birçok Rust kütüphanesinin birden çok crate’e bölündüğünü ve bunların hepsinin bağımlılık grafiğine girdiğini hesaba katmak gerekiyor
      Bu yüzden bağımlılık sayısı gerçekte olduğundan büyük görünüyor. Ayrı crate olsalar da bakımcıları aynı ve çoğu aynı upstream Git deposunun parçası
      Yine de genel hisse katılıyorum. Rust’ta 0.x sürümünde, yarı terk edilmiş çok sayıda crate var ve çoğu zaman daha iyi bir alternatif de olmuyor
    • Standart kütüphane bence iyi fikirlerin ölmeye gittiği yer
      Sonra da httplib3ten sonra httplib4 çıkıyor
      Yani demek istediğim, Rust yaklaşımını çok daha fazla tercih ediyorum. Standart kütüphaneye bağımlı olmakla başka bir bağımlılığa sahip olmak arasında benim için büyük fark yok. Sonuçta ikisi de bağımlılık
      Standart kütüphane olduğu için kalite daha yüksek ya da bakım daha iyi olacak diye düşünmek bence ayrı bir mesele
      Sonunda her şey kaynaklara dayanıyor. Elbette standart kütüphane daha çok kaynak alabilir ama tersine aşırı büyüyüp bakımı imkânsız hâle de gelebilir
    • rusqlite, clap, ratatui, tauri karşılıklarının hepsini standart kütüphanede sunan bir dil, Java hariç gerçekten var mı emin değilim
      Ayrıca Tauri’nin kendisi 14 crate’ten oluşuyor ve bunların her biri build ağacında görünüyor
      https://github.com/tauri-apps/tauri/blob/dev/Cargo.toml
      Ratatui de 6 tane
      https://github.com/ratatui/ratatui/blob/main/Cargo.toml
    • Paket yönetimi neredeyse bütün dillerin ve teknolojilerin baş belası
      Bunu kimse gerçekten “çözebilmiş” değil ve bundan sonra da tek bir çözüm çıkması zor görünüyor
      Go’da kütüphane geliştiricisinin semantic versioning kurallarına doğru düzgün uyacağına güvenmek zorundasınız ve sürümü sabitleyemiyorsunuz. Bu benim de kişisel olarak oldukça sinir olduğum bir nokta
      Bazı dolaylı çözümler var. Git commit hash’i gibi SHA kullanarak sahte sürüm üretmek ya da bilinen bağımlılık önbelleği olan vendoring’e gitmek gibi. Ama vendoring de önbellek yönetimi sorunlarını beraberinde getiriyor
      Hafta sonu Python sanal ortamları kullanmak zorunda kaldım, hiç iyi bitmedi ve neden Python’dan uzaklaştığımı tekrar hatırladım
      Perl’in CPAN’i, Java’nın Maven/Gradle’ı, Ruby’nin gems’i, Go’nun dep/glide/vgo/modules’ı, Rust’ın Cargo’su, Node’un npm/yarn’ü... hepsinde benzer dertler var
      İşletim sistemlerinde de Redhat’in yum/rpm’i, Debian’ın apt’si, Ubuntu’nun snap’i var. Özellikle snap neden böyle, hiç anlamıyorum
    • Go’ya çok hâkim değilim ama Go standart kütüphanesinde Tauri’ye karşılık gelen şeyin ne olduğunu merak ediyorum
      Belki kullanım senaryosunda frontend’i Go olarak bırakıp sadece backend’i Rust yapmak mantıklı olabilir mi diye düşünüyorum
  • Bu metin hem bir geçiş rehberi hem de Rust savunusu olmaya çalıştığı için tuhaf hissettiriyor
    Sonuçta Rust mı Go mu kullanılacağı sorusunda asıl mesele neredeyse tamamen “yönetilen bir runtime istiyor musunuz” noktasına çıkıyor. Bir nesil Rust programcısı, yönetilen runtime’ın kötü olduğuna ve onun olmamasının başlı başına önemli bir özellik olduğuna kendini inandırdı
    Ama bu açıkça yanlış. Yönetilen runtime isteyen programlama alanları, istemeyenlerden daha fazla
    Bu, böyle durumlarda varsayılan seçimin hep Go olması gerektiği anlamına gelmiyor. Rust’ı tercih etmek için birçok öznel neden de var. Go kullanırken matchi özlüyorum ama tokioyu ve Async Rust’ı özlemiyorum
    İkisi de problem alanını zorla bükmeyi gerektirmeyen neredeyse her durumda meşru seçimler. Örneğin Go ile Linux kernel modülü yazmaya kalkmak garip bir tercih olurdu
    Rust ve Go kavgası bizim alanın tuhaf ve biraz utandırıcı bir kenar kavgası gibi. Sektörün büyük bir kısmı Python ya da Node ile gayet iyi bütün sistemler kuruyor ve hangi statik tipli derlenen dili kullanacağını tartışan ineklerle dalga geçiyor. Gerçek soru Rust’a karşı Go değil, Python’a karşı Rust/Go

    • Node’u PureScript ile kullanmak belki fena olmayabilir
      Ama genel olarak Rust ve Go tarafının dinamik tipleme kötülüğüne karşı güç birliği yapması gerektiğini düşünüyorum. Tip ipuçlarının artık en iyi uygulama sayılması, aslında bunun bir kusur olduğunun kabulü değil mi?
      İyi tip ipuçları olsa bile tip çıkarımının gerisinde kalıyorlar. Tip çıkarımı, tip değiştiğinde çok sayıda kod satırını olduğu gibi bırakmaya izin verirken istem dışı tip değişimlerini de engelliyor
    • Node tarafı, statik olarak derlenen tipler istediği için TypeScript’i benimsedi
      Keşke TS tarafında biraz daha fazla runtime olsaydı. Python’da kıskandığım tek şey, HTTP endpoint’lerinde JSON şema doğrulamasını çok doğal biçimde yapabilmeleri
      Zod’dan geçmek zorunda olmak sürekli bir sinir kaynağı ve bence bu, TS ekibinin fazla doktriner olmasının sonucu
  • LLM yazımının izleri giderek daha incelikli hâle geliyor ama yine de hemen fark ediliyor. Özellikle genuine kelimesi buna örnek
    “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” gibi örnekler
    Bence yazının tamamı AI üretimi değil ama AI desteği almış gibi duruyor. Eğer öyleyse, yazar bunu genuinely iyi yapmış
    Başkalarının buna değinmemesinden, bunun içeriğe ciddi zarar vermediği anlaşılıyor sanırım; ama bunun gittikçe yaygınlaşıp tespit edilmesinin zorlaşması tuhaf geliyor

    • Katılıyorum ama nedenini tam bilmiyorum. Bir şeyi tam olarak neyin AI üretimi gibi duyurduğunu açıklayamıyorum
      “Go is clearly working for a lot of people,” cümlesine gelene kadar AI yardımı şüphesine kapılmıştım. Tabii öyle olmayabilir, ben bu konuda çok iyi ayırt edemiyorum
      Somut bir ipucundan çok, ironik biçimde bir his meselesi. Bir yazı AI destekli gibi “geldiğinde”, yazı iyi olsa bile ilgimi hemen kaybediyorum
      İnsanların kendi düşüncelerini, akıllarına geldiği gibi, doğrudan yazarken daha rahat olmalarını isterdim
    • Tamamen konu dışı ama it's worth being precise about ... ifadesi, genuine kullanımından çok daha güçlü biçimde AI kokuyor
    • Yazının tamamının AI tarafından üretildiğini düşünüyorum. Yazar belki girdi olarak bir taslak verdi ve çıktının bazı bölümlerini düzenledi
      Mesela şu paragraf buna örnek: “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.”
      Her cümle bir şey söylüyor, her cümle önemli ve kendi görevini yapıyor. Böyle bir yazı, blog yazısından çok son derece profesyonel bir kitapta ya da makalede beklenir
      Bu yüzden de ironik biçimde yazıyı daha zor okunur ve daha sıkıcı yapıyor
    • Son bir yılda LLM yazılarının özellikle yüzey ve altyapı katmanı hakkında konuşmaya olağanüstü meyilli olduğunu hissettim
      LLM üretimi metnin klişe ifadelerle dolu olmayacağını zaten beklemiyorum. Ama umarım hepimiz daha iyi bir editörlük sezgisi geliştiririz de aynı sesi tekrar tekrar okumak zorunda kalmayız
  • Yeni bir projeyse rahatlıkla Rust ile yazılabilir
    Ama ortada mevcut, çalışan ve gelir üreten bir sistem varsa, yeniden yazılması gereken parçaları mevcut dilde düzeltip devam etmek en doğrusu
    Bildiğiniz bir dil ve güvendiğiniz bir ekiple sistemi küçük ve ölçülebilir adımlarla iyileştirin. Bunun dışı israf niteliğinde bir din savaşı

    • Ekip C#/Java/Go gibi dillerle başarıyla ürün çıkarmış ve bunlarla rahatsa, Rust kullanmak için bir neden göremiyorum
  • Benchmark çalıştırmadan önce de Rust’ı seviyordum ama çoğu LLM için Rust ve Go yazma verimliliği farkı beklediğimden çok daha büyüktü. Özellikle başlangıç ortamı sorunlarını düzeltebilen ajan tipi harness’lerde bu daha da belirgindi
    Bunu görünce oldukça güçlü bir Rust savunucusuna dönüştüm. Mevcut codebase’den çağrılacak batch processing tool’ları Rust ile yazıp iyi sonuç aldım ama tüm production geçişini henüz denemedim
    Yazıda sözü geçen Go sorunlarının, özellikle nil işleme ile ilgili olanların, Codex ile yapılan kapsamlı code review sayesinde giderek çözüldüğünü düşünüyorum. En iyisi baştan sorun olmaması ama tasarım ve implementasyona ne kadar emek veriliyorsa review ve anlamaya da o kadar emek veren geliştiriciler için bu tür güvenlik açıkları artık giderek isteğe bağlı bir şeye dönüşüyor
    Dil verileri burada: https://gertlabs.com/rankings?mode=agentic_coding

    • Ayrıntılı derleyici hataları ve güçlü tip sistemi sayesinde ajanların düzelt → derle → düzelt döngüsünü yönetmesi kolaylaşıyor
      Rust, kullanıcıyı oldukça sıkı biçimde belirli bir raya oturtuyor. Codex her zaman bir şeyleri derlenir hâle getiriyor
      Dezavantajı ise bazen deyimsel yaklaşım mümkün olmadığında başarısız olmak gerekirken, bunun yerine derlenen ve isteneni yapan ama aptalca bir implementasyon üretebilmesi
    • LLM açısından Rust’ın zayıf noktası derleme süresi
      LLM’ler insanlardan daha hızlı kod yazdığı için, göreli olarak derleme bekleme süresi daha büyük hâle geliyor. 100 bin satır ve üzeri gibi belirli bir ölçeğe ulaşan projelerde Rust’ın yaklaşık 10 kat daha yavaş derleme süresi darboğaz olmaya başlıyor
      Çekirdek altyapıyı yazıyorsanız bu maliyeti ödemeye değer olabilir ama internete açık olmayan dahili servisler geliştiriyorsanız asıl mesele geliştirme hızı olabilir
      Yavaş derlemenin insanların geliştirme hızını da etkilediğini düşünüyorum ama ilginç biçimde geliştiriciler bunu nicel olarak ölçmeye çok nadiren çalışıyor
  • Eğer asıl engel ayrıntılı ve uzun kodsa, Go 1.28’e gelmesi planlanan şu özellik bunu ciddi biçimde azaltabilir
    https://github.com/golang/go/issues/12854#issue-110104883

  • “Kuruluşun bağımlı olduğu, yüksek uptime gerektiren ve iş için kritik servis” ifadesi komik geldi
    Hele bu Rust servisi Kubernetes üzerinde çalışıyorsa daha da komik

  • Zaten Rust kullanıyorum ve Go deneyimim yok, dolayısıyla bu yazı bana bire bir hitap etmiyor olabilir
    Ama takıldığım bir nokta var. Rust’ta veri yarışlarının “derleme zamanında yakalandığını” söylemek en azından biraz abartılı geliyor
    Bu ifade, sanki Rust mutex starvation gibi şeyleri ya da diğer eşzamanlılık problemlerini de çözebiliyormuş izlenimi verebilir. Oysa durum böyle değil
    Data race’in dar kapsamlı, biçimsel bir terim olduğunu biliyorum ama yine de bunun daha açık yazılabileceğini düşünüyorum