İçeriğe geç
academia.sh

Ders 01 / 12

Rust'ın Tasarım Hedefleri

On sekiz yazımlık bir ölçüm çekirdeği program içinden koşturulur ve her yazımı üç sınıftan birine yerleştirir — ifade, deyim, öğe; sonuç sekizi ifade, beşi deyim, beşi öğe olarak sabittir ve kursun geri kalanı bu üçlüyü tek soru olarak sürdürür: kaynakta yazılan bu şey bir değer mi üretiyor?

Yayın Güncelleme

Bu derste

  • İfade, deyim ve öğe sınıflarını değer üretme ölçütüyle tanımlar ve birbirinden ayırır
  • Ölçüm çekirdeğini koşturarak on sekiz yazımın sınıf dağılımını doğrular
  • Aynı işi yapan iki yöntemin imzadan okunan nedenle ayrı sınıflara düşmesini açıklar
İçindekiler

Go müfredatı beş kursla kapandı ve son kursu bir programın kendisi hakkında nelerin bilinebileceğini ölçtü: bir bilgiyi kaynağın mı söylediğini, bir aracın mı, yoksa koşumun mu. Bu müfredat aynı yöne bir adım daha yaklaşır ve doğrudan kaynağa bakar. Rust’ta bir satır yazıldığında o satırın kaderi üçe ayrılır: satır değerlendirilir ve bir değer üretir, değerlendirilir ama hiçbir şey üretmez, ya da hiç değerlendirilmez, yalnız bir ad bildirir. Kurs boyunca sorulacak tek soru budur: kaynakta yazdığın bu şey bir değer mi üretiyor?

Bu soru daha önce iki farklı biçimde soruldu. Java müfredatının ilk kursu derleyicinin kaynağa neyi eklediğini ölçtü — kaynakta yazılı olmayan bir şeyin derleme sırasında nasıl tamamlandığını. Go müfredatının ilk kursu ise bir şeyin yazılmamasının karşılığını ölçtü — dilin bunu tanımlı mı bıraktığını, reddettiğini mi, yoksa açıkta mı bıraktığını. Bu kurs ikisinden de ayrılır: burada eksik hiçbir şey yok, mesele kaynakta yazılı olanın değerlendirilirken ne ürettiğidir.

Bu üçlü sınıflandırma bir tasarım hedefi olarak okunmalıdır, üç kategoriyi ezbere sıralayan bir liste olarak değil: aynı işi kaynakta üç sınıftan hangisiyle yazdığın, o satırın nerede kullanılabileceğini belirliyor. Bir ifadeyi bir değişkene bağlayabilir, bir işleve geçirebilir ya da başka bir ifadenin içine gömebilirsin; bir deyim bunların hiçbirine uygun değildir, çünkü elinde ()ten başka bir şey yoktur. Kursun ilerleyen dersleri bu sınırın nerede işe yaradığını — ve nerede bir yazım hatasına dönüştüğünü — tek tek gösterecek.

İfade, Deyim, Öğe

Üç yanıt vardır ve kurs boyunca bu üç ad değişmez.

Sınıf Tanım
ifade (expression) Değerlendirilir ve () dışında bir değer üretir
deyim (statement) Değerlendirilir ama () üretir — ya da hiç değerlendirilmez
öğe (item) Yalnız bildirir; kendisi değerlendirilmez, değer kullanınca doğar

Bu üçlünün ortasında görünen () bir boşluk değildir — Temel Tipler dersinde ayrıca ele alınacak, ama şimdiden söylenmesi gereken şu: () de bir tiptir ve tek bir değeri vardır. Bir deyim “hiçbir şey üretmiyor” değil, bu tek değerli tipin değerini üretiyor. if, match, loop ve blok Rust’ta birer değerdir; noktalı virgül bir değeri atar; fn, struct, enum, mod ve const ise hiç değerlendirilmez, yalnız derleyiciye bir ad tanıtır. Bu üç yol var olduğu için soru burada bir kurs taşıyabiliyor.

Programlama Temelleri kursu (M01/K02) if yazımını Python’la kurdu; orada if bir deyimdi ve bir değer üretmezdi. Bu kavram burada tekrar kurulmaz — tek cümlelik bu atıf, üç sınıfın neden Rust’ta ayrı bir eksen oluşturduğunu göstermek içindir: aynı yazım her dilde aynı sınıfa düşmüyor.

Ölçüm Çekirdeği

Kursun geri kalanında kullanılan çekirdek ifade.rsdir. On sekiz ayrı yazımı aynı çerçevede koşturur, her birini bir gözlem defterine yazar ve sonunda sınıf başına toplamı basar. Gözlem defterinin kendisi bir demet (tuple) dizisidir — her satır adı, sınıfı ve değeri üç ayrı metin parçası olarak bir arada tutar. Demet, farklı tipteki birkaç değeri tek bir birim gibi taşımanın en doğrudan yoludur; burada üç alanın da metin olması bir zorunluluk değil, tablonun tek biçimde yazdırılabilmesi için yapılmış bir seçimdir. Gözlem defteri dilin bir parçası değildir — kod okunurken buna dikkat edilmelidir.

RD1. gozle işlevi ve defter demeti (String, String, String üçlüsü) çekirdeğin kendi tasarım kararıdır, Rust’ın bir düzeneği değildir. Bir yazımın hangi sınıfa girdiğine karar veren şey her satırın kaynaktaki biçimidir; gozle çağrısı yalnız bu kararı bir tabloya kaydeder ki on sekiz satır tek koşumda karşılaştırılabilsin.

// ifade.rs — kaynakta yazilan her sey uc siniftan birine girer:
// deger ureten (ifade), () ureten ya da hic degerlendirilmeyen (deyim),
// yalniz bildiren (oge).

use std::collections::BTreeMap;
use std::collections::HashSet;

const SINIR: i32 = 100;

struct Nokta {
    x: i32,
    y: i32,
}

enum Renk {
    Kirmizi,
    Yesil,
}

mod olcu {
    pub fn birim() -> i32 {
        3
    }
}

fn iki_kat(x: i32) -> i32 {
    x * 2
}

fn gozle(defter: &mut Vec<(String, String, String)>, ad: &str, sinif: &str, deger: String) {
    defter.push((ad.to_string(), sinif.to_string(), deger));
}

fn main() {
    let mut defter: Vec<(String, String, String)> = Vec::new();

    // --- ifade: deger ureten yazimlar ---

    let a = 7;
    let b = if a > 5 { "buyuk" } else { "kucuk" };
    gozle(&mut defter, "if bir deger olarak", "ifade", b.to_string());

    let renk = if a > 5 { Renk::Yesil } else { Renk::Kirmizi };
    let ad = match renk {
        Renk::Kirmizi => "kirmizi",
        Renk::Yesil => "yesil",
    };
    gozle(&mut defter, "match bir deger olarak", "ifade", ad.to_string());

    let blok = {
        let ic = iki_kat(a);
        ic + 1
    };
    gozle(&mut defter, "blok bir deger olarak", "ifade", blok.to_string());

    let mut sayac = 0;
    let dongu = loop {
        sayac += 1;
        if sayac == 4 {
            break sayac * 10;
        }
    };
    gozle(&mut defter, "loop break ile deger dondurur", "ifade", dongu.to_string());

    let n = Nokta { x: 1, y: 2 };
    gozle(&mut defter, "yapi soz dizimi bir deger uretir", "ifade", format!("x={} y={}", n.x, n.y));

    let v = vec![1, 2, 3, 4];
    gozle(&mut defter, "vec makrosu bir deger uretir", "ifade", format!("uzunluk={}", v.len()));

    let toplam: i32 = v.iter().map(|x| iki_kat(*x)).sum();
    gozle(&mut defter, "yineleyici zinciri bir deger uretir", "ifade", toplam.to_string());

    let mut kume: HashSet<i32> = HashSet::new();
    let ilk = kume.insert(5);
    let ikinci = kume.insert(5);
    gozle(&mut defter, "kume ekleme bir deger dondurur", "ifade", format!("{} sonra {}", ilk, ikinci));

    // --- deyim: () ureten ya da hic degerlendirilmeyen yazimlar ---

    let noktali = {
        iki_kat(2);
    };
    gozle(&mut defter, "noktali virgulle biten blok", "deyim", format!("{:?}", noktali));

    let mut d = 1;
    let atama = { d = d + 8; };
    gozle(&mut defter, "atamanin kendisi", "deyim", format!("{:?} (d={})", atama, d));

    let mut liste = Vec::new();
    let itme = liste.push(d);
    gozle(&mut defter, "diziye itme", "deyim", format!("{:?} (uzunluk={})", itme, liste.len()));

    let mut kez = 0;
    let dongu_degeri = for _ in 0..3 {
        kez += 1;
    };
    gozle(&mut defter, "for dongusu", "deyim", format!("{:?} (kez={})", dongu_degeri, kez));

    let yazim = print!("");
    gozle(&mut defter, "yazdirmanin degeri", "deyim", format!("{:?}", yazim));

    // --- oge: yalniz bildiren yazimlar; degerleri kullanildiklarinda dogar ---

    gozle(&mut defter, "fn bildirimi", "oge", format!("bildirim; cagrisi {} verdi", iki_kat(3)));
    gozle(&mut defter, "struct bildirimi", "oge", format!("bildirim; ornegi x={} verdi", n.x));
    gozle(&mut defter, "enum bildirimi", "oge", "bildirim; varyanti yesil verdi".to_string());
    gozle(&mut defter, "mod bildirimi", "oge", format!("bildirim; icindeki cagri {} verdi", olcu::birim()));
    gozle(&mut defter, "const bildirimi", "oge", format!("bildirim; okunmasi {} verdi", SINIR));

    // --- rapor ---

    println!("{:<40}{:<8}{}", "gozlem", "sinif", "deger");
    for (ad, sinif, deger) in &defter {
        println!("{:<40}{:<8}{}", ad, sinif, deger);
    }

    let mut sayim: BTreeMap<&str, i32> = BTreeMap::new();
    for (_, sinif, _) in &defter {
        *sayim.entry(sinif.as_str()).or_insert(0) += 1;
    }
    println!();
    for (sinif, adet) in &sayim {
        println!("{:<8}{}", sinif, adet);
    }
    println!("toplam  {}", defter.len());
}
gozlem                                  sinif   deger
if bir deger olarak                     ifade   buyuk
match bir deger olarak                  ifade   yesil
blok bir deger olarak                   ifade   15
loop break ile deger dondurur           ifade   40
yapi soz dizimi bir deger uretir        ifade   x=1 y=2
vec makrosu bir deger uretir            ifade   uzunluk=4
yineleyici zinciri bir deger uretir     ifade   20
kume ekleme bir deger dondurur          ifade   true sonra false
noktali virgulle biten blok             deyim   ()
atamanin kendisi                        deyim   () (d=9)
diziye itme                             deyim   () (uzunluk=1)
for dongusu                             deyim   () (kez=3)
yazdirmanin degeri                      deyim   ()
fn bildirimi                            oge     bildirim; cagrisi 6 verdi
struct bildirimi                        oge     bildirim; ornegi x=1 verdi
enum bildirimi                          oge     bildirim; varyanti yesil verdi
mod bildirimi                           oge     bildirim; icindeki cagri 3 verdi
const bildirimi                         oge     bildirim; okunmasi 100 verdi

deyim   5
ifade   8
oge     5
toplam  18

RD2. Koşum belirlenimcidir: aynı kaynak her koşumda aynı on sekiz satırı, aynı sırayla ve aynı sınıf dağılımıyla üretir. Hiçbir satır bir dosya sisteminden, saatten ya da rastgele üreteçten okumuyor — hepsi sabit bir kaynaktan hesaplanıyor. On sekiz yazımın 8’i ifade, 5’i deyim, 5’i öğedir; bu sayı kursun geri kalanında değişmez, yalnız her ders kendi kesitini alır.

Sınıf başına toplamı hesaplayan son blok bir sıralı sözlük (BTreeMap) kullanıyor, karma sözlük (HashMap) değil. Bu seçim rastgele değil: bir karma sözlüğün anahtarları arasında gezinme sırası koşumdan koşuma değişebilir, üç sınıfın toplamı doğru çıksa bile satırların hangi sırayla basılacağı garanti edilmez. Sıralı sözlük anahtarları her zaman aynı sırada — burada alfabetik olarak deyim, ifade, öğe — dolaştığı için çıktı metni de belirlenimci kalıyor. Bu, kursun ortama bağlı veri konusundaki kuralının en somut örneğidir: bir koleksiyonun sırası bir gösterim aracı olarak kullanılacaksa, o sıranın güvence taşıyan bir yapıdan gelmesi gerekir. Bu ayrım Sıralı ve Küme Yapıları dersinde kendi başına bir konu olacak; burada yalnız çekirdeğin kendi çıktısını neden güvenle koşum sonrası koşuma karşılaştırılabildiğini açıklıyor.

Sonuçların Okunuşu

Sekiz ifade satırının ortak noktası, hepsinin bir let bağlamasının sağ tarafında durup bir değer üretmesidir: if/match bir dal seçer ve o dalın değerini verir, blok kendi son satırını verir, loop yalnız break’e verdiği değeri verir, yapı sözdizimi ve vec makrosu yeni bir değer kurar, yineleyici zinciri bir toplam biriktirir, küme ekleme ise bool döndürür. Beş deyim satırı ise tam tersini gösteriyor: noktalı virgülle kapanan blok, bir atama, Vec::push, for döngüsü ve boş bir yazdırma — beşi de () basıyor, çünkü beşi de “bir iş yap” diyor, “bir değer ver” demiyor. Beş öğe satırı ayrı bir eksende duruyor: fn, struct, enum, mod ve const bildirimlerinin kendisi tabloya hiç girmiyor, tabloya giren şey bu bildirimlerin kullanımından doğan değer — iki_kat(3)’ün döndürdüğü 6, Nokta örneğinin alanı, olcu::birim()’in çağrısı, SINIR’ın okunması. Bir bildirim yazıldığı anda hiçbir şey üretmiyor; yalnız bir ad tanıtıyor ve o ad ilerideki bir satırda kullanılınca değer doğuyor.

RD3. iki_kat öğesi çekirdekte üç ayrı yerde kullanılıyor: bir kez blok içinde (ic + 1 hesabına girecek değeri üretmek için), bir kez yineleyici zincirinde her elemana uygulanmak için, bir kez de “fn bildirimi” satırında doğrudan çağrılıp sonucu tabloya yazılmak için. Üçünde de iki_kat’ın kendisi değişmiyor — değişen, her çağrıya verilen argüman ve o çağrının döndürdüğü değer. Bu, öğenin “değersiz” olmasının ne anlama geldiğini somutlaştırıyor: bildirim tek, ama kullanım sayısı ve her kullanımın ürettiği değer serbest.

Bu üç grup arasında sınıf sayısı eşit değildir ve eşit olması da beklenmiyor: kaynağın çoğu bir değer üretmek için yazılıyor (8), geri kalanı ya bir yan etki için (5) ya da bir ad tanıtmak için (5) yazılıyor. Kursun geri kalanı bu üç grubu tek tek derinleştirecek — if, match, loop ve döngülerin tamamı Denetim Akışı dersinde, atama ve bağlama Değişkenler ve Değişmezlik dersinde, bildirimlerin kaynak üzerindeki payı Kurulum ve Paket Yöneticisi dersinde ayrıca ele alınacak. Bu derste kalan tek soru, sınıfın nereden okunduğudur.

Sınırlayıcı Ölçüm: Sınıf İmzadan Okunur

Tabloda yan yana duran iki satır bu sorunun yanıtını taşıyor: “kume ekleme bir deger dondurur” (ifade, true sonra false) ile “diziye itme” (deyim, () (uzunluk=1)). İkisi de aynı işi yapıyor gibi görünür — bir koleksiyona bir eleman eklemek. Ama biri koşumda bir değer üretti, öbürü üretmedi. Fark, işin doğasında değil, çağrılan yöntemin imzasında yatıyor.

HashSet::insert hem kümeyi değiştirir hem de eklemenin gerçekten yeni bir öğe kattığını söyleyen bir mantıksal değer döndürür. Çekirdekte 5 iki kez eklenmiş: ilk çağrı kümeye yeni bir öğe kattığı için true, ikinci çağrı zaten var olan 5i tekrar eklemeye çalıştığı için false döndürüyor — tablodaki true sonra false bu iki çağrının yan yana konmuş sonucudur. Vec::push ise hiçbir zaman böyle bir bilgi vermiyor: bir diziye eleman eklemek her zaman başarılı olduğu için döndürülecek bir mantıksal değer yok, imza yalnız () veriyor.

Bu, “ekleme” diye adlandırılan tek bir işin iki ayrı Rust yöntemine iki ayrı sınıf verdiğini gösteriyor. Sınıfı belirleyen okuyucunun işin ne olduğuna dair sezgisi değil, derleyicinin gördüğü dönüş tipidir: dönüş tipi () ise deyim, başka bir tip ise ifade. Bu ölçüt biçimsel ve mekanik olduğu için, bir yöntemin çağrısına bakmadan, yalnız imzasına bakarak hangi sınıfa gireceği önceden söylenebilir — Option ya da bool döndüren bir değiştirici her zaman ifadedir, yalnız () döndüren bir değiştirici her zaman deyimdir. Koleksiyonlar dersinde bu ayrım Vec::popin Option döndürmesiyle bir kez daha karşımıza çıkacak.

Bu, kursun tasarım hedefi başlığı altında durmasının nedenini de açıklıyor: sınıf bir çalışma zamanı gözlemi değil, bir imza kararıdır ve imza kaynakta koşum yapılmadan okunabilir. Bir okuyucu HashSet::insertin dönüş tipine bakarak, hiçbir örneği koşturmadan, onun bir ifade olduğunu söyleyebilir. Standart kitaplığın kendi tasarımı bu sınıflandırmayı önceden belirliyor; ders boyunca ölçülen şey, bu belirlemenin kaynağı okurken nasıl bir farkla karşımıza çıktığıdır.

Özet

  • Kaynakta yazılan her şey üç sınıftan birine girer: ifade bir değer üretir, deyim () üretir ya da hiç değerlendirilmez, öğe yalnız bir ad bildirir ve değeri kullanımdan doğar.
  • () boşluk değildir; tek değerli bir tiptir ve deyimin ürettiği şey budur.
  • Ölçüm çekirdeği on sekiz yazımı tek koşumda sınıflandırır: 8’i ifade, 5’i deyim, 5’i öğe — bu sayı kursun geri kalanında değişmez.
  • Aynı iş (ekleme) iki ayrı yönteme iki ayrı sınıf verebilir; sınıf işin doğasından değil, yöntemin dönüş tipinden — imzasından — okunur.

Sonraki Adım

Üç sınıf artık tanımlı, ama bir kaynağın gerçekten çalışabilmesi için bunlardan çok daha küçük, çok daha zorunlu bir küme var: bir programın en az ne yazması gerekir. Sıradaki ders bu en küçük kümeyi sorar — kaynağın kendisinde durmayan, ayrı bir dosyada tutulan bir parça değişince kaynak hiç değişmeden çıktının nasıl değiştiğini gösterir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat