Yapıları Kullanan Örnek Bir Program

Yapıları ne zaman kullanmak isteyebileceğimizi anlamak için, bir dikdörtgenin alanını hesaplayan bir program yazalım. Tek değişkenler kullanarak başlayacağız ve ardından bunun yerine yapıları kullanana kadar programı yeniden düzenleyeceğiz.

Piksel cinsinden belirtilen bir dikdörtgenin genişlik ve yüksekliğini alacak rectangles adlı, Cargo ile yeni bir ikili proje oluşturalım ve dikdörtgenin alanını hesaplayalım. Liste 5-8, projemizin src/main.rs dosyasında tam olarak bunu yapmanın bir yolunu içeren kısa bir program göstermektedir.

Dosya adı: src/main.rs

fn main() {
    let width1 = 30;
    let height1 = 50;

    println!(
        "The area of the rectangle is {} square pixels.",
        area(width1, height1)
    );
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

Liste 5-8: Ayrı genişlik ve yükseklik değişkenleri tarafından belirtilen bir dikdörtgenin alanını hesaplama

Şimdi, bu programı cargo run kullanarak çalıştırın.

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/rectangles`
The area of the rectangle is 1500 square pixels.

Bu kod, her bir boyutta alan fonksiyonunu çağırarak dikdörtgenin alanını bulmayı başarır, ancak bu kodu açık ve okunabilir hale getirmek için daha fazlasını yapabiliriz.

Bu kodla ilgili sorun, area'nın imzasında belirgindir:

fn main() {
    let width1 = 30;
    let height1 = 50;

    println!(
        "The area of the rectangle is {} square pixels.",
        area(width1, height1)
    );
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

area fonksiyonunun bir dikdörtgenin alanını hesaplaması gerekiyor, ancak yazdığımız fonksiyonun iki parametresi var ve programımızın hiçbir yerinde parametrelerin ilişkili olduğu net değil. Genişliği ve yüksekliği birlikte gruplamak daha okunaklı ve daha yönetilebilir olurdu. Bunu, Bölüm 3'ün “Demet Türü” bölümünde, demetleri kullanarak yapabileceğimizin bir yolunu zaten tartışmıştık.

Demetlerle Yeniden Düzenleme

Liste 5-9, programımızın demet kullanan başka bir sürümünü gösterir.

Dosya adı: src/main.rs

fn main() {
    let rect1 = (30, 50);

    println!(
        "The area of the rectangle is {} square pixels.",
        area(rect1)
    );
}

fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

Liste 5-9: Bir demet ile dikdörtgenin genişliğini ve yüksekliğini belirtme

Bir bakıma bu program daha iyi. Demetler yapı eklememize izin verir ve biz sadece bir argümanı geçeceğiz. Ancak başka bir şekilde, bu versiyon daha az açıktır: demetler öğelerini adlandırmaz, bu nedenle demetin bölümlerine indekslememiz gerekir, bu da hesaplamamızı daha az belirgin hale getirir.

Genişlik ve yüksekliğin karıştırılması alan hesabı için önemli değil, ancak dikdörtgeni ekranda çizmek istiyorsak önemlidir! Genişliğin 0 küme indeksi ve yüksekliğin küme indeksi 1 olduğunu aklımızda tutmalıyız. Eğer bizim kodumuzu kullanacak olsaydı, başka birinin bunu anlaması ve akılda tutması daha da zor olurdu. Verilerimizin anlamını kodumuzda iletmediğimiz için, hataları tanıtmak artık daha kolay.

Yapılarla Yeniden Düzenleme: Daha Fazla Anlam Ekleme

Verileri etiketleyerek anlam eklemek için yapılar kullanırız. Kullandığımız demeti, Liste 5-10'da gösterildiği gibi, parçaların adlarının yanı sıra bütün için bir ad içeren bir yapıya dönüştürebiliriz.

Dosya adı: src/main.rs

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        area(&rect1)
    );
}

fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

Liste 5-10: Bir Rectangle yapı tanımlama

Burada bir yapı tanımladık ve onu Rectangle olarak adlandırdık. Kıvrımlı parantezler içinde, her ikisi de u32 tipinde olan alanları width ve height olarak tanımladık. Ardından, ana olarak, 30 genişliğinde ve 50 yüksekliğinde belirli bir Rectangle tanımı oluşturduk.

area fonksiyonumuz şimdi, türü bir struct Rectangle tanımının değişmez bir ödünç alma şekli olan rectangle adını verdiğimiz bir parametre ile tanımlanır. Bölüm 4'te bahsedildiği gibi, yapıyı sahiplenmek yerine ödünç almak istiyoruz. Bu şekilde main, sahipliğini korur ve fonksiyon imzasında ve fonksiyonu çağırdığımız yerde &'yi kullanmamızın nedeni olan rect1'i kullanmaya devam edebilir.

area fonksiyonu, Rectangle tanımının width ve height üyelerine erişir (ödünç alınan bir yapı tanımının üyelerine erişmenin üye değerlerini hareket ettirmediğini, bu nedenle sık sık yapı ödünçlerini gördüğünüzü unutmayın). area için fonksiyon imzamız şimdi tam olarak ne demek istediğimizi söylüyor: width ve height üyelerini kullanarak Rectangle alanını hesaplayın. Bu, genişlik ve yüksekliğin birbiriyle ilişkili olduğunu ifade eder ve 0 ve 1'lik demet indeks değerlerini kullanmak yerine değerlere açıklayıcı isimler verir. Bu, netlik için bir kazançtır.

Türetilmiş Tanımlar ile Faydalı İşlevsellik Ekleme

Programımızda hata ayıklarken bir Rectangle tanımını yazdırabilmek ve tüm üyelerin değerlerini görebilmek faydalı olacaktır. Liste 5-11 println! makrosunu önceki bölümlerde kullandığımız gibi kullanmayı dener. Ancak bu işe yaramayacaktır.

Dosya adı: src/main.rs

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {}", rect1);
}

Liste 5-11: Bir Rectangle tanımını yazdırmaya çalışmak

Bu kodu derlediğimizde, bir hata alıyoruz:

error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`

println! makrosu birçok türde biçimlendirme yapabilir ve varsayılan olarak süslü parantezler println!'e Display olarak bilinen biçimlendirmeyi kullanmasını söyler. Şimdiye kadar gördüğümüz ilkel tipler varsayılan olarak Display'i uygular, çünkü bunu yapabileceğiniz tek yoldur.

   = help: the trait `std::fmt::Display` is not implemented for `Rectangle`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

Hadi deneyelim! println! makro çağrısı şimdi println!("rect1 is {:?}", rect1);. :? belirtecini küme parantezlerinin içine koymak şunu söyler: println! için Debug adında bir çıktı formatı kullanmak istiyoruz. Debug tanımı yapımızı geliştiriciler için yararlı olacak şekilde yazdırmamızı sağlar, böylece kodumuz ayıklanırken değerini görebilirsiniz.

Kodu bu değişiklikle derleyin. Hala bir hata alıyoruz:

error[E0277]: `Rectangle` doesn't implement `Debug`

Ama yine de derleyici bize yardımcı olacak bir not veriyor:

   = help: the trait `Debug` is not implemented for `Rectangle`
   = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`

Rust, hata ayıklama bilgilerini yazdırma fonksiyonunu içerir, ancak bu fonksiyonu yapımız için kullanılabilir hale getirmek için açıkça seçmeliyiz. Bunu yapmak için, Liste 5-12'de gösterildiği gibi, yapı tanımından hemen önce #[derive(Debug)] dış niteliğini ekleriz.

Dosya adı: src/main.rs

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {:?}", rect1);
}

Liste 5-12: Debug tanımını türetmek için özniteliği ekleme ve hata ayıklama biçimlendirmesini kullanarak Rectangle tanımı yazdırma

Şimdi programı çalıştırdığımızda herhangi bir hata almayacağız ve aşağıdaki çıktıyı göreceğiz:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/rectangles`
rect1 is Rectangle { width: 30, height: 50 }

Güzel! En güzel çıktı değil, ancak hata ayıklama sırasında kesinlikle yardımcı olacak bu örnek için tüm alanların değerlerini gösterir. Daha büyük yapılarımız olduğunda, okunması biraz daha kolay çıktılara sahip olmak yararlıdır; bu durumlarda println!'de {:?} yerine {:#?} kullanabiliriz. Bu örnekte, {:#?} stilinin kullanılması şu sonucu verecektir:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
     Running `target/debug/rectangles`
rect1 is Rectangle {
    width: 30,
    height: 50,
}

Debug formatını kullanarak bir değer yazdırmanın başka bir yolu da dbg! makrosudur. Bu makro ifadenin sahipliğini alır (bir referans alan println!'nin aksine) ve bu ifadenin sonuç değeriyle birlikte kodunuzda gerçekleşir ve değerin sahipliğini döndürür.

Not: dbg!'yi çağırmak, println!'nin aksine standart hata konsolu akışına (stderr) yazdırır. Bölüm 12'deki “Standart Çıktı Yerine Standart Hataya Hata Mesajları Yazma” bölümünde stderr ve stdout hakkında daha fazla konuşacağız.

İşte, genişlik alanına atanan değerle ve ayrıca rect1'deki tüm yapının değeriyle ilgilendiğimiz bir örnek:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let scale = 2;
    let rect1 = Rectangle {
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
}

30 * scale ifadesine dbg! makrosunu koyabiliriz çünkü dbg! ifadenin değerinin sahipliğini döndürür. Biz ise dbg!'nin rect1'in sahipliğini almasını istemiyor olduğumuzdan dolayı bir diğer çağrıda rect1'in bir referansını kullanacağız.

Bu örneğin çıktısı şuna benzemelidir:

$ cargo run
   Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/rectangles`
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect1 = Rectangle {
    width: 60,
    height: 50,
}

Çıktının ilk bitinin src/main.rs 10. satırdan geldiğini görebiliriz, burada 30 * scale ifadesinin hatalarını ayıklıyoruz. dbg! src/main.rs dosyasının 14. satırındaki çağrı, Rectangle yapısı olan &rect1 değerini verir. Bu çıktı, Rectangle türünün güzel Debug biçimlendirmesini kullanır. dbg! makrosu, kodunuzun ne yaptığını anlamaya çalışırken gerçekten yardımcı olabilir!

Rust, Debug tanımına ek olarak, derive özelliğiyle birlikte kullanmamız için özel türlerimize faydalı davranışlar ekleyebilecek bir dizi özellik sağlamıştır. Bu özellikler ve davranışları Ekleme C'de listelenmiştir. Bu özellikleri özel davranışlarla nasıl uygulayacağınızı ve kendi özelliklerinizi nasıl oluşturacağınızı Bölüm 10'da ele alacağız. derive'ın dışında da birçok nitelik vardır; daha fazla bilgi için, Rust Reference'ın “Nitelikler” bölümüne bakın.

area fonksiyonumuz çok spesifiktir: sadece dikdörtgenlerin alanını hesaplar. Bu davranışı Rectangle yapımıza daha yakından bağlamak faydalı olacaktır, çünkü başka hiçbir türle çalışmayacaktır. area fonksiyonunu Rectangle türümüzde tanımlı bir area metoduna çevirerek bu kodu nasıl yeniden düzenlemeye devam edebileceğimizebakalım.