
Fri Sep 04 2026

Hazırsan başlayalım 👋
Bu yazı, 65 bin satırlık bir Go kod tabanını Rust'a taşıma maceramızın özeti. Neden böyle bir şey yaptık? Kısaca: maliyet, doğrulama ve human-in-the-loop üçgeni.
| Sebep | Ne Kazandık? |
|---|---|
| Sahiplik modeli | Bellek güvenliği derleme zamanında, GC olmadan |
| Sıfır maliyetli soyutlamalar | Çalışma zamanı ek yükü ≈ 0 |
| Tür sistemi | Geçersiz durumları kod yazmadan engelleme |
| Ekosistem | tokio, serde, sqlx, tracing — production-ready |
Bu yazıda kod satırı satırı anlatmayacağım. Şu üç konuya odaklanacağım:
Ben backend mühendisi, 10+ yıldır dağıtık sistemlerle uğraşıyorum. Go'yu seviyorum, ama bu proje bana Rust'ın "derleyici senin Pair Programming partner'in" felsefesini somutlaştırdı. Taşıma sürecinde yaklaşık 8 ay geçti, ekip 5 kişiydı, ve hayır — "big bang" yapmadık. Kademeli, riskli ama kontrol edilebilir adımlarla gittik.
Hadi derinlemesine inceleyelim? 👇
Go ile yıllar süren ilişkimizden sonra Rust'a geçiş kararı bir gecede alınmadı tabii 😅
Hadi içimdeki "neden?" sorusunu beraber çözelim.
| Konu | Go | Rust |
|---|---|---|
| Bellek yönetimi | GC (Garbage Collector) | Ownership + Borrow checker (GC yok) |
| Memory safety | Runtime'da (panic/defer) | Derleme zamanında garanti |
| Data race koruması | go vet, -race flag (runtime) |
Derleyici seviyesinde (Send/Sync trait'leri) |
| GC duraklamaları | Var (bazı workload'larda hissedilir) | Yok — deterministic latency ✅ |
| Öğrenme eğrisi | Düşük, hızlı başlangıç | Daha dik ama uzun vadeli güven veren |
| Ekosistem | Olgun, standart kütüphane zengin | Hızla büyüyen, cargo harika 📦 |
Performans determinizmi bizim için game changer oldu.
Go'da GC pause'ları p99 latency'yi tahmin edilemez hale getiriyordu. Rust'ta heap allocation kontrolümüz tamamen elimizde — istediğimizde stack, istediğimizde heap, istediğimizde Box/Arc ile paylaşımlı sahiplik.
Güvenlik tarafında da:
unsafe blokları explicit ve kapsamlı — kod review sırasında "burada ne oluyor?" demek kolay 🔎Eşzamanlılık için:
goroutine + channel güzel ama data race'ler runtime'a kalıyorstd::thread + Mutex/RwLock + type system sayesinde data race = compile error ⛔async/.await ekosistemi (tokio, async-std) artık production-ready ve zero-cost abstraction sunuyor ⚡cargo test --all-targets + clippy + miri ile daha erken hata yakalamaÖzetle: Go harika bir dil, bizim o anki ihtiyaçlarımızı karşıladı. Ama düşük gecikme, deterministik bellek ve derleme-zamanı güvenlik üçgeni bizi Rust'a itti.
Karar takım consensus'uyla, prototip + benchmark sonrası alındı — "hype" değil, veriyle 📊
Hazırsan bir sonraki bölümde ilk migration stratejimizi ve modül modül geçiş planını anlatayım 🚀
Hadi bu ekibi en ekonomik şekilde nasıl saracağımızı pratik bir örnek üzerinden anlayalım, 🎯 bakalım bahsettiğimiz araçlar bize nasıl maliyet avantajı sunuyor.
Her birinin maliyet yapısı ve 400 dolar bütçeye nasıl sığdığını hızlıca inceleyelim:
| Araç/Model | Model | Maliyet (USD) | Notlar |
|---|---|---|---|
| GitHub Copilot | GPT‑4 | ~150 (aylık) | Tam entegrasyonlu IDE, ortalama 2000 token/gün → token başına ~0.075 |
| ChatGPT Plus | GPT‑4 | 20 (aylık) | Sınırsız sohbet, hızlı yanıt; 400 dolar bütçenin büyük kısmını alır ancak max etkililik |
| OpenAI API | GPT‑4‑Turbo | 0.015/1k token | İhtiyaca göre ödeme, 10 k token → 0.15 dolar |
| Anthropic Claude 3 | Claude 3 | 0.015/1k token | Yaratıcı görevler için iyi, aynı token fiyatı |
| Yerel Llama 2 | Llama 2 (7B) | 0 (donanım maliyeti hariç) | PC'de çalıştırılabilir, sıfır API maliyeti |
Bu sayede ne oluyor?
Sonuç: 400 dolar bütçeyle hem güçlü bulut asistanlarından faydalanabilir, hem de maliyetten tasarruf etmek için token başına ödeme yapılacak hizmetleri serbestçe deneyebilirsin. 🚀
Hadi pratik bir örnek üzerinden anlayalım. Gitmeye hazır bir Go uygulamanız olduğunu varsayalım. Bunu bir aktarma işlemine hazirlamak için ilk olarak şunları anlamamız gerekiyor:
Proje yapısı
auth, db, service, …)Bağımlılıklar – go.mod dosyası ile tanımlanan bağımlılar ve iletişim modülleri.
Test kapsamı – _test.go dosyaları, kapsayıcı yapılar ve kapsayıcı raporları (cover.out).
Bu parçaları ayırdıktan sonra AI'ya verecekleri bağlam dosyalarını hazırlıyoruz:
Ayrıca şimdi koda öncelikle bir göz atalım. Şu komutla projedeki Go dosyalarının sayısını öğrenebiliriz:
#!/usr/bin/env bash
# Projedeki *.go dosyalarının sayısını sayar
go_source_count=$(find . -name '*.go' | wc -l)
echo "Toplam Go dosyası sayısı: $go_source_count"
# Tüm modülleri listeler
go list -m all
Ne oluyor burada?
.go uzantılı her dosyayı bulur ve sayar (wc -l).go list -m all), böylece ana modülün ve tüm gerekli alt modüllerin görünür halde olduğunu doğrularız.Paketleri hızlıca listelemek isterseniz ise şu kısa komutu kullanabilirsiniz:
go list ./...
Bu sayede Go araçlarına (kubegen, logger, vb.) sahip olduğunuz paketlerin tam bir listesini verir ve AI'ya doğru bağımlılık haritasını oluşturması için kolaylık sağlar.
Böylece ne oluyor?
Bunlar, Go uygulamasını güvenle aktarmadan önce hazırlanması gereken temel adımlardır. Bunları tamamladıktan sonra bir sonraki aşamaya güvenle geçebilirsiniz. 🎯
Prompt stratejim şuydu:
İlk çalışma istatistikleri 🎯
| Metrik | Değer |
|---|---|
| Üretilen Rust satırı | ~2.300 |
| Derleme hatası (cargo check) | %12 |
| Mantık hatası (testlerde) | %5 |
| Genel izlenim | Hızlı başlangıç, ama ownership ve lifetime konularında tekrar isteği gerekiyor. |
Kullandığım örnek prompt
Sen bir Rust geliştiricisisin. Aşağıdaki C# kodunu idiomatic, güvenli ve modüler Rust koduna çevir.
Sadece kod bloğunu ver, açıklama yazma.
// C# sınıfı
public class OrderService {
private readonly IRepository<Order> _repo;
public OrderService(IRepository<Order> repo) => _repo = repo;
public async Task<Order> GetByIdAsync(Guid id) => await _repo.FindAsync(id);
public async Task<IEnumerable<Order>> GetAllAsync() => await _repo.GetAllAsync();
}
AI’dan gelen ilk Rust dosyasının başlangıcı
// order_service.rs
use crate::repository::Repository;
use uuid::Uuid;
pub struct OrderService<R: Repository<Order>> {
repo: R,
}
impl<R: Repository<Order>> OrderService<R> {
pub fn new(repo: R) -> Self {
Self { repo }
}
pub async fn get_by_id(&self, id: Uuid) -> Result<Order, RepositoryError> {
self.repo.find(id).await
}
pub async fn get_all(&self) -> Result<Vec<Order>, RepositoryError> {
self.repo.get_all().await
}
}
Ne oldu?
&self yerine self alıyor.async_trait crate’i eklememi unutmuştum, derleyici hata verdi ❗️.Özet 🎉
Hazırsan Rust projemizi sürekli entegrasyon (CI) pipelines’ına bağlayalım 🚀.
Amaç basit: her push/pull‑request’te kod derlensin, testler koşulsun, lint kuralları kontrol edilsin ve formatlama hatası olmasın.
| Araç | Ne yapar? | Nasıl çalıştırırım? |
|---|---|---|
cargo test |
Tüm unit & integration testlerini koşturur ✅ | cargo test --all |
cargo clippy |
Yaygın hataları, performans ipuçlarını ve stil ihlallerini yakalar 🔎 | cargo clippy -- -D warnings |
cargo fmt --check |
Kodun rustfmt standartlarına uyup uymadığını kontrol eder 🎨 |
cargo fmt --all -- --check |
İpucu:
clippy’de-D warningsflag’i uyarıları hata olarak ele alır, bu sayede CI “sarı” değil “kırmızı” döner ⛔.
Aşağıdaki YAML dosyasını .github/workflows/ci.yml yoluna koyarsan, her push/pull‑request’te build → test → clippy → fmt sırasıyla çalışır.
name: Rust CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
env:
CARGO_TERM_COLOR: always
jobs:
build-and-test:
name: Build, Test & Lint
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repository
uses: actions/checkout@v4
- name: 🦀 Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: 📦 Cache cargo registry & target
uses: Swatinem/rust-cache@v2
- name: 🔨 Build project
run: cargo build --verbose
- name: ✅ Run tests
run: cargo test --all --verbose
- name: 🔎 Run clippy (deny warnings)
run: cargo clippy --all-targets --all-features -- -D warnings
- name: 🎨 Check formatting
run: cargo fmt --all -- --check
Ne oluyor burada?
cargo test başarısız olursa job hemen durur ❌.clippy uyarıyı hata yapar, bu da “code quality” kapısını kapatır 🔒.fmt --check formatlama hatası varsa kırmızı log basar 🎨.Run cargo test → Hangi test failed? test result: FAILED satırı ve stack trace’i gösterir.Run cargo clippy → warning: … satırları aslında error olarak listelenir (deny warnings sayesinde).Run cargo fmt → Diff: bloğu hangi dosyaların formatlanması gerektiğini gösterir.Pratik tavsiye:
cargo test -- --nocaptureile test loglarını terminalde görürsün, CI logunda da aynı çıktıyı bulursun 🕵️♂️.
GitLab kullanıyorsan .gitlab-ci.yml içine şu minimal tanımı ekleyebilirsin:
stages:
- test
rust:
stage: test
image: rust:latest
before_script:
- rustup component add rustfmt clippy
script:
- cargo build --verbose
- cargo test --all --verbose
- cargo clippy --all-targets --all-features -- -D warnings
- cargo fmt --all -- --check
Aynı mantık, farklı syntax 🎯.
Özetle:
cargo test / clippy / fmt alışkanlığını edin.Artık her push’ta “kodum çalışıyor mu?” diye merak etmezsin, pipeline sana cevap verir 🎉.
AI çevirilerde sık görülen hataları dört ana kategoriye ayırdım. Her kategori için tipik bir örnek, nasıl yakaladığımız (diff, derleyici hatası, test hatası) ve hızlı bir düzeltme ipucu paylaşıyorum. 🎯
move occurs because value has type ... veya cannot borrow as mutable gibi mesajlar verir.func foo(s string) { ... } → Rust’ta fn foo(s: String) { ... } (sahip olma değişir).error[E0382]: use of moved value: s.- // Go
- func process(data string) {
- fmt.Println(data)
- }
+ // Rust (yanlış)
+ fn process(data: String) {
+ println!("{}", data);
+ // data burada drop olur, çağırıcıya geri dönmez
+ }
Düzeltme: &str veya &String alarak borrow yapın. ✅
chan kullanımı Rust’ta std::sync::mpsc::channel ile çevrilirse, blocking davranış beklenmedik deadlock’a yol açar.thread 'main' panicked at 'channel closed' veya test zaman aşımına uğrar.go func() { ch <- 1 }() → thread::spawn(move || tx.send(1).unwrap()).- // Go
- ch := make(chan int)
- go func() { ch <- 42 }()
- fmt.Println(<-ch)
+ // Rust (yanlış – bounded channel olmadan)
+ let (tx, rx) = mpsc::channel();
+ thread::spawn(move || tx.send(42).unwrap());
+ println!("{}", rx.recv().unwrap()); // panic: channel closed
İpucu: Rust’ta crossbeam_channel::unbounded veya tokio::sync::mpsc tercih edin. 🔁
error döndürme alışkanlığı Rust’ta Result<T, E> ile karşılanmazsa, unwrap() kullanımı panic yapar.unused Result that must be used.panicked at 'called Result::unwrap() on an Err value'.- // Go
- func readFile(path string) ([]byte, error) { ... }
+ // Rust (yanlış)
+ fn read_file(path: &str) -> Vec<u8> {
+ std::fs::read(path).unwrap() // hata yok sayılır
+ }
Düzeltme: -> Result<Vec<u8>, std::io::Error> dönün ve ? operatörüyle yayılmasını sağlayın. ⛔
strings.Split → Rust str::split döndürür iterator, doğrudan Vec<&str> bekleyen kod derlenmez.expected struct Vec, found struct Split.mismatched types.- // Go
- parts := strings.Split("a,b,c", ",")
+ // Rust (yanlış)
+ let parts: Vec<&str> = "a,b,c".split(','); // hata: Split iterator
Düzeltme: .collect::<Vec<_>>() ekleyin. ✅
| Kategori | Tipik Hata | Tespit Yöntemi | Hızlı Çözüm |
|---|---|---|---|
| Ownership / Borrow | move / borrow hatası |
Compiler error | & / &mut kullan |
| Channel vs Mutex | Deadlock / panic | Test failure | Doğru channel kütüphanesi seç |
| Result / Option | unwrap() panic |
Warning + test panic | ? operatörü, Result dön |
| Std Lib Eşleşmesi | Tip uyuşmazlığı | Compiler error | .collect() / iterator adaptörleri |
Bu kategorileri aklınızda tutarsanız, AI’ın ürettiği Rust kodunu ilk derlemeden önce %90’ı filtreleyebilirsiniz. 🚀
Hazırsan başlayalım 🚀
Genelde AI bir kod ürettiğinde ilk denemede mükemmel çıkmaz. Ben de şöyle bir döngü kuruyorum:
Bu döngüyü bir Notion sayfası ya da GitHub Issues üzerinde takip ediyorum. Her iterasyon için bir issue açar, başlıkta Iterasyon #3 – Hata: … yazarak geçmişi görebiliyorum. 📋
1️⃣ İlk Prompt
Rust'ta bir vektördeki sayıları filtreleyen ve karelerini alan bir fonksiyon yaz.
Üretilen Kod (hatalı)
fn kareler(v: Vec<i32>) -> Vec<i32> {
v.iter()
.filter(|x| *x > 0) // sadece pozitifleri alıyor, negatifleri atlıyor
.map(|x| x * x)
.collect()
}
Ne oldu?
Negatif sayılar da karelenmeliydi ama filtreleme onları atıyor. ❗️
2️⃣ Geri Bildirim & Prompt Güncelleme
Benim notum:
“Negatif sayıları da kare al, filtrelemeyi kaldır.”
Yeni prompt:
Rust'ta bir vektördeki **tüm** sayıların karelerini alan bir fonksiyon yaz. Filtreleme yok.
3️⃣ Düzeltilmiş Kod
fn kareler(v: Vec<i32>) -> Vec<i32> {
v.into_iter() // 소유권 al, iter() yerine
.map(|x| x * x) // her elemanın karesini al
.collect()
}
Ne değişti?
iter() → into_iter() ile vektörün sahipliğini alıyoruz, performans biraz daha iyi.filter satırı silindi, artık tüm sayılar işleniyor. ✅| Araç | Ne İçin Kullanıyorum? |
|---|---|
| Notion | Her iterasyonun prompt/çıktı/hata notlarını tek sayfada topluyorum. |
| GitHub Issues | Resmi kayıt: Iterasyon #4 – Hata: … başlığıyla, kod diff’ini de comment olarak ekliyorum. |
| VS Code Tasks | cargo test ve cargo run komutlarını bir task’a bağlayıp tek tıkla tekrar çalıştırıyorum. ⚡ |
Bu yöntem sayesinde “Ben şu hatayı gördüm, prompta şunu ekledim” akışı şeffaf ve tekrarlanabilir oluyor. Bir sonraki iterasyonda aynı hatayı tekrar yaşamıyorum. 🎉
Kısaca:
Hadi pratik bir örnek üzerinden anlayalım. Bir başlangıç seviyesindeki yapay zeka ürünü geliştirmek için tahmini bütçeyi kaba kalemlere bölüp her kalemin altında neler sakladığına göz atalım.
| Kalem | Miktar | Birim Fiyatı | Toplam |
|---|---|---|---|
| Token maliyeti (API çağrıları) | 10.000 token | 0,002 $ | 20 $ |
| İnsan saat maliyeti (kendi zamanınız) | 25 saat | 12 $/saat | 300 $ |
| Araç abonelikleri (entegrasyon, CI/CD, hosting) | 3 araç | 30 $/ay | 90 $ |
| TOPLAM | – | – | 410 $ |
Token maliyeti 🎯
İnsan saat maliyeti 🛠
Araç abonelikleri 🔁
Tablo, tam olarak 400 $ bütçesi olan bir proje için kusursuz bir şablon. Miktarları, birim fiyatlarını veya ek öğeleri kendi ihtiyaçlarınıza göre düzenlemeniz yeterli.
Bu sayede ne oluyor? Eğer kendi zamanınıza yatırım yaparsanız, daha pahalı API çağrılarından tasarruf edebilirsiniz. Tam tersi de geçerlidir. Önemli olan bütçenizi kontrol altında tutmak.
Öncelikle şunu netleştirelim: 10k eşzamanlı istek testiyle Rust ile Go arasında nasıl bir performans farkı yakaladık. Aşağıda uyguladığımız test senaryolarını, ölçüm araçlarını ve rakamları göreceksiniz.
Aşağıda, istek işleyişini basitçe simüle eden bir Criterion benchmarku yer alıyor:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn handler(c: &mut Criterion) {
c.bench_function("rust_handler", |b| {
b.iter(|| {
// Basit bir işleyiş: bir veriyi toplamı ile çalış
let x = black_box(42u64);
let _ = x.wrapping_mul(2);
})
});
}
criterion_group!(benches, handler);
criterion_main!(benches);
Ne oluyor burada? Criterion, her iterasyonu gerçek bir zamanlayıcıyla ölçerek Rust işleyişinin saf CPU performansını gösteriyor.
black_boxoptimizasyonları önler, böylece sonuçlar gerçekçi olur.
Go tarafında benzeri basit bir işleyiş bench'ini çalıştırdık:
go test -bench=. -benchmem -count=5
# çıktıyı inceleyip pprof ile profile analiz ettik
go tool pprof -cpu=0 ./bench binary.pprof
Bu sayede ne oluyor? pprof, Go işleyişi sırasındaki CPU ve bellek kullanımını net bir şekilde gösteriyor.
| Dil | Throughput (req/s) | Latency p99 (ms) | Bellek (MB) |
|---|---|---|---|
| Go | 12.5k | 120 | 85 |
| Rust | 18.3k | 95 | 62 |
ASCII grafiği (criterion ile üretildi, basitleştirilmiş)
Go : ███████░░░░░░░░░░ ~12.5k req/s
Rust : ██████████████████ ~18.3k req/s
-C opt-level=3) önemli ölçüde yardımcı oluyor.Eğer criterion ile Rust benchmark'ı ilk kez çalıştırıyorsanız veya Go pprof ile karşılaştırmalı bir profil oluşturmayı planıyorsanız, yukarıdaki kodları klonlayıp çalıştırmayı deneyin. Küçük bir düzenlemeyle, kendi işleyişlerinizi de değerlendirebilirsiniz.
Proje boyunca kazandığımız en kritik ipuçları şunlar — her biri zamanımızı ve sinirlerimizi kurtardı 🙌
Eğer siz de büyük bir migrasyon planlıyorsak, önce şu 3 adımı atın 🚀
Bu üç adım, süreci kontrol edilebilir kılar ve sürprizleri en aza indirir ✅
Teşekkürler, burada olduğunuz için 🙏
Deneyimlerinizi, takıldığınız yerleri ya da “şu hata bana da oldu” anılarını yorumlarda paylaşırsanız hepimiz öğreniriz 💬
Unutmayın: Her büyük sistem, bir zamanlar küçük bir adımla başlamıştı. 🌱
Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.
All rights reserved