İçeriğe geç
Educora
İleri22 dk10 / 10

Modüller, Cargo ve proje: kelime sayacı

Kodu modüllere ve crate'lere böl, crates.io'dan bağımlılık ekle, `cargo test` ile testler yaz ve öğrendiklerinle bir metin dosyasındaki kelimeleri sayan bir komut satırı aracı geliştir.

Kendini test et
Bu derste öğreneceklerin
  • Paket, crate ve modül kavramlarını ayırmak, pub ve use ile çalışmak
  • crates.io'dan bağımlılık eklemek ve Cargo.toml dosyasını okumak
  • #[test] fonksiyonları yazıp cargo test ile ç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.toml dosyası olan bir Cargo projesi. cargo new tam olarak bunu oluşturur.
  • Crate — bir derleme birimi. src/main.rs çalıştırılabilir (binary) bir crate, src/lib.rs ise bir kütüphane crate'i oluşturur. Bir pakette ikisi birden olabilir.
  • Modül — bir crate içinde mod ile oluşturulan ad alanı. Modülün öğeleri varsayılan olarak gizlidir; dışarıdan görünmeleri için pub yazılır.
  • Yol — geometry::square_area gibi :: ile yazılan ad; use ise uzun bir yolu kısa bir adla kapsama alır.
Rust
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));
}
Beklenen çıktı
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.

Terminal
cargo add serde --features derive
cargo add clap --features derive
cargo update
Text
[package]
name = "wordcount"
version = "0.1.0"
edition = "2024"

[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
Gerçekte 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.
KomutNe yapar
cargo add namebağımlılık ekler
cargo updatebağımlılıkları izin verilen en yeni sürümlere günceller
cargo testtüm testleri çalıştırır
cargo doc --openprojenin 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.

Rust
// 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.

Rust
// 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.
Text
$ 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.00s
Kısaltılmış çıktı: süreler ve dosya adındaki karma değer her bilgisayarda farklıdır. Testler paralel çalıştığı için test ... 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.

Rust
// 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}");
    }
}
Text
The sea, the wind and the sun.
The wind is cold; the sea is deep.
Proje klasörüne kaydedilmiş poem.txt dosyası.
Text
$ cargo run -q -- poem.txt
Total words: 15
Unique words: 8
the: 5
is: 2
sea: 2
15 kelimeden 8'i farklıdır. is, sea ve wind ikişer kez geçiyor; alfabetik sırayla ilk ikisi gösterildi.

Önemli noktalar

  • Paket, Cargo.toml içeren bir projedir; main.rs çalıştırılabilir, lib.rs kütüphane crate'i oluşturur.
  • Modül öğeleri varsayılan olarak gizlidir; pub onları açar, use yolları kısaltır.
  • cargo add crates.io'dan bağımlılık ekler, Cargo.lock ise tam sürümleri kaydeder.
  • Testler, #[cfg(test)] modülündeki #[test] fonksiyonlarıdır ve cargo test ile ç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.

1 / 10
Bir modülün içindeki fonksiyon varsayılan olarak nasıl görünür?