Rust ile modern yazılım geliştirmeye adım atın. Bu kapsamlı rehber, dilin temel özelliklerini, kurulumunu ve ilk uygulamanızı adım adım öğrenmenizi sağlayarak güvenli ve performanslı sistemler oluşturmanız için ilk adımı atmanızı sağlayacak.
Günümüz yazılım dünyasında performans, güvenlik ve eşzamanlılık kritik öneme sahiptir. Bu bağlamda, Rust programlama dili, özellikle sistem programlama alanında güçlü bir alternatif olarak öne çıkmaktadır. Peki, Rust tam olarak nedir ve neden bu kadar büyük bir ilgi görmektedir?
Rust, Mozilla tarafından geliştirilen, derlenmiş, statik olarak tipleştirilmiş bir sistem programlama dilidir. Geleneksel olarak C veya C++ gibi dillerin hüküm sürdüğü alanlarda, yani işletim sistemleri, oyun motorları, web sunucuları ve gömülü sistemler gibi kaynak kısıtlı ve yüksek performans gerektiren uygulamalarda kullanılmak üzere tasarlanmıştır. Ancak Rust’ı rakiplerinden ayıran en temel özellik, çalışma zamanı (runtime) bir çöp toplayıcıya (Garbage Collector – GC) ihtiyaç duymadan bellek güvenliğini garanti etmesidir. Bu sayede, hem C/C++’ın sağladığı düşük seviyeli kontrol ve performans avantajlarını sunar hem de yaygın bellek hataları (null pointer dereferencing, veri yarışları, buffer overflow gibi) riskini ortadan kaldırır. Bu, özellikle büyük ölçekli ve kritik öneme sahip sistemlerin geliştirilmesinde geliştiricilere muazzam bir güven ve verimlilik sağlar. Örneğin, Cloudflare, Discord ve hatta Firefox’un bazı çekirdek bileşenleri, performans ve güvenlik ihtiyaçları nedeniyle Rust’a geçiş yapmıştır. Dolayısıyla, Rust yalnızca bir programlama dili değil, aynı zamanda yazılım geliştirmeye yeni bir yaklaşım getiren bir felsefedir: performanstan ödün vermeden güvenlik. Bellek yönetimi konusunda sunduğu yenilikçi “ownership” (sahiplik) ve “borrowing” (ödünç alma) sistemleri sayesinde, derleyici, potansiyel bellek hatalarını daha kod yazılırken yakalar ve derleme aşamasında uyarır. Bu da, yazılımın daha dayanıklı ve öngörülebilir olmasını sağlar. Ayrıca, güçlü tip sistemi ve desen eşleştirme (pattern matching) yetenekleri ile kodun okunabilirliğini ve bakımını kolaylaştırırken, hata ayıklama süreçlerini de basitleştirir. Rust topluluğu da oldukça aktiftir ve dilin sürekli gelişimine katkıda bulunur, bu da onu öğrenmek ve kullanmak için cazip bir seçenek haline getirir. Öte yandan, eşzamanlı programlama konusunda da yerleşik araçlar sunarak çok iş parçacıklı uygulamaların güvenli bir şekilde yazılmasına olanak tanır. Tüm bu faktörler, Rust’ı modern yazılım mühendisliğinin temel taşlarından biri yapmaktadır.
Rust Geliştirme Ortamı Bilgisayarınıza Nasıl Kurulur?
Rust ile programlamaya başlamak için ilk adım, geliştirme ortamınızı kurmaktır. Bu süreç oldukça basittir ve genellikle “rustup” adı verilen bir araç ile yönetilir. Rustup, Rust derleyicisini (rustc), Cargo (paket yöneticisi ve build sistemi) ve diğer araçları tek bir komutla kurmanızı sağlar. Bu bölümde, işletim sisteminize göre Rust’ı nasıl kuracağınızı ve ilk projenizi nasıl oluşturacağınızı adım adım inceleyeceğiz. İlk olarak, bir terminal veya komut istemcisi açarak kuruluma başlamamız gerekiyor. Bu, Windows’ta PowerShell veya Komut İstemi, macOS ve Linux’ta ise terminal uygulaması olabilir. Daha sonra, Rust’ın resmi web sitesindeki yönergeleri takip ederek rustup’ı kuracağız. Bu süreç, internet bağlantınızın hızına bağlı olarak birkaç dakika sürebilir. Kurulum tamamlandığında, rustup, Rust’ın varsayılan araç zincirini (compiler, standart kütüphane vb.) indirip bilgisayarınıza kuracaktır. Bu kurulum aynı zamanda Rust’ın paket yöneticisi ve build sistemi olan Cargo’yu da içerir ki bu, Rust projeleriyle çalışmak için hayati bir araçtır. Cargo sayesinde yeni projeler oluşturabilir, bağımlılıkları yönetebilir ve projenizi derleyip çalıştırabilirsiniz. Bu yapılandırma, geliştiricinin Rust ekosistemine sorunsuz bir şekilde entegre olmasını sağlar. Aşağıdaki adımlar, kurulum sürecini ve ilk Rust projenizi oluşturmayı detaylandırır.
Rustup Kullanarak Kurulum Adımları
- rustup Kurulumu: Terminalinizde aşağıdaki komutu çalıştırın:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Windows kullanıcıları için ise Rust'ın resmi web sitesinden (rust-lang.org)rustup-init.exedosyasını indirip çalıştırmak en kolay yoldur. Kurulum sırasında varsayılan seçenekleri (1) seçmeniz genellikle en uygunudur. - Ortam Değişkenlerinin Ayarlanması: Kurulum sonrası,
rustupgenellikle gerekli ortam değişkenlerini otomatik olarak ayarlar. Ancak, bazı durumlarda terminali kapatıp yeniden açmanız gerekebilir. - Kurulumu Doğrulama: Rust derleyicisinin ve Cargo'nun doğru bir şekilde kurulduğundan emin olmak için şu komutları çalıştırın:
rustc --version cargo --version
Bu komutlar, kurulu Rust ve Cargo sürümlerini ekrana yazmalıdır. - İlk Rust Projenizi Oluşturma: Rust projeleri için genellikle Cargo kullanılır. Yeni bir proje oluşturmak için:
cargo new hello_rust cd hello_rust
Bu komutlar,hello_rustadında yeni bir dizin oluşturur, bu dizinin içinesrcklasörünü veCargo.tomldosyasını yerleştirir.src/main.rsiçinde temel bir "Hello, world!" programı bulunur. - Projenizi Çalıştırma: Oluşturduğunuz projeyi çalıştırmak için
hello_rustdizininde aşağıdaki komutu kullanın:cargo run
Bu komut önce kodunuzu derler (eğer derlenmemişse) ve ardından çalıştırılabilir dosyayı çalıştırır. Terminalde "Hello, world!" çıktısını görmelisiniz.
Görsel betimleme olarak, bir terminal ekranında adım adım komutların çalıştırıldığını ve her komutun ardından gelen çıktıyı gösteren bir dizi ekran görüntüsü hayal edebilirsiniz. Özellikle cargo new hello_rust komutunun ardından oluşan dizin yapısı (hello_rust/src/main.rs ve hello_rust/Cargo.toml) net bir şekilde gösterilmelidir. Mobil uyumlu bir kurulum rehberi için, web sayfasının bu adımları küçük ekranlarda bile okunur ve takip edilebilir kılacak şekilde tasarlanması önemlidir. Örneğin, kod blokları için yatay kaydırma çubukları veya daha küçük font boyutları kullanılabilir.
@media screen and (max-width: 768px) {
.code-block {
overflow-x: auto;
white-space: nowrap;
font-size: 0.9em;
}
ol li {
padding-left: 10px;
}
}
rustup update komutu ile Rust kurulumunuzu her zaman güncel tutabilirsiniz.
Rust'ın Temel Sözdizimi ve Değişkenler Nasıl Çalışır?
Rust'ın geliştirme ortamını kurduktan sonra, dilin temel sözdizimini ve değişkenlerle nasıl çalıştığını anlamak, Rust yolculuğunuzda sağlam bir temel oluşturacaktır. Rust, güvenliği ve performansı vurgulayan belirli sözdizimi kurallarına sahiptir. Bu bölümde, fonksiyonların nasıl tanımlandığından, değişkenlerin nasıl beyan edildiğine, veri tiplerinden, mutasyona uğramazlık kavramına kadar temel yapı taşlarını ele alacağız. Bir Rust programı genellikle fn main() {} bloğu ile başlar; bu, programın yürütme noktasını temsil eder. Diğer çoğu dilde olduğu gibi, Rust'ta da kod satırları noktalı virgül (;) ile sona erer ve bu, ifadenin tamamlandığını gösterir. Ancak bazı kontrol akışı yapıları veya son ifadeler noktalı virgül gerektirmeyebilir. Özellikle makro çağrıları (örneğin println!) ünlem işareti ile belirtilir ve standart fonksiyonlardan ayırt edilir. Bu temel yapı, Rust'ın modüler ve organize bir şekilde kod yazma felsefesinin bir parçasıdır. Rust'ta değişken yönetimi ise diğer dillerden bazı önemli farklılıklar gösterir. Varsayılan olarak tüm değişkenler değişmezdir (immutable), yani bir kere değer atandıktan sonra değiştirilemezler. Bu, olası hataları önlemek ve kodun öngörülebilirliğini artırmak için bilinçli bir tasarım kararıdır. Eğer bir değişkenin değerini değiştirmek isterseniz, bunu açıkça mut anahtar kelimesiyle belirtmeniz gerekir. Bu yaklaşım, eşzamanlı programlamada veri yarışlarının ve beklenmedik yan etkilerin önlenmesine yardımcı olur.
Değişkenler ve Veri Tipleri
Rust'ta değişkenler let anahtar kelimesiyle tanımlanır. Örneğin:
fn main() {
let x = 5; // x değeri 5 olarak atanır ve değiştirilemez
println!("x'in değeri: {}", x);
}
Eğer bir değişkenin değerini değiştirmek istiyorsanız, mut anahtar kelimesini kullanmalısınız:
fn main() {
let mut y = 10; // y değeri 10 olarak atanır ve değiştirilebilir
println!("y'nin ilk değeri: {}", y);
y = 15;
println!("y'nin yeni değeri: {}", y);
}
Rust, statik tipleştirilmiş bir dil olduğundan, derleyici her değişkenin tipini derleme zamanında bilir. Çoğu zaman tip çıkarımı (type inference) sayesinde tipi açıkça belirtmenize gerek kalmaz, ancak bazen açıkça belirtmek gerekebilir:
fn main() {
let z: i32 = 20; // z, 32-bit işaretli tam sayı olarak belirtildi
let f: f64 = 3.14; // f, 64-bit float olarak belirtildi
}
Rust'ın temel veri tipleri aşağıdaki gibidir:
| Kategori | Tip | Açıklama |
|---|---|---|
| Tam Sayılar | i8, i16, i32, i64, i128, isize | İşaretli tam sayılar (boyutlarına göre) |
| Tam Sayılar | u8, u16, u32, u64, u128, usize | İşaretsiz tam sayılar (boyutlarına göre) |
| Kayan Noktalar | f32, f64 | Kayan noktalı sayılar (tek ve çift hassasiyet) |
| Boolean | bool | Mantıksal değerler (true/false) |
| Karakter | char | Unicode ölçekli tek bir karakter |
| Tuple | (T, U, ...) | Farklı tiplerde sabit sayıda değerin gruplandırılması |
| Diziler | [T; N] | Aynı tipten sabit uzunlukta değerlerin listesi |
Bir başka ilginç özellik "shadowing" (gölgeleme)dir. Rust'ta aynı isimde yeni bir değişken tanımlayarak önceki değişkeni gölgeleyebilirsiniz. Bu, değişkenin tipini veya mutasyonunu değiştirmek istediğiniz durumlarda kullanışlıdır:
fn main() {
let sayi = 5;
let sayi = sayi + 1; // sayi artık 6
let sayi = sayi * 2; // sayi artık 12
println!("Sayının son değeri: {}", sayi);
}
Bu, mut kullanmaktan farklıdır çünkü gölgeleme, aslında bellekte yeni bir değişken yaratır ve önceki değişkeni erişilemez hale getirir. Bu yaklaşım, değişkenin değerinin değiştiği ancak mantıksal olarak aynı kavramı temsil ettiği durumlarda kodun daha anlaşılır olmasını sağlar. Ayrıca, sabitler (const) de vardır. Sabitler const anahtar kelimesiyle tanımlanır, her zaman tipinin açıkça belirtilmesi gerekir ve sadece sabit ifadelere atanabilirler, yani çalışma zamanında hesaplanamazlar. Sabitler, mut ile işaretlenemezler ve gölgelenemezler. Örneğin:
const MAX_PUAN: u32 = 100_000; // Sabit tanımlama
Bu temel değişken ve tip bilgileri, Rust'ta daha karmaşık programlar yazarken size rehberlik edecektir. Rust'ın katı tip sistemi ve varsayılan değişmezlik yaklaşımı, başlangıçta alışılması zor gelebilir, ancak zamanla kodunuzu daha güvenli ve hatasız hale getirdiğini fark edeceksiniz. Özellikle büyük ölçekli uygulamalarda, bu prensiplerin faydaları daha belirgin hale gelir. Böylelikle, finansal bir hesaplayıcı gibi bir uygulamada kullanıcıdan alınan girdilerin (örneğin yaş veya miktar) farklı tiplerde saklanması ve gerektiğinde yeni hesaplamalarla gölgelenmesi, kodun hem temiz hem de güvenli kalmasını sağlar.
Kontrol Akışı: Koşullu İfadeler ve Döngüler Nasıl Kullanılır?
Her programlama dilinde olduğu gibi, Rust'ta da program akışını kontrol etmek için koşullu ifadeler ve döngüler temel yapı taşlarıdır. Bu yapılar, belirli koşullara bağlı olarak kodun farklı bölümlerini çalıştırmamızı veya belirli bir kod bloğunu tekrarlayan bir şekilde yürütmemizi sağlar. Rust, bu konularda diğer dillerle benzerlik gösterse de, kendine özgü bazı özellikler ve vurgular sunar. Özellikle match ifadesi, Rust'a özgü güçlü bir desen eşleştirme mekanizması sunar ve geleneksel if-else yapılarının ötesine geçer. Bu bölüm, Rust'taki if-else if-else yapılarını, match ifadesinin gücünü ve çeşitli döngü türlerini (loop, while, for) ayrıntılı örneklerle ele alacaktır. Koşullu ifadeler, programınızın belirli koşullar altında farklı kararlar vermesini sağlar. Örneğin, bir kullanıcının yaşına göre farklı mesajlar göstermek veya bir dosyanın varlığına göre farklı işlemler yapmak gibi senaryolarda kullanılırlar. Döngüler ise, belirli bir kod bloğunu tekrar tekrar çalıştırmak için kullanılır; bu, bir listedeki tüm öğeleri işlemek veya belirli bir koşul sağlanana kadar bir işlemi tekrarlamak gibi durumlar için idealdir. Rust'ta döngüler, performans ve güvenliği göz önünde bulundurularak tasarlanmıştır ve genellikle iteratörlerle birlikte kullanılır, bu da kodun hem verimli hem de okunabilir olmasını sağlar. Böylece, bir oyunun mantığını geliştirirken karakterin can durumu veya envanterindeki eşyaların türüne göre farklı aksiyonlar almasını sağlamak için kontrol akışı yapılarını etkin bir şekilde kullanabiliriz.
Koşullu İfadeler (if/else if/else ve match)
Rust'ta if ifadeleri, diğer dillerdeki gibi bir koşulun true veya false olmasına göre kod bloklarını çalıştırır:
fn main() {
let sayi = 7;
if sayi < 5 {
println!("Sayı 5'ten küçük.");
} else if sayi == 5 {
println!("Sayı 5'e eşit.");
} else {
println!("Sayı 5'ten büyük.");
}
// if ifadeleri değer döndürebilir!
let durum = if sayi % 2 == 0 {
"çift"
} else {
"tek"
};
println!("Sayı {}'dir.", durum);
}
En güçlü kontrol akışı araçlarından biri match ifadesidir. match, bir değeri bir dizi desenle karşılaştırır ve eşleşen desene ait kodu çalıştırır. Kapsamlıdır ve tüm olası durumları ele almayı zorunlu kılar, bu da potansiyel hataları azaltır. Desen eşleştirme, karmaşık senaryoları sadeleştirmek için harikadır.
fn main() {
let durum_kodu = 200;
match durum_kodu {
200 => println!("Başarılı (OK)"),
404 => println!("Bulunamadı (Not Found)"),
500 => println!("Sunucu Hatası (Internal Server Error)"),
_ => println!("Bilinmeyen durum kodu: {}", durum_kodu), // _ diğer tüm durumları kapsar
}
enum Renk {
Kırmızı,
Mavi,
Yeşil(u32, u32, u32), // Tuple struct
Sarı { r: u32, g: u32, b: u32 }, // Struct
}
let benim_rengim = Renk::Yeşil(0, 255, 0);
match benim_rengim {
Renk::Kırmızı => println!("Bu kırmızı renk."),
Renk::Mavi => println!("Bu mavi renk."),
Renk::Yeşil(r, g, b) => println!("Bu yeşil renk: R:{}, G:{}, B:{}", r, g, b),
Renk::Sarı { r, .. } => println!("Bu sarı renk, R değeri: {}", r), // Sadece r'yi al, diğerlerini önemseme
}
}
Döngüler (loop, while, for)
Rust'ta üç ana döngü türü vardır:
loop: Belirli bir koşul sağlanana veyabreakkomutu verilene kadar sonsuz döner. En temel döngüdür ve bir döngüden değer döndürmek için kullanılabilir.while: Bir koşultrueolduğu sürece çalışır. Koşulfalseolduğunda döngü sona erer.for: Belirli bir koleksiyonun veya sayı aralığının her öğesi üzerinde döner. Rust'ta genelliklerangeveya iteratörlerle kullanılır. Bu döngü, güvenli ve verimli olduğu için en çok tercih edilen döngü türüdür.
fn main() {
let mut sayac = 0;
let sonuc = loop {
sayac += 1;
if sayac == 10 {
break sayac * 2; // Döngüden değer döndürme
}
};
println!("Döngüden dönen sonuç: {}", sonuc); // Çıktı: 20
}
fn main() {
let mut sayi = 3;
while sayi != 0 {
println!("{}!", sayi);
sayi -= 1;
}
println!("BUM!");
}
fn main() {
let a = [10, 20, 30, 40, 50];
for eleman in a.iter() {
println!("Değer: {}", eleman);
}
// 1'den 4'e kadar (4 dahil değil)
for numara in 1..5 {
println!("Numara: {}", numara);
}
// 1'den 5'e kadar (5 dahil)
for numara in 1..=5 {
println!("Numara (dahil): {}", numara);
}
}
Döngüleri ve koşullu ifadeleri kullanarak, karmaşık bir oyunun durum yönetimini kolayca modelleyebilirsiniz. Örneğin, can değeri sıfırın altına düştüğünde oyunun bitmesini kontrol eden bir if ifadesi veya bir envanterdeki tüm eşyaları işlemek için bir for döngüsü kullanabilirsiniz. Hatta match ile belirli düşman türlerine karşı farklı stratejiler uygulamak mümkündür. Tüm bu yapılar, Rust'ın güçlü ve esnek bir dil olmasını sağlar. Özellikle break ve continue ifadeleri, döngülerin davranışını daha da hassas bir şekilde kontrol etmenize olanak tanır. break bir döngüyü tamamen sonlandırırken, continue döngünün mevcut iterasyonunu atlayıp bir sonrakine geçmesini sağlar. Bu kontrol mekanizmaları, daha karmaşık algoritmaları ve veri işleme görevlerini kolaylıkla uygulamanızı sağlar. Rust'ın bu zengin kontrol akışı özellikleri, geliştiricilere hem esneklik hem de yüksek performans sunar.
Fonksiyonlar ve Modüller ile Kodu Yapılandırmak Neden Önemlidir?
Büyük ve karmaşık yazılım projeleri geliştirirken, kodun okunabilir, sürdürülebilir ve yönetilebilir olması hayati önem taşır. Bu noktada, fonksiyonlar ve modüller gibi yapılandırma araçları devreye girer. Rust, bu araçları güçlü ve esnek bir şekilde kullanarak geliştiricilere kodlarını düzenli tutma imkanı sunar. Fonksiyonlar, belirli bir görevi yerine getiren kod bloklarını kapsüllerken, modüller bu fonksiyonları ve diğer ilgili öğeleri mantıksal gruplar halinde bir araya getirerek büyük projelerin mimarisini düzenler. Bu sayede, aynı kod parçasını tekrar tekrar yazmak yerine fonksiyonları çağırarak kod tekrarını önler ve hata yapma olasılığını azaltırız. Ayrıca, her fonksiyonun belirli bir işlevi olması, kodun anlaşılabilirliğini artırır ve ekip içinde çalışma süreçlerini kolaylaştırır. Özellikle ekip projelerinde, her geliştiricinin belirli bir modül üzerinde bağımsız çalışabilmesi, paralel geliştirme süreçlerini hızlandırır. Modüler bir yapı, kodun farklı bölümlerinin bağımsız olarak test edilmesine olanak tanır, bu da hata ayıklama süreçlerini basitleştirir ve yazılımın genel kalitesini artırır. Bu yaklaşım, sadece küçük betiklerde değil, işletim sistemleri veya web servisleri gibi devasa projelerde de temel bir gerekliliktir. Fonksiyonlar ve modüller, kodun içindeki karmaşıklığı soyutlayarak, her bir bileşenin sadece kendi sorumluluğuyla ilgilenmesini sağlar. Böylece, bir projenin büyümesiyle birlikte yönetilmesi zor bir hale gelmesi engellenmiş olur. Fonksiyonlar, girdileri alır, bir işleme tabi tutar ve bir çıktı döndürür. Modüller ise, bu fonksiyonları ve veri yapılarını hiyerarşik bir düzende organize eder. Bu yapılandırma, daha büyük ölçekli ve ekip tabanlı projelerde kritik bir rol oynar.
Fonksiyon Tanımlama ve Kullanımı
Rust'ta fonksiyonlar fn anahtar kelimesiyle tanımlanır. Fonksiyonlar parametre alabilir ve bir değer döndürebilir. Eğer bir fonksiyon değer döndürmüyorsa, varsayılan olarak () (birim tip) döndürür.
fn main() {
println!("Merhaba dünyadan!");
selamlama("Ali"); // Fonksiyon çağrısı
let toplam = topla(5, 7); // Değer döndüren fonksiyon çağrısı
println!("5 + 7 = {}", toplam);
}
// Parametre alan ve değer döndürmeyen fonksiyon
fn selamlama(isim: &str) {
println!("Merhaba, {}!", isim);
}
// Parametre alan ve değer döndüren fonksiyon
fn topla(sayi1: i32, sayi2: i32) -> i32 {
// Son ifade noktalı virgül ile bitmiyorsa, o ifade değer olarak döndürülür
sayi1 + sayi2
// Alternatif olarak: return sayi1 + sayi2;
}
Fonksiyonlarda değer döndürmenin iki yolu vardır: return anahtar kelimesi kullanmak veya fonksiyonun son ifadesinin değerini döndürmek. Rust'ta genellikle son ifadeyi döndürme yöntemi tercih edilir ve bu, fonksiyonun daha kısa ve okunabilir olmasını sağlar. Ayrıca, Rust fonksiyonları, ifadelere (expressions) ve deyimlere (statements) ayrılır. Deyimler bir değer döndürmezken (örneğin let x = 5;), ifadeler bir değer döndürür (örneğin x + 1). Bir fonksiyonun gövdesindeki son ifade, otomatik olarak fonksiyonun döndürdüğü değer olur. Bu ayrım, Rust'ın işlevsel programlama paradigmasından gelen güçlü bir özelliğidir ve kodu daha özlü hale getirir.
Modüller ile Kodu Yapılandırma
Modüller, Rust'ta kodunuzu mantıksal birimlere ayırmak için kullanılır. Büyük projelerde, tüm kodu tek bir dosyada tutmak yerine, benzer işlevlere sahip fonksiyonları, struct'ları ve enum'ları ayrı modüllerde toplamak iyi bir pratiktir. Bu, kodun keşfedilebilirliğini, yeniden kullanılabilirliğini ve sürdürülebilirliğini artırır. Modüller, mod anahtar kelimesiyle tanımlanır ve genellikle ayrı dosyalara yerleştirilir. Örneğin, src/main.rs dosyanız varsa ve src/matematik.rs adında bir modül oluşturmak istiyorsanız:
src/main.rs:
mod matematik; // matematik modülünü main.rs'e dahil et
fn main() {
println!("Ana fonksiyondan!");
let toplam = matematik::topla(10, 20);
println!("Toplam: {}", toplam);
// use ile modül içindeki öğeyi doğrudan kullanabiliriz
use matematik::carp;
let carpim = carp(5, 5);
println!("Çarpım: {}", carpim);
}
src/matematik.rs:
// Bu fonksiyon sadece bu modül içinde erişilebilir
fn ozel_fonksiyon() {
println!("Bu özel bir matematik fonksiyonu.");
}
// Bu fonksiyon dışarıdan erişilebilir (public)
pub fn topla(a: i32, b: i32) -> i32 {
a + b
}
// Bu fonksiyon da dışarıdan erişilebilir
pub fn carp(a: i32, b: i32) -> i32 {
a * b
}
Modüller, varsayılan olarak içerdikleri öğeleri (fn, struct, enum vb.) gizler. Bu öğelere dışarıdan erişmek için pub anahtar kelimesini kullanmanız gerekir. use anahtar kelimesi ise, başka bir modülün veya crate'in öğelerini mevcut kapsamınıza dahil etmenizi sağlar, böylece her seferinde tam yolu yazmak zorunda kalmazsınız. Modül ağacınız büyüdükçe, use ile daha temiz ve okunabilir kod yazabilirsiniz. Örneğin, bir web sunucusu uygulamasında, rotaları (routes), veritabanı işlemlerini (db), yardımcı fonksiyonları (utils) ve yapılandırma ayarlarını (config) ayrı modüllerde tutmak, projenin mimarisini çok daha anlaşılır hale getirir. Her modül kendi içinde tutarlı ve bağımsız bir birim olarak işlev görür. Bu, aynı zamanda, farklı bileşenler üzerinde çalışan geliştiricilerin birbirlerinin kodunu etkilemeden daha verimli çalışmasına olanak tanır. Modüllerin doğru bir şekilde yapılandırılması, Rust projelerinin uzun vadeli sürdürülebilirliği için kritik öneme sahiptir.
Rust'ta Hata Yönetimi: Panikleme mi, Sonuç mu Tercih Edilmeli?
Rust'ın en dikkat çekici özelliklerinden biri, güvenliğe verdiği önemdir ve bu, dilin hata yönetimi yaklaşımında açıkça görülür. Diğer dillerde sıkça karşılaşılan çalışma zamanı hatalarını (runtime errors) en aza indirmek için Rust, iki ana hata türünü ve bunlarla başa çıkma stratejisini benimser: kurtarılamaz hatalar (panic!) ve kurtarılabilir hatalar (Result). Hata yönetimi, robust (sağlam) ve güvenilir yazılımlar yazmak için kritik bir beceridir. Bu bölümde, bu iki yaklaşımı derinlemesine inceleyecek, ne zaman hangisini kullanmanız gerektiğine dair rehberlik edecek ve hata yayılımını (? operatörü) nasıl kolayca yöneteceğinizi göstereceğiz. Genellikle, bir programın tamamen çökmesini gerektiren ciddi, beklenmedik hatalar için panic! kullanılırken, beklenen ama kontrol altına alınabilecek durumlar için Result tercih edilir. Bu ayrım, Rust geliştiricisine kodunu yazarken hata senaryolarını düşünmeye zorlar ve bu da nihai ürünün çok daha kararlı olmasını sağlar. Örneğin, bir dosya okuma işlemi sırasında dosyanın bulunamaması veya ağ bağlantısının kesilmesi gibi durumlar kurtarılabilir hatalardır ve Result enum'ı ile ele alınmalıdır. Ancak, programın çekirdek mantığında meydana gelen ve uygulamanın tutarsız bir duruma düşmesine neden olan bir hata, genellikle panic! ile sonuçlanmalıdır. Bu bilinçli ayrım, Rust'ın "sıfır maliyetli soyutlamalar" felsefesinin bir parçasıdır ve geliştiricilere performanstan ödün vermeden güvenlik sağlar. Dolayısıyla, Rust'ta hata yönetimi sadece bir teknik detay değil, aynı zamanda güvenli ve dayanıklı yazılımlar oluşturmanın temel bir prensibidir. Bu bölümde, dosya I/O işlemleri gibi gerçek dünya senaryolarında bu mekanizmaların nasıl uygulandığını da göstereceğiz.
Kurtarılamaz Hatalar: Panikleme (panic!)
Kurtarılamaz hatalar, programın öngörülemeyen ve düzeltilemeyen bir duruma düştüğü anlamına gelir. Bu tür durumlarda Rust, panic! makrosu ile programın çökmesine neden olur. panic! genellikle, güvenli olmayan bir durumda kaldığında programın diğer kısımlarının hatalı veri ile çalışmasını engellemek için kullanılır. Bu durumlar genellikle mantıksal hatalar veya programın beklediği bir invariant'ın bozulmasıdır. Örneğin, bir dizinin dışına erişmeye çalışmak, varsayılan olarak panic! ile sonuçlanır:
fn main() {
let v = vec![1, 2, 3];
// Bu satır çalışma zamanında panic'e neden olur çünkü 100. indeks mevcut değil
// println!("100. indeks: {}", v[100]);
// Yorum satırı yapılmıştır, çünkü bu programı çökertecektir.
// Özel bir panic mesajı göndermek için:
// panic!("Beklenmedik bir hata oluştu ve program sonlandırılıyor!");
}
Genel olarak, panic! kullanımından mümkün olduğunca kaçınılmalı ve yalnızca geri döndürülemez hatalar için saklanmalıdır. Uygulamanın tutarlılığını veya güvenliğini ciddi şekilde tehlikeye atacak durumlar için uygundur. Özellikle kütüphane kodlarında panic! kullanmaktan kaçınmak, kütüphaneyi kullanan uygulamaların hata yönetimini kendilerinin yapmasına olanak tanır.
Kurtarılabilir Hatalar: Result Enum'ı
Çoğu hata durumu, kurtarılabilir hatalardır. Örneğin, bir dosya açmaya çalışırken dosyanın var olmaması veya bir ağ isteğinin başarısız olması gibi durumlar. Bu tür hatalar, Result adında özel bir enum ile temsil edilir. Result enum'ı iki varyanta sahiptir:
Ok(T): İşlem başarılı olduğunda veTtipinde bir değer döndürdüğünde.Err(E): İşlem başarısız olduğunda veEtipinde bir hata döndürdüğünde.
Dosya okuma örneğiyle Result kullanımını görelim:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let dosya_acma_sonucu = File::open("hello.txt");
let dosya = match dosya_acma_sonucu {
Ok(dosya) => dosya,
Err(hata) => match hata.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => {
println!("Dosya oluşturuldu ve açıldı.");
fc
},
Err(e) => panic!("Dosya oluşturulamadı: {:?}", e),
},
diger_hata => panic!("Dosya açarken başka bir hata oluştu: {:?}", diger_hata),
},
};
println!("Dosya başarıyla işlendi.");
}
Bu örnekte, match ifadesini kullanarak File::open fonksiyonundan dönen Result değerini kontrol ediyoruz. Eğer Ok ise, dosya handle'ını alıyoruz. Eğer Err ise, hatanın türüne bakarak farklı işlemler yapıyoruz (örneğin, dosya yoksa oluşturuyoruz). expect ve unwrap gibi kısayollar da vardır:
use std::fs::File;
fn main() {
// unwrap(): Eğer Result Ok ise değeri döndürür, Err ise panic yapar.
// Sadece hatanın oluşmayacağından eminseniz kullanın.
let _dosya_unwrap = File::open("var_olan_dosya.txt").unwrap();
// expect(): unwrap gibidir, ancak daha açıklayıcı bir panic mesajı sağlar.
// Hatanın oluşmayacağından emin olmadığınızda ama programın çökmesini kabul ettiğinizde kullanın.
let _dosya_expect = File::open("baska_dosya.txt")
.expect("baska_dosya.txt açılamadı. Uygulama devam edemez.");
}
Her ikisi de panic! çağırır, bu yüzden üretim kodunda dikkatli kullanılmalıdır. Genellikle geliştirme ve test aşamalarında veya hatanın gerçekten kurtarılamaz olduğu durumlarda tercih edilirler. Güvenli kod yazmak için match veya if let (daha kısa match formları) ile hata durumlarını açıkça ele almak en iyisidir. Özellikle hata yayılımı için Rust'ın sunduğu ? operatörü, Result enum'ı ile çalışmayı çok daha kolaylaştırır. Bir fonksiyon Result döndürdüğünde, ? operatörünü kullanarak başarılı değeri doğrudan alabilir, hata durumunda ise hatanın mevcut fonksiyon tarafından çağrıldığı yere otomatik olarak yayılmasını sağlayabilirsiniz. Bu, iç içe match ifadelerinden kaçınarak daha temiz bir hata yönetimi kodu yazmanıza olanak tanır.
use std::fs::File;
use std::io::{self, Read};
fn oku_dosya_icerigi() -> Result {
let mut dosya = File::open("merhaba.txt")?; // Hata varsa hemen döndür
let mut icerik = String::new();
dosya.read_to_string(&mut icerik)?; // Hata varsa hemen döndür
Ok(icerik)
}
fn main() {
match oku_dosya_icerigi() {
Ok(s) => println!("Dosya içeriği: {}", s),
Err(e) => println!("Dosya okuma hatası: {}", e),
}
}
Bu örnekte, ? operatörü, eğer File::open veya read_to_string bir Err döndürürse, bu hatayı oku_dosya_icerigi fonksiyonundan hemen döndürür. Eğer Ok dönerse, içerideki değeri alır ve işleme devam eder. Bu, hata yönetimini daha zarif ve okunabilir hale getirir. Rust'ta bu güçlü hata yönetimi mekanizmaları, uygulamanızın beklenmedik durumlarla güvenli bir şekilde başa çıkmasını sağlar. Özellikle kritik sistemlerde, bu tür sağlam hata işleme stratejileri, uygulamanın dayanıklılığı için vazgeçilmezdir.
Sonuç: Rust ile Geleceğin Yazılımını İnşa Etmek
Bu makalede, Rust programlama dilinin temellerini, neden bu kadar önemli olduğunu, geliştirme ortamının nasıl kurulduğunu, temel sözdizimini, değişkenleri, kontrol akışı mekanizmalarını ve hata yönetimini detaylı bir şekilde inceledik. Rust'ın benzersiz bellek güvenliği garantileri, üstün performansı ve eşzamanlı programlama yetenekleri, onu modern yazılım geliştirmenin en çekici dillerinden biri haline getirmektedir. ownership ve borrowing gibi kavramlar sayesinde derleyici seviyesinde güvenlik sağlarken, Cargo gibi araçlarla geliştirici deneyimini kolaylaştırır. Kontrol akışı yapıları, özellikle match ifadesinin gücü, karmaşık mantıkların zarif bir şekilde ifade edilmesini sağlar. Fonksiyonlar ve modüller, büyük projelerin düzenli ve sürdürülebilir bir şekilde yönetilmesi için kritik öneme sahiptir. Son olarak, panic! ve Result enum'ı ile Rust'ın hata yönetimi yaklaşımı, uygulamaların daha dayanıklı ve öngörülebilir olmasını garanti eder. Bu rehber, Rust ile yolculuğunuza başlamak için sağlam bir temel oluşturmuştur. Rust öğrenmek, başlangıçta bazı yeni kavramlara alışmayı gerektirse de, uzun vadede daha güvenli, daha hızlı ve daha güvenilir yazılımlar geliştirme becerisi kazandıracaktır. Bu nedenle, Rust'ın sunduğu fırsatları keşfetmeye devam etmeniz, kariyeriniz için önemli bir yatırım olacaktır. Önümüzdeki "Rust Part 2" makalesinde, sahiplik (ownership), ödünç alma (borrowing) ve ömür (lifetimes) gibi Rust'ın temel bellek yönetimi kavramlarına daha derinlemesine bakacağız. Bu kavramlar, Rust'ı gerçekten benzersiz kılan ve bellek güvenliği sorunlarını çöp toplama olmadan nasıl çözdüğünü açıklayan esas unsurlardır. Ayrıca, veri yapıları, traitler ve genericler gibi ileri düzey konulara da değinerek Rust'ın gücünü daha iyi anlamanızı sağlayacağız. Geleceğin yazılımını inşa etmeye devam etmek için sabırsızlanıyoruz.
Sıkça Sorulan Sorular (SSS)
-
Rust öğrenmek zor mudur?
Rust, özellikle diğer dillerden (Python, JavaScript gibi) geçiş yapanlar için başlangıçta bazı yeni ve karmaşık kavramlar (sahiplik, ödünç alma, ömürler) sunar. Ancak, bu kavramlar bir kez anlaşıldığında, Rust ile daha güvenli ve performanslı kod yazma yeteneği kazandırır. Öğrenme eğrisi dik olsa da, uzun vadede getirdiği avantajlar bu çabaya değerdir.
-
Rust hangi alanlarda kullanılır?
Rust, sistem programlama (işletim sistemleri, gömülü sistemler), web servisleri (backend), oyun geliştirme, CLI araçları, blockchain uygulamaları, webAssembly (WebAssembly) ve network programlama gibi yüksek performans ve güvenlik gerektiren birçok alanda kullanılır.
-
Rust ile hangi işletim sistemlerinde geliştirme yapabilirim?
Rust, platformlar arası uyumluluğa sahiptir. Windows, macOS ve Linux gibi popüler işletim sistemlerinde sorunsuz bir şekilde geliştirme yapabilir ve derlenmiş uygulamalarınızı bu platformlarda çalıştırabilirsiniz.
-
Garbage Collector yoksa bellek nasıl yönetilir?
Rust, sahiplik (ownership) ve ödünç alma (borrowing) sistemi aracılığıyla bellek yönetimi yapar. Derleme zamanında, her değerin bir "sahibi" olduğunu ve sahibinin kapsam dışına çıktığında değerin düşürüleceğini garanti eder. Bu sayede çalışma zamanında bir çöp toplayıcıya ihtiyaç duymadan bellek güvenliği sağlanır.
-
Rust ne kadar hızlıdır?
Rust, C ve C++ gibi dillere yakın bir performans sunar. Çalışma zamanı maliyeti olmayan soyutlamalar ve derleyici optimizasyonları sayesinde, çoğu sistem programlama görevi için ideal bir seçimdir.