- Paket, crate ve modül kavramlarını ayırmak,
pubveuseile çalışmak - crates.io'dan bağımlılık eklemek ve
Cargo.tomldosyasını okumak #[test]fonksiyonları yazıpcargo testile çalıştırmak- Kütüphane ve çalıştırılabilir kısımdan oluşan eksiksiz bir CLI programı kurmak
Artık Rust'ın temel araçlarını biliyorsun; gerçek bir araç geliştirme zamanı. Bu derste bir metin dosyasını okuyan ve en sık kullanılan kelimeleri gösteren bir kelime sayacı yazacağız. Bu sırada büyük projeleri nasıl düzenleyeceğini öğreneceksin: kodu modüllere bölmek, başkalarının kütüphanelerini kullanmak ve kodun doğruluğunu testlerle denetlemek.
Paketler, crate'ler ve modüller
- Paket —
Cargo.tomldosyası olan bir Cargo projesi.cargo newtam olarak bunu oluşturur. - Crate — bir derleme birimi.
src/main.rsçalıştırılabilir (binary) bir crate,src/lib.rsise bir kütüphane crate'i oluşturur. Bir pakette ikisi birden olabilir. - Modül — bir crate içinde
modile oluşturulan ad alanı. Modülün öğeleri varsayılan olarak gizlidir; dışarıdan görünmeleri içinpubyazılır. - Yol —
geometry::square_areagibi::ile yazılan ad;useise uzun bir yolu kısa bir adla kapsama alır.
mod geometry {
pub fn square_area(side: f64) -> f64 {
side * side
}
pub fn circle_area(r: f64) -> f64 {
round2(std::f64::consts::PI * r * r)
}
fn round2(x: f64) -> f64 {
(x * 100.0).round() / 100.0
}
}
use geometry::circle_area;
fn main() {
println!("{}", geometry::square_area(1.5));
println!("{}", circle_area(2.0));
}2.25 12.57
round2, pub değildir: modülün içinde kullanılabilir, ama main içinden geometry::round2(1.0) çağrısı E0603 hatası verirdi (function round2 is private).Bir modül büyüdüğünde ayrı bir dosyaya taşınır: main.rs ya da lib.rs içine mod counter; yazmak, Rust'a modülün kodunu src/counter.rs dosyasında aramasını söyler. Modül yollarında crate:: crate'in kökünü, super:: ise üst modülü belirtir.
Bağımlılıklar ve crates.io
crates.io, Rust topluluğunun resmî kütüphane kayıt deposudur. Bir kütüphane eklemek için cargo add komutu yeterlidir: Cargo.toml dosyasının [dependencies] bölümüne bir satır ekler, bir sonraki derlemede de Cargo kütüphaneyi indirip derler. "1.0" gibi bir sürüm gereksinimi “1.x ile uyumlu herhangi bir sürüm” demektir (SemVer). Tam olarak seçilen sürümler Cargo.lock dosyasına yazılır; böylece proje her bilgisayarda aynı şekilde derlenir.
cargo add serde --features derive
cargo add clap --features derive
cargo update[package]
name = "wordcount"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }cargo add, en son sürümün tam numarasını yazar. serde verileri JSON ve diğer biçimlere dönüştürür, clap ise komut satırı argümanlarını ayrıştırır. Kelime sayacımızın bunlara ihtiyacı yok; bu yalnızca bir örnek.| Komut | Ne yapar |
|---|---|
cargo add name | bağımlılık ekler |
cargo update | bağımlılıkları izin verilen en yeni sürümlere günceller |
cargo test | tüm testleri çalıştırır |
cargo doc --open | projenin ve bağımlılıklarının belgelerini oluşturup tarayıcıda açar |
cargo install --path . | kendi aracını sistem komutu olarak kurar |
Proje: kütüphane kısmı
Projeyi cargo new wordcount ile oluştur ve mantığı dosya ve ekran işlerinden ayır: kelimeleri sayan saf fonksiyon src/lib.rs içinde, dosyayı okuyup sonucu yazdıran kod ise src/main.rs içinde olacak. Bu ayrım fonksiyonu test etmeyi kolaylaştırır. Fonksiyon metni boşluklardan böler, her kelimeden noktalama işaretlerini atar, küçük harfe çevirir, bir HashMap içinde sayar ve sonucu önce sayıya göre (büyükten küçüğe), eşit sayılarda ise alfabetik olarak sıralar.
// src/lib.rs
use std::collections::HashMap;
pub fn count_words(text: &str) -> Vec<(String, usize)> {
let mut counts: HashMap<String, usize> = HashMap::new();
for raw in text.split_whitespace() {
let word = raw
.chars()
.filter(|c| c.is_alphanumeric())
.collect::<String>()
.to_lowercase();
if !word.is_empty() {
*counts.entry(word).or_insert(0) += 1;
}
}
let mut result: Vec<(String, usize)> = counts.into_iter().collect();
result.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
result
}sort_by, iki elemanı karşılaştıran bir closure alır: b.1.cmp(&a.1) sayıya göre azalan sıra verir, .then(...) ise sayılar eşitse kelimeleri alfabetik dizer. Böylece HashMap'in rastgele sırası ortadan kalkar.Testler
Rust'ta testler genellikle denetledikleri kodun yanına yazılır. #[cfg(test)] özniteliği bir modülü yalnızca cargo test sırasında derler, #[test] ise bir fonksiyonu test olarak işaretler. assert_eq!(a, b) iki değerin eşitliğini, assert!(koşul) ise koşulun doğruluğunu denetler; başarısızlıkta test panikler ve iki değeri de gösterir.
// end of src/lib.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_and_sorts() {
let result = count_words("b a B, c a b");
assert_eq!(result[0], (String::from("b"), 3));
assert_eq!(result[1], (String::from("a"), 2));
assert_eq!(result.len(), 3);
}
#[test]
fn ignores_punctuation_only_text() {
assert!(count_words(" ... ").is_empty());
}
}use super::*;, üst modüldeki her şeyi, count_words dahil, testlere getirir. Temizlendikten sonra B, kelimesi b olur; bu yüzden b üç kez sayılır.$ cargo test
Compiling wordcount v0.1.0 (/home/murad/wordcount)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.84s
Running unittests src/lib.rs (target/debug/deps/wordcount-3f2a9c1d8e7b6a50)
running 2 tests
test tests::counts_and_sorts ... ok
test tests::ignores_punctuation_only_text ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00stest ... satırlarının sırası değişebilir.Proje: main.rs ve çalıştırma
main.rs, kütüphaneye paket adıyla erişir: use wordcount::count_words;. env::args() komut satırı argümanlarını verir (sıfırıncı argüman programın kendi adıdır), fs::read_to_string ise dosyayı okuyup bir Result döndürür. Hataları eprintln! ile standart hata akışına yazar ve process::exit(1) ile başarısız bir kodla çıkarız.
// src/main.rs
use std::env;
use std::fs;
use std::process;
use wordcount::count_words;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: wordcount <file>");
process::exit(1);
}
let text = match fs::read_to_string(&args[1]) {
Ok(text) => text,
Err(e) => {
eprintln!("Cannot read {}: {}", args[1], e);
process::exit(1);
}
};
let words = count_words(&text);
let total: usize = words.iter().map(|(_, n)| n).sum();
println!("Total words: {total}");
println!("Unique words: {}", words.len());
for (word, n) in words.iter().take(3) {
println!("{word}: {n}");
}
}The sea, the wind and the sun.
The wind is cold; the sea is deep.poem.txt dosyası.$ cargo run -q -- poem.txt
Total words: 15
Unique words: 8
the: 5
is: 2
sea: 2is, sea ve wind ikişer kez geçiyor; alfabetik sırayla ilk ikisi gösterildi.Önemli noktalar
- Paket,
Cargo.tomliçeren bir projedir;main.rsçalıştırılabilir,lib.rskütüphane crate'i oluşturur. - Modül öğeleri varsayılan olarak gizlidir;
pubonları açar,useyolları kısaltır. cargo addcrates.io'dan bağımlılık ekler,Cargo.lockise tam sürümleri kaydeder.- Testler,
#[cfg(test)]modülündeki#[test]fonksiyonlarıdır vecargo testile çalıştırılır. - Mantığı
lib.rs'de, girdi/çıktıyımain.rs'de tutmak kodu test etmeyi kolaylaştırır.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.