19 Nisan 2025

Rust Eğitimi: Başlangıç Rehberi

Rust, Stack Overflow anketlerinde üst üste yıllarca “en sevilen” programlama dili unvanını alarak geliştiricilerin ilgisini sürekli olarak çekmektedir. Bu sadece bir abartı değil; Rust, diğer sistem programlama dillerinde bulunan yaygın zorlukları ele alan performans, güvenlik ve modern dil özelliklerinin etkileyici bir karışımını sunar. Rust'ı özel kılan şeyin ne olduğunu merak ediyorsanız ve yolculuğunuza başlamak istiyorsanız, bu başlangıç kılavuzu size başlamak için temel bilgileri sağlar. Temel sözdizimini, sahiplik gibi benzersiz kavramları ve Rust ekosistemini güçlendiren temel araçları keşfedeceğiz.

Neden Rust Programlamayı Öğrenmelisiniz?

Rust, kendini güvenilir ve verimli yazılımlar oluşturmaya yönelik bir dil olarak konumlandırır. Başlıca avantajları, bir çöp toplayıcıya güvenmeden bellek güvenliği sağlaması ve korkusuz eşzamanlılığa olanak tanıması etrafında döner. İşte Rust öğrenmeyi düşünmeniz için nedenler:

  1. Performans: Rust doğrudan yerel makine koduna derlenir ve C ve C++ ile karşılaştırılabilir performans sunar. Bu hıza güvenlikten ödün vermeden ulaşır, bu da onu oyun motorları, işletim sistemleri, tarayıcı bileşenleri ve yüksek performanslı web hizmetleri gibi performans açısından kritik uygulamalar için uygun hale getirir.
  2. Bellek Güvenliği: Rust'ın amiral gemisi özelliği, ödünç alma ve yaşam süreleri ile tamamlanan sahiplik sistemidir. Bu sistem, bellek güvenliğini derleme zamanında garanti eder. C/C++ gibi dilleri sıklıkla rahatsız eden sallanan işaretçileri, arabellek taşmalarını veya veri yarışlarını unutun. Rust derleyicisi, kodunuz çalışmadan önce bu yaygın hataları önleyen katı bir bekçi görevi görür.
  3. Eşzamanlılık: Eşzamanlı programlama (birden çok görevi görünüşte aynı anda çalıştırma) doğru yapmak herkesin bildiği gibi zordur. Rust bununla doğrudan mücadele eder. Sahiplik ve tip sistemleri, veri yarışlarını derleme zamanında önlemek için birlikte çalışarak çok iş parçacıklı uygulamalar yazmayı önemli ölçüde daha kolay ve daha güvenli hale getirir. Bu “korkusuz eşzamanlılık”, geliştiricilerin modern çok çekirdekli işlemcilerden yaygın tuzaklara düşmeden etkili bir şekilde yararlanmalarını sağlar.
  4. Modern Araçlar: Rust, temel deneyime entegre edilmiş olağanüstü bir paket yöneticisi ve derleme aracı olan Cargo ile birlikte gelir. Cargo, bağımlılık yönetimini, test etmeyi, derlemeyi ve crate'lerin (Rust'ın paketler veya kütüphaneler için kullandığı terim) yayınlanmasını sorunsuz bir şekilde hallederek tüm geliştirme iş akışını kolaylaştırır.
  5. Büyüyen Ekosistem ve Topluluk: Rust topluluğu canlı, aktif ve yeni gelenlere karşı misafirperver olmasıyla bilinir. Kütüphane (crate) ekosistemi, web geliştirmeden (Actix, Rocket, Axum gibi çerçeveler) ve ağ oluşturmadan (asenkron işlemler için Tokio gibi) gömülü sistemlere, veri bilimine ve komut satırı araçlarına kadar çeşitli alanları kapsayacak şekilde hızla genişlemektedir.

Rust Programlamayı Öğrenin: Temel Araçlar

İlk Rust kod satırınızı yazmadan önce, Rust araç zincirini kurmanız gerekir. Rust'ı kurmanın standart ve önerilen yolu, Rust araç zinciri yükleyicisi olan rustup kullanmaktır.

  1. rustup: Bu komut satırı aracı, Rust kurulumlarınızı yönetir. Farklı Rust sürümlerini (kararlı, beta veya nightly sürümleri gibi) kurmanıza, güncellemenize ve bunlar arasında kolayca geçiş yapmanıza olanak tanır. İşletim sisteminize özel kurulum talimatları için resmi Rust web sitesini (https://www.rust-lang.org/tools/install) ziyaret edin.
  2. rustc: Bu, Rust derleyicisidir. Rust kaynak kodunuzu .rs dosyalarına yazdıktan sonra, rustc bunları bilgisayarınızın anlayabileceği çalıştırılabilir ikili dosyalara veya kütüphanelere derler. Kritik olmasına rağmen, günlük iş akışınızda rustc'yi doğrudan çok sık çağırmazsınız.
  3. cargo: Bu, Rust'ın derleme sistemi ve paket yöneticisidir ve en sık etkileşimde bulunacağınız araçtır. Cargo, birçok yaygın geliştirme görevini düzenler:
    • Yeni projeler oluşturma (cargo new).
    • Projenizi derleme (cargo build).
    • Projenizi çalıştırma (cargo run).
    • Otomatik testleri çalıştırma (cargo test).
    • Bağımlılıkları yönetme (Cargo.toml dosyanızda listelenen harici kütüphaneleri veya crate'leri otomatik olarak alma ve derleme).
    • Başkalarının kullanması için kendi crate'lerinizi crates.io'ya (merkezi Rust paket deposu) yayınlama.

Yerel bir kuruluma ihtiyaç duymadan hızlı denemeler yapmak için, resmi Rust Playground gibi çevrimiçi platformlar veya Replit gibi entegre geliştirme ortamları mükemmel seçeneklerdir.

İlk Rust Programınız: Merhaba Rust!

Herhangi bir yeni dili öğrenmek için bir geçiş töreni olan geleneksel “Merhaba, dünya!” programıyla başlayalım. main.rs adında bir dosya oluşturun ve aşağıdaki kodu ekleyin:

fn main() {
    // Bu satır konsola metin yazdırır
    println!("Merhaba Rust!");
}

Bu basit programı temel araçları kullanarak derlemek ve çalıştırmak için:

  1. Derleme: Terminalinizi veya komut istemcinizi açın, main.rs dosyasını içeren dizine gidin ve şunu çalıştırın:
    rustc main.rs
    
    Bu komut, çalıştırılabilir bir dosya (örneğin, Linux/macOS'ta main, Windows'ta main.exe) oluşturan Rust derleyicisini (rustc) çağırır.
  2. Çalıştırma: Derlenmiş programı terminalinizden yürütün:
    ./main
    # Windows'ta şunu kullanın: .\main.exe
    

Ancak, tek bir dosyanın ötesindeki her şey için Cargo kullanmak standart ve çok daha kullanışlı bir yaklaşımdır:

  1. Yeni bir Cargo projesi oluşturun: Terminalinizde şunu çalıştırın:
    cargo new merhaba_rust
    cd merhaba_rust
    
    Cargo, merhaba_rust adında yeni bir dizin oluşturur; bu dizin, içinde main.rs bulunan (zaten “Merhaba Rust!” koduyla doldurulmuş) bir src alt dizinini ve Cargo.toml adında bir yapılandırma dosyasını içerir.
  2. Cargo ile Çalıştırın: Basitçe şunu çalıştırın:
    cargo run
    
    Cargo, derleme adımını halledecek ve ardından ortaya çıkan programı çalıştırarak konsolunuzda “Merhaba Rust!” görüntüleyecektir.

Kod parçacığını inceleyelim:

  • fn main(): Bu, ana fonksiyonu tanımlar. fn anahtar kelimesi bir fonksiyon bildirimini belirtir. main özel bir fonksiyon adıdır; her çalıştırılabilir Rust programının çalışmaya başladığı giriş noktasıdır. Parantezler (), bu fonksiyonun girdi parametresi almadığını gösterir.
  • {}: Süslü parantezler bir kod bloğunu veya kapsamı tanımlar. Fonksiyona ait tüm kod bu parantezlerin içine girer.
  • println!("Merhaba Rust!");: Bu satır, konsola metin yazdırma eylemini gerçekleştirir.
    • println! bir Rust makrosudur. Makrolar fonksiyonlara benzer ancak önemli bir farkları vardır: ünlem işareti ! ile biterler. Makrolar derleme zamanı kod üretimi gerçekleştirerek normal fonksiyonlardan daha fazla güç ve esneklik sunar (println!'in yaptığı gibi değişken sayıda argümanı işlemek gibi). println! makrosu sağlanan metni konsola yazdırır ve sonuna otomatik olarak bir yeni satır karakteri ekler.
    • "Merhaba Rust!" bir dizi (string) sabitidir – çift tırnak içine alınmış, metni temsil eden sabit bir karakter dizisidir.
    • ;: Noktalı virgül, ifadenin sonunu işaretler. Çalıştırılabilir Rust kodunun çoğu satırı (ifadeler) noktalı virgülle biter.

Yeni Başlayanlar İçin Rust Eğitimi: Temel Kavramlar

Şimdi, Rust programlama dilinin temel yapı taşlarına dalalım.

Değişkenler ve Değişebilirlik

Değişkenler, veri değerlerini saklamak için kullanılır. Rust'ta değişkenleri let anahtar kelimesiyle bildirirsiniz.

let elmalar = 5;
let mesaj = "Beş tane al";

Rust'ta temel bir kavram, değişkenlerin varsayılan olarak değişmez olmasıdır. Bu, bir değer bir değişken adına bağlandıktan sonra, o değeri daha sonra değiştiremeyeceğiniz anlamına gelir.

let x = 10;
// x = 15; // Bu satır derleme zamanı hatasına neden olur! Değişmez `x` değişkenine iki kez atama yapılamaz.
println!("x'in değeri: {}", x); // {} x'in değeri için bir yer tutucudur

Bu varsayılan değişmezlik, yaygın bir hata kaynağı olabilen verilerin kazara değiştirilmesini önleyerek daha güvenli, daha öngörülebilir kod yazmanıza yardımcı olan kasıtlı bir tasarım seçimidir. Değeri değişebilen bir değişkene ihtiyacınız varsa, bildirim sırasında onu mut anahtar kelimesini kullanarak açıkça değişebilir olarak işaretlemelisiniz.

let mut sayac = 0; // 'sayac'ı değişebilir olarak bildir
println!("Başlangıç sayacı: {}", sayac);
sayac = 1; // 'sayac' 'mut' ile bildirildiği için buna izin verilir
println!("Yeni sayaç: {}", sayac);

Rust ayrıca gölgelemeye izin verir. Aynı kapsam içinde önceki bir değişkenle aynı ada sahip yeni bir değişken bildirebilirsiniz. Yeni değişken eskisini “gölgeler”, yani adın sonraki kullanımları yeni değişkene atıfta bulunur. Bu, tamamen yeni bir değişken oluşturduğumuz için mutasyondan farklıdır ve bu değişkenin farklı bir tipi bile olabilir.

let bosluklar = "   "; // 'bosluklar' başlangıçta bir dizi dilimidir (&str)
let bosluklar = bosluklar.len(); // 'bosluklar' şimdi uzunluğu tutan yeni bir değişken tarafından gölgeleniyor (bir tam sayı, usize)
println!("Boşluk sayısı: {}", bosluklar); // Tam sayı değerini yazdırır

Temel Veri Tipleri

Rust statik tipli bir dildir. Bu, her değişkenin tipinin derleme zamanında derleyici tarafından bilinmesi gerektiği anlamına gelir. Ancak, Rust'ın mükemmel tip çıkarımı vardır. Çoğu durumda, tipi açıkça yazmanız gerekmez; derleyici genellikle değere ve onu nasıl kullandığınıza göre bunu anlayabilir.

let miktar = 10;         // Derleyici i32 (varsayılan işaretli tam sayı tipi) çıkarımı yapar
let fiyat = 9.99;          // Derleyici f64 (varsayılan kayan noktalı sayı tipi) çıkarımı yapar
let aktif = true;         // Derleyici bool (boolean) çıkarımı yapar
let bas_harf = 'R';         // Derleyici char (karakter) çıkarımı yapar

Açık olmak isterseniz veya buna ihtiyacınız varsa (örneğin, netlik için veya derleyicinin yardıma ihtiyacı olduğunda), iki nokta üst üste : ve ardından tip adını kullanarak tip belirtimleri sağlayabilirsiniz.

let skor: i32 = 100;       // Açıkça işaretli 32-bit tam sayı
let oran: f32 = 0.5;       // Açıkça tek duyarlıklı kayan noktalı sayı
let tamamlandi_mi: bool = false; // Açıkça boolean
let not: char = 'A';      // Açıkça bir karakter (Unicode skaler değeri)

Rust'ın birkaç yerleşik skaler tipi (tek değerleri temsil eden) vardır:

  • Tam Sayılar: İşaretli tam sayılar (i8, i16, i32, i64, i128, isize) hem pozitif hem de negatif tam sayıları saklar. İşaretsiz tam sayılar (u8, u16, u32, u64, u128, usize) yalnızca negatif olmayan tam sayıları saklar. isize ve usize tipleri bilgisayarın mimarisine (32-bit veya 64-bit) bağlıdır ve öncelikle koleksiyonları indekslemek için kullanılır.
  • Kayan Noktalı Sayılar: f32 (tek duyarlıklı) ve f64 (çift duyarlıklı). Varsayılan f64'tür.
  • Booleanlar: bool tipinin iki olası değeri vardır: true veya false.
  • Karakterler: char tipi tek bir Unicode skaler değerini temsil eder (sadece ASCII karakterlerinden daha kapsamlıdır), tek tırnak içine alınır (örneğin, 'z', 'π', '🚀').

Diziler: String vs &str

Rust'ta metin işlemek genellikle yeni başlayanlar için kafa karıştırıcı olabilen iki ana tipi içerir:

  1. &str (“dizi dilimi” olarak telaffuz edilir): Bu, bellekte bir yerde saklanan UTF-8 kodlu bayt dizisine değişmez bir referanstır. Dizi sabitleri ( "Merhaba" gibi) &'static str tipindedir, yani doğrudan programın ikili dosyasında saklanırlar ve programın tüm süresi boyunca yaşarlar. Dilimler, dizi (string) verilerine sahip olmadan bir görünüm sağlar. Boyutları sabittir.
  2. String: Bu, sahiplenilmiş, büyüyebilir, değişebilir, UTF-8 kodlu bir dizi (string) tipidir. String verileri yığında (heap) saklanır, bu da yeniden boyutlandırılmasına olanak tanır. Genellikle dizi verilerini değiştirmeniz gerektiğinde veya dizinin kendi verilerine sahip olması ve kendi yaşam süresini yönetmesi gerektiğinde (genellikle fonksiyonlardan dizileri döndürürken veya bunları struct'larda saklarken) String kullanırsınız.
// Dizi sabiti (program ikili dosyasında saklanır, değişmez)
let statik_dilim: &'static str = "Ben değişmezim";

// Bir sabitten sahiplenilmiş, yığında ayrılmış bir String oluştur
let mut dinamik_dizi: String = String::from("Başla");

// String'i değiştir (değişebilir olduğu ve verilerine sahip olduğu için mümkün)
dinamik_dizi.push_str(" ve büyü");
println!("{}", dinamik_dizi); // Çıktı: Başla ve büyü

// String'in bir kısmına referans veren bir dizi dilimi oluştur
// Bu dilim, dinamik_dizi'den veri ödünç alır
let dizeden_dilim: &str = &dinamik_dizi[0..5]; // "Başla"ya referans verir
println!("Dilim: {}", dizeden_dilim);

Fonksiyonlar

Fonksiyonlar, kodu adlandırılmış, yeniden kullanılabilir birimler halinde düzenlemek için temeldir. Özel main fonksiyonuyla zaten karşılaştık.

// Fonksiyon tanımı
fn selamla(isim: &str) { // Bir parametre alır: 'isim', bir dizi dilimidir (&str)
    println!("Merhaba, {}!", isim);
}

// İki i32 parametresi alan ve bir i32 döndüren fonksiyon
fn topla(a: i32, b: i32) -> i32 {
    // Rust'ta, bir fonksiyon gövdesindeki son ifade,
    // noktalı virgülle bitmediği sürece otomatik olarak döndürülür.
    a + b
    // Bu şuna eşdeğerdir: return a + b;
}

fn main() {
    selamla("Alice"); // 'selamla' fonksiyonunu çağır

    let toplam = topla(5, 3); // 'topla'yı çağır, döndürülen değeri 'toplam'a bağla
    println!("5 + 3 = {}", toplam); // Çıktı: 5 + 3 = 8
}

Fonksiyonlarla ilgili önemli noktalar:

  • Fonksiyonları bildirmek için fn anahtar kelimesini kullanın.
  • Parametre adlarını ve tiplerini parantez içinde belirtin.
  • Fonksiyonun dönüş tipini bir ok -> işaretinden sonra belirtin. Bir fonksiyon değer döndürmezse, dönüş tipi örtük olarak ()'dır (boş bir demet, genellikle “birim tipi” olarak adlandırılır).
  • Fonksiyon gövdeleri bir dizi ifadeden (bir eylem gerçekleştiren, genellikle ; ile biten talimatlar) oluşur ve isteğe bağlı olarak bir deyim (bir değere değerlendirilen bir şey) ile biter.
  • Bir fonksiyon bloğundaki son deyimin değeri, noktalı virgülü yoksa otomatik olarak döndürülür. return anahtar kelimesi, fonksiyonun herhangi bir yerinden açık, erken dönüşler için kullanılabilir.

Kontrol Akışı

Rust, kodun hangi sırayla yürütüleceğini belirlemek için standart kontrol akış yapıları sağlar:

  • if/else/else if: Koşullu yürütme için kullanılır.
let sayi = 6;

if sayi % 4 == 0 {
    println!("sayı 4'e bölünebilir");
} else if sayi % 3 == 0 {
    println!("sayı 3'e bölünebilir"); // Bu dal yürütülür
} else {
    println!("sayı 4 veya 3'e bölünemez");
}

// Önemli olarak, 'if' Rust'ta bir deyimdir, yani bir değere değerlendirilir.
// Bu, onu doğrudan 'let' ifadelerinde kullanmanıza olanak tanır.
let kosul = true;
let deger = if kosul { 5 } else { 6 }; // deger 5 olacaktır
println!("Değer: {}", deger);
// Not: 'if' deyiminin her iki dalı da aynı tipe değerlendirilmelidir.
  • Döngüler: Tekrarlanan yürütme için kullanılır.
    • loop: Sonsuz bir döngü oluşturur. Döngüden çıkmak için genellikle break kullanırsınız, isteğe bağlı olarak bir değer döndürebilirsiniz.
    • while: Belirtilen bir koşul doğru kaldığı sürece döngü yapar.
    • for: Bir koleksiyonun veya bir aralığın öğeleri üzerinde yinelenir. Bu, Rust'ta en sık kullanılan ve genellikle en güvenli döngü türüdür.
// loop örneği
let mut sayac = 0;
let sonuc = loop {
    sayac += 1;
    if sayac == 10 {
        break sayac * 2; // Döngüden çık ve 'sayac * 2' döndür
    }
};
println!("Döngü sonucu: {}", sonuc); // Çıktı: Döngü sonucu: 20

// while örneği
let mut num = 3;
while num != 0 {
    println!("{}!", num);
    num -= 1;
}
println!("FIRLAT!!!");

// for örneği (bir dizi üzerinde yineleme)
let a = [10, 20, 30, 40, 50];
for eleman in a.iter() { // .iter() dizinin elemanları üzerinde bir yineleyici oluşturur
    println!("değer: {}", eleman);
}

// for örneği (bir aralık üzerinde yineleme)
// (1..4) 1, 2, 3'ü içeren bir aralık oluşturur (4 hariç)
// .rev() yineleyiciyi tersine çevirir
for sayi in (1..4).rev() {
    println!("{}!", sayi); // 3!, 2!, 1! yazdırır
}
println!("TEKRAR FIRLAT!!!");

Yorumlar

Derleyicinin yok sayacağı açıklamalar ve notlar eklemek için yorumları kullanın.

// Bu tek satırlık bir yorumdur. Satırın sonuna kadar uzanır.

/*
 * Bu çok satırlı, blok bir yorumdur.
 * Birkaç satıra yayılabilir ve daha uzun açıklamalar için
 * kullanışlıdır.
 */

 let sansli_sayi = 7; // Yorumları bir satırın sonuna da yerleştirebilirsiniz.

Sahipliği Anlamak: Rust'ın Temel Kavramı

Sahiplik, Rust'ın en belirgin ve merkezi özelliğidir. Rust'ın bir çöp toplayıcıya ihtiyaç duymadan derleme zamanında bellek güvenliğini garanti etmesini sağlayan mekanizmadır. Sahipliği kavramak, Rust'ı anlamanın anahtarıdır. Üç temel kuralı takip eder:

  1. Sahip: Rust'taki her değerin, sahibi olarak belirlenmiş bir değişkeni vardır.
  2. Tek Sahip: Belirli bir değerin herhangi bir zamanda yalnızca bir sahibi olabilir.
  3. Kapsam ve Bırakma: Sahip değişken kapsam dışına çıktığında (örneğin, bildirildiği fonksiyon sona erdiğinde), sahip olduğu değer bırakılır. Bu, belleğinin otomatik olarak serbest bırakıldığı anlamına gelir.
{ // s burada geçerli değil, henüz bildirilmedi
    let s = String::from("merhaba"); // s bu noktadan itibaren geçerlidir;
                                     // s yığında (heap) ayrılan String verisine 'sahiptir'.
    // s'yi burada kullanabilirsiniz
    println!("{}", s);
} // Kapsam burada sona erer. 's' artık geçerli değildir.
  // Rust, 's'nin sahip olduğu String için otomatik olarak özel bir 'drop' fonksiyonu çağırır,
  // yığın belleğini serbest bırakır.

Sahiplenilmiş bir değeri (bir String, Vec veya sahiplenilmiş tipler içeren bir struct gibi) başka bir değişkene atadığınızda veya değere göre bir fonksiyona geçirdiğinizde, sahiplik taşınır. Orijinal değişken geçersiz hale gelir.

let s1 = String::from("orijinal");
let s2 = s1; // String verisinin sahipliği s1'den s2'ye TAŞINDI.
             // s1 bu noktadan sonra artık geçerli sayılmaz.

// println!("s1: {}", s1); // Derleme zamanı hatası! Taşıma işleminden sonra değer burada ödünç alındı.
                           // s1 artık verilere sahip değil.
println!("s2: {}", s2); // s2 şimdi sahibidir ve geçerlidir.

Bu taşıma davranışı, iki değişkenin kapsam dışına çıktıklarında yanlışlıkla aynı bellek konumunu serbest bırakmaya çalıştığı “çift serbest bırakma” hatalarını önler. Tam sayılar, kayan noktalı sayılar, booleanlar ve karakterler gibi ilkel tipler Copy trait'ini uygular, yani atandıklarında veya fonksiyonlara geçirildiklerinde taşınmak yerine basitçe kopyalanırlar.

Ödünç Alma ve Referanslar

Bir fonksiyonun sahipliği devretmeden bir değeri kullanmasını isterseniz ne olur? Referanslar oluşturabilirsiniz. Bir referans oluşturmaya ödünç alma denir. Bir referans, başka bir değişkenin sahip olduğu verilere sahipliğini almadan erişmenizi sağlar.

// Bu fonksiyon bir String'e referans (&) alır.
// String'i ödünç alır ancak sahipliğini almaz.
fn uzunlugu_hesapla(s: &String) -> usize {
    s.len()
} // Burada, s (referans) kapsam dışına çıkar. Ancak String verisine sahip olmadığı için,
  // referans kapsam dışına çıktığında veriler BIRAKILMAZ.

fn main() {
    let s1 = String::from("merhaba");

    // '&' sembolünü kullanarak s1'e bir referans iletiyoruz.
    // s1 hala String verisine sahiptir.
    let len = uzunlugu_hesapla(&s1);

    // Sahiplik hiçbir zaman taşınmadığı için s1 burada hala geçerlidir.
    println!("'{}' uzunluğu {}.", s1, len);
}

Referanslar, tıpkı değişkenler gibi varsayılan olarak değişmezdir. Ödünç alınan verileri değiştirmek isterseniz, &mut ile gösterilen değişebilir bir referansa ihtiyacınız vardır. Ancak Rust, veri yarışlarını önlemek için değişebilir referanslar etrafında katı kurallar uygular:

Ödünç Alma Kuralları:

  1. Herhangi bir zamanda, şunlardan birine sahip olabilirsiniz:
    • Bir adet değişebilir referans (&mut T).
    • İstediğiniz sayıda değişmez referans (&T).
  2. Referanslar her zaman geçerli olmalıdır (işaret ettikleri verilerden daha uzun yaşayamazlar – bu, genellikle örtük olarak yaşam süreleri tarafından yönetilir).

Bu kurallar derleyici tarafından zorlanır.

// Bu fonksiyon bir String'e değişebilir bir referans alır
fn degistir(bir_dizi: &mut String) {
    bir_dizi.push_str(", dünya");
}

fn main() {
    // Değişebilir ödünç almaya izin vermek için 's' değişebilir olarak bildirilmelidir
    let mut s = String::from("merhaba");

    // Ödünç alma kuralı uygulamasının örneği (hataları görmek için satırların yorumunu kaldırın):
    // let r1 = &s; // değişmez ödünç alma - TAMAM
    // let r2 = &s; // başka bir değişmez ödünç alma - TAMAM (birden çok değişmez ödünç almaya izin verilir)
    // let r3 = &mut s; // HATA! Değişmez ödünç almalar etkinken `s` değişebilir olarak ödünç alınamaz
    //                  // Rust zorlar: ya birden çok okuyucu (&T) YA DA tek yazıcı (&mut T), asla ikisi birden olmaz
    // println!("{}, {}", r1, r2); // r1/r2'yi kullanmak onları aktif tutar ve hatayı tetikler
    //                             // Bu println olmadan, Rust'ın NLL'si (Non-Lexical Lifetimes) r1/r2'yi erken bırakırdı,
    //                             // &mut s'yi burada geçerli kılardı

    // Başka ödünç alma aktif olmadığı için burada değişebilir bir ödünç almaya izin verilir
    degistir(&mut s);
    println!("{}", s); // Çıktı: merhaba, dünya
}

Bileşik Veri Tipleri

Rust, birden çok değeri daha karmaşık tipler halinde gruplamanın yollarını sunar.

Struct'lar

Struct'lar (yapılar için kısaltma), ilgili veri alanlarını tek bir ad altında gruplayarak özel veri tipleri tanımlamanıza olanak tanır.

// User adında bir struct tanımla
struct User {
    active: bool,
    username: String, // Sahiplenilmiş String tipini kullanır
    email: String,
    sign_in_count: u64,
}

fn main() {
    // User struct'ının bir örneğini oluştur
    // Örnekler tüm alanlar için değer sağlamalıdır
    let mut user1 = User {
        email: String::from("biri@ornek.com"),
        username: String::from("birkullaniciadi123"),
        active: true,
        sign_in_count: 1,
    };

    // Struct alanlarına nokta gösterimi kullanarak eriş
    // Alan değerlerini değiştirmek için örnek değişebilir olmalıdır
    user1.email = String::from("baskaeposta@ornek.com");

    println!("Kullanıcı e-postası: {}", user1.email);

    // Bir User örneği oluşturmak için yardımcı bir fonksiyon kullanma
    let user2 = build_user(String::from("user2@test.com"), String::from("user2"));
    println!("User 2 aktif durumu: {}", user2.active);

    // Struct güncelleme sözdizimi: Kalan alanlar için mevcut bir örnekten
    // bazı alanları kullanarak yeni bir örnek oluşturur.
    let user3 = User {
        email: String::from("user3@domain.com"),
        username: String::from("user3"),
        ..user2 // 'active' ve 'sign_in_count' değerlerini user2'den alır
    };
    println!("User 3 oturum açma sayısı: {}", user3.sign_in_count);
}

// Bir User örneği döndüren fonksiyon
fn build_user(email: String, username: String) -> User {
    User {
        email, // Alan başlatma kısayolu: parametre adı alan adıyla eşleşiyorsa
        username,
        active: true,
        sign_in_count: 1,
    }
}

Rust ayrıca demet struct'ları (adlandırılmış demetler, örn. struct Color(i32, i32, i32);) ve birim benzeri struct'ları (hiçbir alanı olmayan ve bir tip üzerinde bir trait uygulamanız gerektiğinde ancak herhangi bir veri saklamanız gerekmediğinde kullanışlıdır, örn. struct AlwaysEqual;) destekler.

Enum'lar

Enum'lar (numaralandırmalar), olası varyantlarını numaralandırarak bir tip tanımlamanıza olanak tanır. Bir enum değeri yalnızca olası varyantlarından biri olabilir.

// IP adresi türlerini tanımlayan basit enum
enum IpAddrKind {
    V4, // Varyant 1
    V6, // Varyant 2
}

// Enum varyantları ilişkili veriler de tutabilir
enum IpAddr {
    V4(u8, u8, u8, u8), // V4 varyantı dört u8 değeri tutar
    V6(String),         // V6 varyantı bir String tutar
}

// Rust'ın standart kütüphanesinde çok yaygın ve önemli bir enum: Option<T>
// Mevcut olabilecek veya olmayabilecek bir değer kavramını kodlar.
// enum Option<T> {
//     Some(T), // T tipinde bir değerin varlığını temsil eder
//     None,    // Bir değerin yokluğunu temsil eder
// }

// Başka bir önemli standart kütüphane enum'u: Result<T, E>
// Başarılı (Ok) veya başarısız (Err) olabilecek işlemler için kullanılır.
// enum Result<T, E> {
//     Ok(T),   // Başarıyı temsil eder, T tipinde bir değer içerir
//     Err(E),  // Başarısızlığı temsil eder, E tipinde bir hata değeri içerir
// }

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    // İlişkili verilerle IpAddr enum'unun örneklerini oluşturma
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));

    // Option<T> ile örnek
    let some_number: Option<i32> = Some(5);
    let no_number: Option<i32> = None;

    // 'match' kontrol akışı operatörü enum'larla çalışmak için mükemmeldir.
    // Enum varyantına göre farklı kod yürütmenizi sağlar.
    match some_number {
        Some(i) => println!("Bir sayı alındı: {}", i), // Eğer Some ise, iç değeri i'ye bağla
        None => println!("Hiçbir şey alınmadı."),         // Eğer None ise
    }

    // 'match' kapsamlı olmalıdır: tüm olası varyantları ele almalısınız.
    // Alt çizgi '_' açıkça listelenmeyen varyantları yakalamak için
    // bir joker karakter deseni olarak kullanılabilir.
}

Option<T> ve Result<T, E>, Rust'ın potansiyel olarak eksik değerleri ve hataları sağlam bir şekilde ele alma yaklaşımının merkezinde yer alır.

Cargo'ya Giriş: Rust Derleme Aracı ve Paket Yöneticisi

Cargo, Rust projelerini derleme, test etme ve yönetme sürecini kolaylaştıran Rust ekosisteminin vazgeçilmez bir parçasıdır. Geliştiricilerin sıklıkla övdüğü özelliklerden biridir.

  • Cargo.toml: Bu, Rust projenizin manifest dosyasıdır. TOML (Tom's Obvious, Minimal Language) formatında yazılmıştır. Projeniz hakkında temel meta verileri (adı, sürümü ve yazarları gibi) ve en önemlisi bağımlılıklarını (projenizin güvendiği diğer harici crate'ler) listeler.
    [package]
    name = "my_project"
    version = "0.1.0"
    edition = "2021" # Kullanılacak Rust sürümünü belirtir (dil özelliklerini etkiler)
    
    # Bağımlılıklar aşağıda listelenir
    [dependencies]
    # Örnek: Rastgele sayı üretimi için 'rand' crate'ini ekleyin
    # rand = "0.8.5"
    # Derlediğinizde, Cargo 'rand' ve bağımlılıklarını indirip derleyecektir.
    
  • cargo new <proje_adi>: Yeni bir ikili (çalıştırılabilir) uygulama proje yapısı oluşturur.
  • cargo new --lib <kutuphane_adi>: Yeni bir kütüphane (diğer programlar tarafından kullanılması amaçlanan crate) proje yapısı oluşturur.
  • cargo build: Projenizi ve bağımlılıklarını derler. Varsayılan olarak, optimize edilmemiş bir hata ayıklama derlemesi oluşturur. Çıktı target/debug/ dizinine yerleştirilir.
  • cargo build --release: Projenizi optimizasyonlar etkinleştirilmiş olarak derler, dağıtım veya performans testi için uygundur. Çıktı target/release/ dizinine yerleştirilir.
  • cargo run: İkili projenizi derler (gerekirse) ve çalıştırır.
  • cargo check: Kodunuzu derleme hataları açısından hızlıca kontrol eder, ancak son çalıştırılabilir dosyayı üretmez. Bu genellikle cargo build'dan çok daha hızlıdır ve hızlı geri bildirim için geliştirme sırasında kullanışlıdır.
  • cargo test: Projeniz içinde tanımlanan tüm testleri çalıştırır (genellikle src dizininde veya ayrı bir tests dizininde bulunur).

Cargo.toml dosyanıza bir bağımlılık eklediğinizde ve ardından cargo build veya cargo run gibi bir komut çalıştırdığınızda, Cargo merkezi depo crates.io'dan gerekli crate'i (ve onun bağımlılıklarını) indirmeyi ve her şeyi birlikte derlemeyi otomatik olarak halleder.

Rust Yolculuğunuzdaki Sonraki Adımlar

Bu başlangıç kılavuzu, başlamanız için mutlak temelleri kapsamıştır. Rust dili, ilerledikçe keşfedilecek çok daha güçlü özellikler sunar:

  • Hata Yönetimi: Result enum'unda ustalaşma, ? operatörünü kullanarak hata yayma ve özel hata tipleri tanımlama.
  • Koleksiyonlar: Vec<T> (dinamik diziler/vektörler), HashMap<K, V> (hash haritaları), HashSet<T> vb. gibi yaygın veri yapılarıyla etkili bir şekilde çalışma.
  • Jenerikler: Tip parametreleri (<T>) kullanarak farklı veri tipleri üzerinde çalışabilen esnek, yeniden kullanılabilir kod yazma.
  • Trait'ler: Tiplerin uygulayabileceği paylaşılan davranışları ve arayüzleri tanımlama (diğer dillerdeki arayüzlere benzer, ancak daha güçlüdür).
  • Yaşam Süreleri: Rust'ın referansların her zaman geçerli olmasını nasıl sağladığını anlama, bazen açık yaşam süresi belirtimleri ('a) gerektirir.
  • Eşzamanlılık: İş parçacıklarını, mesajlaşma için kanalları, Mutex ve Arc ile paylaşılan durumu ve Rust'ın asenkron programlama için güçlü async/await sözdizimini keşfetme.
  • Makrolar: Gelişmiş derleme zamanı kod üretimi için kendi makrolarınızı yazmayı öğrenme.
  • Modüller: Daha büyük projeleri mantıksal birimler halinde organize etme, öğelerin görünürlüğünü (genel/özel) kontrol etme.
  • Test Etme: Rust'ın yerleşik test çerçevesini kullanarak etkili birim, entegrasyon ve belge testleri yazma.

Sonuç

Rust benzersiz ve ilgi çekici bir öneri sunar: C++ gibi alt seviye dillerden beklenen ham performans, özellikle bellek yönetimi ve eşzamanlılık etrafındaki yaygın hataların tüm sınıflarını ortadan kaldıran güçlü derleme zamanı güvenlik garantileriyle birleştirilmiştir. Öğrenme eğrisi, sahiplik ve ödünç alma gibi yeni kavramları içselleştirmeyi (ve bazen katı olan derleyiciyi dinlemeyi) içerse de, bunun karşılığı son derece güvenilir, verimli ve uzun vadede bakımı genellikle daha kolay olan yazılımdır. Cargo aracılığıyla mükemmel araçları ve destekleyici topluluğu ile Rust, öğrenmesi ödüllendirici bir dildir.

Küçük başlayın, Cargo kullanarak deneyler yapın, derleyicinin yardımcı (bazen ayrıntılı olsa da) hata mesajlarını rehberlik olarak benimseyin ve bu güçlü ve giderek daha popüler hale gelen dilde ustalaşma yolunda emin adımlarla ilerleyeceksiniz. Mutlu Rustlamalar!

İlgili makaleler