İçeriğe geç
academia.sh

Ders 03 / 12

Değişkenler ve Değişmezlik

let bir bağlama deyimidir ve atamanın kendisi () üretir; aynı adın üç kez yeniden bağlanması (gölgeleme) ile bir kez bağlanıp iki kez değiştirilmesi (mut) aynı sayısal sonuca ulaşır, ama yalnız gölgeleme aradan tip değiştirebilir — çünkü biri her seferinde yeni bir bağlama kurar, öbürü var olan bağlamanın üzerine yazar.

Yayın Güncelleme

Bu derste

  • let bağlamasının varsayılan olarak değişmez olduğunu ve atamanın kendisinin () ürettiğini gösterir
  • Gölgeleme ile mut ile yeniden atamayı aynı sayısal sonuca ulaşan iki ayrı düzenek olarak karşılaştırır
  • Gölgelemenin bağlamanın tipini değiştirebildiğini, mut ile yeniden atamanın tipi koruduğunu ayırt eder
İçindekiler

Giriş noktasının gövdesine artık inilebilir ve gövdenin ilk satırlarının çoğu bir ada bir değer bağlar (binding). Bu ders bağlamanın kendisini iki açıdan ölçüyor: bağlama işleminin kendisi bir ifade mi deyim mi ve aynı adı yeniden bağlamanın Rust’ta iki ayrı yolu — biri her seferinde yepyeni bir bağlama kuran gölgeleme, öbürü var olan bağlamanın üzerine yazan mut — neden aynı sonuca ulaşırken birbirinden bu kadar farklı davranıyor.

Programlama Temelleri kursu (M01/K02) değişken tanımını Python’la kurdu ve gölgelemeyi de orada tanımladı. Bu kavramlar burada tekrar kurulmaz; bu dersin payı, Rust’ın bağlamayı varsayılan olarak değişmez (immutable) kurması ve gölgelemeye bu değişmezliğin içinden bir kapı açmasıdır.

Bu sıralama gelişigüzel değil. Önceki ders bir kaynağın çalışabilmesi için gereken en küçük kümeyi kurdu; bu ders o kümenin içine, giriş noktasının gövdesine iniyor ve orada bir adın bir değere nasıl bağlandığını soruyor. Sıradaki ders bu bağlanan değerin hangi tipte olabileceğini soracak — ama bir tipin ne olduğunu sormadan önce, bir adın o tipteki değere nasıl tutunduğunu ve bu tutunmanın ne kadar sıkı olduğunu bilmek gerekiyor. Değişmezlik işte bu sıkılığın adıdır.

Bağlama Bir Deyimdir

let ad = deger; yazımı iki ayrı parçaya ayrılır. Eşittir işaretinin sağı bir ifadedir — değerlendirilir ve bir değer üretir. Bütün satır, yani let ile başlayan bağlamanın kendisi, bir deyimdir: hiçbir yerde bir değer olarak kullanılamaz. Bu ayrım küçük görünse de sonuçları somuttur — bir bağlamayı bir işleve argüman olarak geçiremez, bir listeye ekleyemez, başka bir ifadenin içine gömemezsin, çünkü elinde ()ten başka bir şey yoktur.

RD8. Rust’ta let yalnız gövde düzeyinde durur ve bir ifadenin içine yerleştirilemez; (let x = 5) gibi bir yazım derleyici tarafından kabul edilmez. Bu, letin “her zaman bir deyim” olmasının somut karşılığıdır — istisnası yoktur, hiçbir bağlamda bir değere dönüşmez. Bunun bir sonucu, kaynağı okuyan tarafın yeni bir adın nerede kurulduğunu tek bir yerden takip edebilmesidir: her yeni bağlama bir satırın başında, let ile açılır; bir ifadenin ortasında gizlenmiş bir bağlama arama gerekmez.

Atamanın kendisinin de aynı sınıfa girdiğini önceki derste ifade.rs çekirdeğinde gördük (“atamanın kendisi” satırı () bastı). Burada aynı olguyu, değişkenler konusuna özgü küçük bir örnekle tekrar koşturalım.

// deyim_olarak_atama.rs — atamanin kendisi bir deger uretmez
fn main() {
    let mut x = 1;
    println!("x baslangicta: {}", x);
    let atama_sonucu = { x = 9; };
    println!("x simdi: {}", x);
    println!("atamanin kendi degeri: {:?}", atama_sonucu);
}
x baslangicta: 1
x simdi: 9
atamanin kendi degeri: ()

x = 9 bir ifade olarak değerlendiriliyor — xin yeni değeri belleğe yazılıyor — ama bu ifadenin kendi değeri (). atama_sonucu adlı bağlama bu ()i tutuyor, 9u değil. xin yeni değerine ulaşmanın tek yolu xin kendisini okumaktır; atamanın sonucunu okumak hiçbir zaman işe yaramaz, çünkü orada okunacak bir değer yok.

Bu, önceki dersteki bir gözlemi tamamlıyor: if, match, loop ve blok birer değerdir, ama let ve atama birer değer değildir. Aynı dilde hem “bu bir değerdir” hem “bu bir değer değildir” diyen iki grup yazımın bir arada durması bilinçli bir tercihtir. Bir dallanma ya da bir döngünün değer üretmesi, o yazımın sonucunun doğrudan bir bağlamaya aktarılmasını sağlıyor; bir bağlamanın kendisinin değer üretmesi ise anlamsız olurdu, çünkü bağlama zaten kendisi bir sonuç değil, bir sonucu saklama işlemidir. Değer üretmek ile bir yer ayırıp oraya yazmak, kaynakta iki ayrı eylem olarak kalıyor ve sınıflandırma bu ikisini karıştırmıyor.

Gölgeleme ve mut: Aynı Sonuç, İki Farklı Yol

Aynı adı yeniden bağlamanın Rust’ta iki yolu var. Biri gölgeleme (shadowing): let anahtar sözcüğü tekrar yazılır ve yepyeni bir bağlama kurulur; eski bağlama artık o adla erişilemez ama yok olmamıştır. Öbürü mut: bağlama bir kez let mut ile kurulur, sonraki satırlar = ile doğrudan üzerine yazar; burada yeni bir bağlama yok, var olan bağlamanın tuttuğu değer değişiyor. İkisini yan yana koşturarak aynı iki adımı (üç ekle, iki ile çarp) uygulayalım.

// baglama.rs — golgeleme ile mut arasindaki fark
fn main() {
    // golgeleme: ayni ad uc kez baglaniyor, her biri yeni bir baglama
    let deger = 5;
    let deger = deger + 3;
    let deger = deger * 2;
    println!("golgeleme sonucu: {}", deger);

    // mut: bir kez baglaniyor, iki kez degistiriliyor (ayni baglamaya yazma)
    let mut sayac = 5;
    sayac = sayac + 3;
    sayac = sayac * 2;
    println!("mut sonucu: {}", sayac);

    println!("esit mi: {}", deger == sayac);
}
golgeleme sonucu: 16
mut sonucu: 16
esit mi: true

İki yol da 16ya ulaşıyor, ama vardıkları yer aynı olsa da oraya gidiş biçimleri farklı. Gölgelemede üç ayrı deger bağlaması var — ikincisi birinciyi okuyup yeni bir bağlama kuruyor, üçüncüsü ikinciyi okuyup bir yenisini daha kuruyor. mut tarafında ise tek bir sayac bağlaması var; iki atama satırı da aynı bağlamanın üzerine yazıyor, yeni bir bağlama kurmuyor.

Bu iki yolu ayırt eden soru şudur: bir sonraki satır, önceki satırın kurduğu adı mı okuyor, yoksa önceki satırın kurduğu yere mi yazıyor? Gölgelemede her let deger satırı önce sağdaki ifadeyi (bir önceki degeri kullanarak) değerlendiriyor, sonra bu sonucu tamamen yeni bir bağlamaya yerleştiriyor — okuma ve yeni bağlama kurma iki ayrı adımdır. mut tarafında okuma ve yazma aynı bağlama üzerinde gerçekleşiyor; `sayac = sayac

  • 3satırı öncesayac`ın o anki değerini okuyor, sonra aynı bağlamaya yeni değeri yazıyor. Sonuç ikisinde de bir toplama ve bir çarpmadır, ama biri adı üç kez yeniden kullanırken öbürü aynı adı hiç bırakmıyor.

RD9. Gölgelemede eski bağlama “silinmiyor”, yalnız adıyla erişilemez hâle geliyor. İlk deger (5) ile ikinci deger (8) hâlâ bellekte bir yerde bulunabilir, ama kaynakta onlara ulaşacak bir ad kalmadı — üçüncü let deger ile o ad son bağlamaya taşındı. Bu davranış K02’nin konusu olan bellek yönetimiyle ilgilidir; burada yalnız gözlemlenen şudur: gölgeleme adı yeniden kullanıyor, bağlamayı değil.

Gölgelemenin adı yeniden kullanması, önceki dersteki bir başka olguyla birleşiyor: blok kendisi bir ifadedir ve kendi kapsamını taşır. Bir bloğun içindeki gölgeleme, blok kapandığında etkisini kaybediyor.

// golgeleme_kapsam.rs — golgeleme kapsam icinde kalir
fn main() {
    let deger = 10;
    let iceride = {
        let deger = deger * 100;
        deger
    };
    println!("blok icindeki golgeleme: {}", iceride);
    println!("disaridaki deger degismedi: {}", deger);
}
blok icindeki golgeleme: 1000
disaridaki deger degismedi: 10

İç bloktaki let deger = deger * 100; dış degeri okuyup kendi kapsamında yeni bir bağlama kuruyor; bu yeni bağlama yalnız bloğun içinde geçerli. Blok kapandığında dış deger hiç dokunulmamış hâliyle geri geliyor — iç bağlama dış bağlamanın üzerine hiçbir zaman yazmadı, yalnız aynı adı kendi dar kapsamında ödünç aldı. Bu, gölgelemenin muttan ayrıldığı ikinci nokta: mut ile yapılan bir değişiklik kapsam sınırı tanımaz, aynı bağlamanın olduğu her yerde görünür; gölgeleme ise her let ile hem yeni bir bağlama hem de o bağlamanın kapsamını yeniden kurar.

Gölgelemenin Tip Değiştirebilmesi

Bu iki yol arasındaki fark yalnız mekanik değil, yetenek farkıdır da. Gölgeleme her seferinde yepyeni bir bağlama kurduğu için, yeni bağlamanın tipi eskisiyle aynı olmak zorunda değildir.

// golgeleme_tip.rs — golgeleme ayni adin tipini degistirebilir
fn main() {
    let deger = 21;
    let deger = deger * 2;
    let deger = format!("sonuc={}", deger);
    println!("{}", deger);
}
sonuc=42

İlk deger bir tam sayı, üçüncü deger bir dizgi (String). Üçü de aynı adı taşıyor ama üçü de birbirinden bağımsız bağlamalar; sonuncusu öncekinin tipini bilmek zorunda değil, yalnız değerini okuyor ve yeni bir tipte yeni bir bağlama kuruyor.

mut ile aynı yazım denenirse derleyici reddeder: let mut deger = 21 satırından sonra degere bir dizgi biçimlendirme çağrısının sonucunu atamaya çalışan bir satır derlenmez, çünkü mut yeni bir bağlama kurmuyor, var olan bağlamanın aynı tipteki başka bir değerle değiştirilmesine izin veriyor. Bağlama bir kez i32 olarak kurulduktan sonra, o bağlamaya bir String yazılamaz — derleyici için bu, farklı boyuttaki iki değeri aynı yere sığdırmaya çalışmakla eşdeğerdir. Gölgeleme bu kısıtı taşımıyor, çünkü her let derleyiciye “burada yeni bir yer ayır” diyor; mut ise “var olan yere yaz” diyor ve o yerin biçimi ilk let ile sabitleniyor.

Bu fark, gölgelemenin neden yalnız bir kısayol olmadığını gösteriyor. Aynı adı taşıyan bir dizi dönüşüm zincirinde (bir dizgiyi ayrıştırıp bir sayıya, sonra o sayıyı biçimlendirilmiş bir dizgiye çevirmek gibi) her adımın kendi doğal tipinde kalmasına izin veriyor; okuyan taraf her adımda hangi tipte olduğunu bağlamanın en son let satırından okuyor, ara adımların tipini takip etmek zorunda kalmıyor. golgeleme_tip.rs örneğinde bu zincir üç adımlıydı: tam sayı olarak başladı, tam sayı olarak katlandı, dizgi olarak biçimlendi. mut ile aynı zincir yazılmak istenseydi, ya üç ayrı ad gerekirdi (ham_deger, katlanmis_deger, bicimli_deger), ya da her adımın aynı tipte kalması için ekstra bir dönüştürme adımı eklemek gerekirdi. Gölgeleme bu iki seçeneği de gereksiz kılıyor: tek bir ad, dönüşümün mantıksal adımlarını izliyor, ama her adımın kendi tipini taşımasına izin veriyor.

Bu yeteneğin bir bedeli de var: aynı adın art arda kaç kez gölgelendiğini takip etmek, okuyan taraf için muta göre daha fazla dikkat gerektirir. mut bir bağlamanın tipini asla değiştirmediği için, kodun ortasında sayacı gördüğünde tipin ne olduğu ilk let muttan bellidir. Gölgelemede ise aynı garantiyi vermek için en yakın let deger satırına kadar geriye gitmek gerekir — üç satır önceki deger başka bir tip taşıyor olabilir. Kursun geri kalanında gölgeleme genellikle kısa, birbirine yakın satırlar arasında kullanılacak; bu yakınlık, tip takibinin okuma sırasında kaybolmamasını sağlıyor.

Sınırlayıcı Ölçüm: Değişmezlik Bağlamanın Sınırındadır

Önceki iki bölüm değişmezliği hep tek bir değer üzerinden gösterdi — bir tam sayı ya da bir dizgi. Değişmezliğin, birden çok bileşeni bir arada tutan bir değer üzerinde nasıl davrandığını netleştirmek gerekiyor. mut yazılmadan kurulan bir bağlama yalnız kendi değerini değil, değerin bütün iç alanlarını da korur — bir demedin (tuple) tek bir bileşenini bile ayrıca değiştiremezsin.

// degismezlik_siniri.rs — baglama degismezken ic alanin durumu
fn main() {
    let nokta = (3, 4);
    println!("baslangic: ({}, {})", nokta.0, nokta.1);

    let nokta2 = (nokta.0 + 1, nokta.1);
    println!("yeni baglama: ({}, {})", nokta2.0, nokta2.1);
    println!("eski baglama degismedi: ({}, {})", nokta.0, nokta.1);
}
baslangic: (3, 4)
yeni baglama: (4, 4)
eski baglama degismedi: (3, 4)

nokta hiç mut almadan kuruldu. Onu “güncellemek” isteseydik nokta.0 = 4; yazmak gerekirdi — bu satır derlenmez, çünkü nokta değişmez bir bağlamadır ve bu değişmezlik demedin her bileşenine birden uygulanır, yalnız dışarıdan görünen ada değil. Bunun yerine kod yeni bir bağlama (nokta2) kurdu ve eskisinin bileşenlerinden ikisini okuyup üçüncü bir değeri hesapladı; nokta çıktının son satırında görüldüğü gibi hiç değişmedi. Yeni bağlama eskisinin üzerine değil, eskisinin yanına kuruldu; iki bağlama aynı anda, birbirinden bağımsız olarak var olmaya devam ediyor.

RD10. Bu, “değişmezlik derindir” gibi okunabilir ama tam resim değildir. Burada gösterilen şey, bir bağlamanın kendisinin değişmez olduğu — o bağlamaya ait hiçbir bileşen mut olmadan yeniden yazılamaz. Ama aynı verinin başka bir bağlama ya da referans üzerinden paylaşılıp paylaşılamayacağı, paylaşılırsa değişmezliğin oradan da geçerli olup olmayacağı — bu, sahiplik ve ödünç alma kurallarının sorusudur ve K02’nin konusudur. Burada çizilen sınır şu: bir bağlamanın mut almaması, o bağlamanın kendi adı üzerinden hiçbir değişikliğe izin vermeyeceğini garanti eder; verinin tamamı hakkında daha geniş bir garanti değildir.

Bu sınır, önceki iki dersteki sınırlayıcı ölçümlerle aynı ailededir. Rust’ın Tasarım Hedefleri dersinde bir yöntemin sınıfı çağrı yerine değil imzasına bakılarak belirleniyordu; Kurulum ve Paket Yöneticisi dersinde bir ikilinin adı kaynağa değil bildirim dosyasına bakılarak belirleniyordu. Burada da benzer bir kalıp var: bir bağlamanın “ne kadar değişmez” olduğu, yalnız o bağlamanın kendi bildirimine (mut var mı yok mu) bakılarak belirleniyor — verinin başka yerlerde nasıl paylaşıldığına bakılmadan. Üç ders de aynı dersi tekrarlıyor: Rust’ta bir garanti okurken, garantinin nereden geldiğini bilmek, garantinin kendisi kadar önemli.

Özet

  • let ad = deger; bir deyimdir ve hiçbir yerde bir değer olarak kullanılamaz; sağ taraftaki ifade değerlendirilip ada bağlanır.
  • Bir atamanın kendisi de () üretir; yeni değere ulaşmanın tek yolu adı okumaktır.
  • Gölgeleme her let ile yepyeni bir bağlama kurar; mut ile yeniden atama var olan bağlamanın üzerine yazar. İkisi de aynı sayısal sonuca ulaşabilir.
  • Yalnız gölgeleme bağlamanın tipini değiştirebilir, çünkü her adımda yeni bir yer ayrılır; mut bağlamanın ilk tipine bağlı kalır.
  • Bir bağlamanın değişmez olması, o bağlamanın bütün iç bileşenlerini kapsar, ama verinin başka bir yoldan paylaşılıp paylaşılamayacağı sorusuna yanıt vermez.
  • Gölgeleme kapsamla sınırlıdır: bir blok içindeki gölgeleme, blok kapandığında dış bağlamayı hiç etkilemeden kaybolur; mut ile yapılan değişiklik kapsam tanımaz.

Sonraki Adım

Bağlamaların taşıdığı değerlerin kendisi henüz tek tek incelenmedi — yalnız tam sayılar görüldü. Sıradaki ders Rust’ın temel tiplerine iner: tam sayı, kayan noktalı, mantıksal, karakter ve demet ve her birinin bir ifade olarak ürettiği değerin ne olduğunu, ()in de neden bu tiplerden biri sayıldığını koşturarak gösterir. Bu derste kurulan bağlama kavramı orada değişmeyecek — yalnız bağlanan değerlerin çeşidi genişleyecek ve her tipin kendi sınırları (bir tam sayının taşabileceği aralık, bir demedin sabit uzunluğu gibi) tek tek ölçülecek.

İ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