Bir Dosyayı Okuma
Şimdi, dosya_yolu argümanında belirtilen dosyayı okuma işlevselliğini ekleyeceğiz. Öncelikle bu özelliği test etmek için örnek bir dosyaya ihtiyacımız var: Birkaç satıra yayılan ve tekrar eden bazı kelimeler barındıran kısa bir metin dosyası kullanacağız. Liste 12-3’te yer alan Necip Fazıl Kısakürek şiiri bu iş için gayet uygundur (orijinal kitapta Emily Dickinson’ın “I’m Nobody! Who are you?” şiirinin bulunduğu poem.txt dosyası kullanılır; Türkçe çeviride ise ilerleyen bölümlerdeki arama ve büyük/küçük harf duyarlılığı örnekleriyle uyumlu olması için bu şiir tercih edilmiştir). Projenizin kök dizininde siir.txt adında bir dosya oluşturun ve “Beklenen” şiirinin dizelerini bu dosyaya girin.
Ne hasta bekler sabahı,
Ne taze ölüyü mezar.
Ne de şeytan, bir günahı,
Seni beklediğim kadar.
Geçti istemem gelmeni,
Yokluğunda buldum seni;
Bırak vehmimde gölgeni
Gelme, artık neye yarar?
Metin dosyasını hazırladıktan sonra src/main.rs dosyasını düzenleyin ve Liste 12-4’te gösterildiği gibi dosyayı okuyacak kodu ekleyin.
use std::env;
use std::fs;
fn main() {
// --snip--
let argumanlar: Vec<String> = env::args().collect();
let sorgu = &argumanlar[1];
let dosya_yolu = &argumanlar[2];
println!("Aranan: {sorgu}");
println!("Dosya: {dosya_yolu}");
let icerik = fs::read_to_string(dosya_yolu).expect("Dosya okunamadı");
println!("Metin içeriği:\n{icerik}");
}
İlk olarak bir use deyimiyle standart kütüphanenin ilgili bölümünü kapsama dahil ediyoruz: Dosyalarla çalışmak için std::fs modülüne ihtiyacımız var.
main fonksiyonunda eklediğimiz yeni fs::read_to_string çağrısı dosya_yolu değerini alır, ilgili dosyayı açar ve dosya içeriğini barındıran std::io::Result<String> türünde bir değer döndürür. Hemen ardından gelen .expect("Dosya okunamadı") çağrısı ise okuma başarılı olduğunda bu Result içindeki String değerini icerik değişkenine çıkarır; dosya okunamazsa programı belirtilen mesajla panikletir.
Sonrasında, programın bu aşamaya kadar doğru çalıştığını kontrol edebilmek için dosya okunduktan sonra icerik değerini ekrana yazdıran geçici bir println! çağrısı daha ekliyoruz.
Henüz arama kısmını uygulamadığımız için ilk komut satırı argümanı olarak herhangi bir string, ikinci argüman olarak da siir.txt dosyasını vererek bu kodu çalıştıralım:
$ cargo run -- mezar siir.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep mezar siir.txt`
Aranan: mezar
Dosya: siir.txt
Metin içeriği:
Ne hasta bekler sabahı,
Ne taze ölüyü mezar.
Ne de şeytan, bir günahı,
Seni beklediğim kadar.
Geçti istemem gelmeni,
Yokluğunda buldum seni;
Bırak vehmimde gölgeni
Gelme, artık neye yarar?
Not: fs::read_to_string, arka planda dosyayı açan, geçerli UTF-8 metninin tamamını tek seferde öbekte tahsis edilen bir String içine okuyan ve işlem bittiğinde dosya tanıtıcısını otomatik olarak kapatan pratik bir yardımcı fonksiyondur. Küçük ve orta boyutlu metin dosyaları için bu yöntem son derece kullanışlıdır; ancak gigabaytlarca büyüklükteki metin veya günlük dosyalarında tüm içeriği tek seferde belleğe yüklememek için dosyayı std::fs::File::open ile açıp std::io::BufReader ve BufRead::lines aracılığıyla satır satır akış hâlinde okumak çok daha verimlidir. Ayrıca komutta verdiğimiz siir.txt gibi göreli dosya yolları, derlenen ikili dosyanın bulunduğu target/debug/ dizinine göre değil, programın çalıştırıldığı mevcut çalışma dizinine göre çözümlenir; cargo run komutunu proje kök dizininde çalıştırdığımız için program kök dizindeki siir.txt dosyasını doğrudan bulur.
Harika! Kod dosyanın içeriğini okudu ve ardından ekrana yazdırdı. Ancak kodumuzun birkaç tasarım kusuru var. Şu anda main fonksiyonu birden fazla sorumluluk üstleniyor (hem argümanları ayrıştırıyor hem de dosyayı okuyor) ve sorgu ile dosya_yolu gibi yapılandırma değişkenleri ortak bir yapıda gruplanmadan dağınık biçimde tutuluyor; oysa genel olarak her fonksiyon yalnızca tek bir görevden sorumlu olduğunda fonksiyonlar çok daha anlaşılır ve bakımı kolay olur. Diğer önemli sorun ise hataları olabileceği kadar iyi yönetmiyor olmamız: Olası hata noktaları ve expect çağrıları kodun içine dağılmış durumda, üstelik üretilen hata mesajları hatanın gerçek nedenini kullanıcıya açıklamakta yetersiz kalıyor. Programımız henüz küçük olduğu için bu kusurlar şimdilik büyük bir sorun oluşturmuyor; ancak program büyüdükçe bunları temiz bir şekilde düzeltmek zorlaşacaktır. Daha az miktardaki kodu yeniden düzenlemek çok daha kolay olduğundan, program geliştirirken yeniden düzenlemeye erken aşamada başlamak iyi bir alışkanlıktır. Bir sonraki adımda bunu yapacağız.