İçeriğe geç
academia.sh

Ders 13 / 14

İç Değişebilirlik

Aynı 'bir yazan ya da çok okuyan' kuralının derleme zamanı yerine çalışma zamanında, bir sayaçla sınandığı ölçülüyor; bir bağlamanın kendisi değişmezken içindeki değerin RefCell ile değişebildiği ve başarısız bir ödünç denemesinin panik değil bool sonucuyla alınabildiği gösteriliyor. Kural kaybolmuyor, yer değiştiriyor.

Yayın Güncelleme

Bu derste

  • Aynı "bir yazan ya da çok okuyan" kuralının derleme yerine çalışma zamanında sınandığını koşturarak gösterir
  • Bir bağlamanın kendisi değişmezken içindeki değerin RefCell ile değişebildiğini örnekle kanıtlar
  • Başarısız bir ödünç denemesinin panik değil bool sonucuyla alınabildiğini gösterir
İçindekiler

Önceki üç ders boyunca paylaşılan bir değer hep okunabildi; Rc ve Arcta hiç değiştirilemedi, Boxta ise tek sahip olduğu için (bağlama mut alırsa) değiştirme zaten mümkündü. Değişkenler ve Değişmezlik dersi de bir soru açık bırakmıştı: bir bağlamanın mut almamasının, o bağlamanın kendi adı üzerinden hiçbir değişikliğe izin vermeyeceğini garanti ettiği kurulmuştu, 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ğı sorusu bu kursa (K02) bırakılmıştı. Bu ders tam o soruyu yanıtlıyor: bir değeri, sahiplik paylaşılırken de, bağlama değişmez görünürken de değiştirmenin bir yolu var mı? Bu kursta ölçülen hep aynı kalıyor — bir değerin nerede bittiği —, Eşzamanlılık kursunda (M09/K03) ölçülen koşan bir birimin nasıl duracağı değil.

Yanıt evet, ama bu değişikliğin bir bedeli var; bu ders o bedeli ölçüyor ve bu konunun son dersi olarak konunun dört ölçümünü de bir araya getiriyor.

Atomik Referans Sayımı dersi bu ihtiyacı açıkça bıraktı: Arc üzerinden bir alana yazmaya çalışan bir satır derlenmiyordu, çünkü paylaşılan bir değere aynı anda kaç kopyanın erişebileceğini bilmenin, o değeri güvenle değiştirmek için yeterli olmadığı gösterilmişti. Aynı sınır Rc için de geçerliydi — Boxta tek sahip olduğu için değiştirme zaten mümkündü (bağlama mut alırsa), ama Rc ve Arcta paylaşım başladığı andan itibaren yazma kapandı, çünkü ikisi de içerdikleri değere yalnızca paylaşımlı erişim veriyordu. Bu derste o kapı yeniden açılıyor, ama farklı bir anahtarla: tipin kendisinden değil, çalışma zamanında sınanan bir izinden geçerek.

Kuralın Yer Değiştirmesi

Ödünç Denetleyicisi dersi, “aynı anda ya bir değiştirilebilir referans ya da birden çok paylaşımlı referans” kuralının derleme zamanında sınandığını gösterdi: bu kuralı ihlal eden bir yazım hiç derlenmiyordu. RefCell<T>, aynı kuralı çalışma zamanına taşıyan bir kaptır. İçindeki değere borrow() ile paylaşımlı, borrow_mut() ile değiştirilebilir erişim istenir; RefCell bu istekleri bir sayaçla izler ve kuralı ihlal eden bir istek derlenmez değil, çalışma zamanında reddedilir.

Bu derste redd bir panikle değil, try_borrow() ve try_borrow_mut() çağrılarının döndürdüğü bir sonuçla ölçülüyor: istek uygunsa Ok, değilse Err. Panik metni hiçbir yerde basılmıyor; yalnızca isteğin başarılı olup olmadığı bir bool olarak yazdırılıyor.

RefCell de, tıpkı Rc ve Arc gibi, bir sayaç tutar — ama sayılan şey farklıdır. Rc ve Arcın sayacı kaç sahibin olduğunu sayıyordu ve sıfıra inince değer bitiyordu. RefCellin sayacı kaç aktif ödüncün olduğunu sayar ve değeri hiç bitirmez; yalnızca o an hangi tür erişimin (paylaşımlı mı, değiştirilebilir mi) sürdüğünü izler. Bu iki sayaç birbirinden bağımsız çalışabilir: bir Rc<RefCell<T>> değeri hem kaç kopyasının olduğunu hem de o an kimin onu ödünç aldığını, iki ayrı sayaçla aynı anda tutabilir. Bir kopya bitip sahiplik sayacı azalırken, ödünç sayacı o kopyanın o an bir ödünç tutup tutmadığından tamamen habersizdir — ikisi ayrı soruları yanıtlıyor.

Bağlamanın Değişmezliği İçeriği Bağlamıyor

Aşağıdaki koşum bunu doğrudan gösteriyor: sayac adlı bağlama mut değil, ama içindeki tam sayı yine de değişiyor.

PS10. — “bağlamanın kendisi değişmezken içindeki değerin değişebildiği” iddiası, aşağıdaki koşumda let sayac: RefCell<i64> = RefCell::new(0); satırının mut içermemesine rağmen ilk yazimdan sonra deger: 1 satırının basılmasıyla doğrulanıyor.

// odunc_zaman_kaydirmasi.rs — derleme zamaninda reddedilen kural, calisma zamaninda bir sonuca donusuyor
use std::cell::RefCell;

fn main() {
    let sayac: RefCell<i64> = RefCell::new(0); // baglama mut degil

    {
        let mut yazan = sayac.borrow_mut();
        *yazan += 1;
    }
    println!("ilk yazimdan sonra deger: {}", sayac.borrow());

    let ilk_okuma = sayac.borrow();
    let ikinci_okuma = sayac.borrow();
    println!("iki okuma birlikte: {} ve {}", *ilk_okuma, *ikinci_okuma);

    let yazma_denemesi = sayac.try_borrow_mut();
    println!("okuma surerken yazma denemesi basarili mi: {}", yazma_denemesi.is_ok());

    drop(ilk_okuma);
    drop(ikinci_okuma);

    let ikinci_yazma_denemesi = sayac.try_borrow_mut();
    println!(
        "okumalar birakildiktan sonra yazma denemesi basarili mi: {}",
        ikinci_yazma_denemesi.is_ok()
    );
}
ilk yazimdan sonra deger: 1
iki okuma birlikte: 1 ve 1
okuma surerken yazma denemesi basarili mi: false
okumalar birakildiktan sonra yazma denemesi basarili mi: true

sayac bağlaması hiçbir yerde mut taşımıyor, ama borrow_mut() çağrısı içindeki değeri gerçekten değiştiriyor. Değişkenler ve Değişmezlik dersi, mut almamanın adın kendisi üzerinden hiçbir değişikliğe izin vermeyeceğini kurmuştu; RefCell bu garantiyi bozmuyor — sayac adının kendisi hâlâ yeniden bağlanamıyor, hiçbir alanı sıradan bir atamayla değiştirilemiyor. borrow_mut(), adın kendisinden değil, RefCellin çalışma zamanında kontrol ettiği ayrı bir kapıdan giriyor. ilk_okuma ve ikinci_okuma aynı anda yaşarken ikisi de aynı değeri okuyabiliyor — “çok okuyan” izinli. Onlar hâlâ yaşarken gelen yazma denemesi başarısız oluyor; ikisi de bırakıldıktan sonra aynı deneme başarılı oluyor. Kural aynı: bir yazan ya da çok okuyan, ikisi birden değil.

İki Yazan da Aynı Kuralı İhlal Eder

Kural yalnızca “yazarken okunmasın” değil, “aynı anda iki yazan olmasın” kısmını da kapsıyor. Aşağıdaki koşum bunu ayrıca sınıyor: bir değiştirilebilir ödünç sürerken ikinci bir değiştirilebilir ödünç istendiğinde de aynı redd oluyor.

PS11. — “aynı anda iki değiştirilebilir ödüncün de reddedildiği” iddiası, aşağıdaki koşumun ilk yazan aktifken ikinci yazma denemesi basarili mi: false satırıyla doğrulanıyor.

// iki_yazici_denemesi.rs — ayni anda iki degistirilebilir odunc de reddediliyor
use std::cell::RefCell;

fn main() {
    let veri: RefCell<Vec<i32>> = RefCell::new(vec![1, 2, 3]);

    let mut ilk_yazan = veri.borrow_mut();
    ilk_yazan.push(4);

    let ikinci_yazma_denemesi = veri.try_borrow_mut();
    println!(
        "ilk yazan aktifken ikinci yazma denemesi basarili mi: {}",
        ikinci_yazma_denemesi.is_ok()
    );
    drop(ilk_yazan);

    let simdi_basarili_mi = veri.try_borrow_mut();
    println!("ilk yazan birakildiktan sonra basarili mi: {}", simdi_basarili_mi.is_ok());
    drop(simdi_basarili_mi);
    println!("veri uzunlugu: {}", veri.borrow().len());
}
ilk yazan aktifken ikinci yazma denemesi basarili mi: false
ilk yazan birakildiktan sonra basarili mi: true
veri uzunlugu: 4

ilk_yazan hâlâ elde tutulurken açılan ikinci deneme de reddediliyor — bu kez çakışan iki okuma değil, iki yazma isteği. ilk_yazan üzerinden veriye bir eleman eklenmiş olması (push(4)), koruyucunun bırakıldığı anda kalıcı hâle geliyor: son satırdaki uzunluk dört. Sayaç, kaç okuyucunun aktif olduğunu değil, aktif erişimin türünü de ayırt ediyor — bir yazan varken ne başka bir yazan ne de bir okuyan kabul ediliyor.

Bu iki koşum (önceki bölümdeki ve bu bölümdeki) birlikte kuralın tamamını kapsıyor: çok okuyan izinliyken, bir okuyanla bir yazan da, iki yazan da izinsizdi. Üç durumun ikisi try_borrow ya da try_borrow_mutin Err döndürmesiyle, kalanı Ok döndürmesiyle ölçüldü; hiçbirinde panik metni basılmadı, hiçbirinde program çökmedi. Ödünç Denetleyicisi dersinde derleme zamanında reddedilen “okurken değiştirme” ihlali, burada aynı adla, ama çalışma zamanında bir Err olarak karşımıza çıkıyor.

Kural Kaybolmuyor, Yer Değiştiriyor

Ödünç Denetleyicisi dersindeki üç redd örneğinin hiçbiri kod bloğuna girmedi — hepsi düzyazıda tarif edildi, yanlarına kabul edilen çözüm kondu; ihlal eden kaynak hiçbir zaman derlenmedi. Burada aynı ihlal türü — bir değer yazılırken aynı değere başka bir erişim istenmesi — gerçekten derleniyor; yalnızca çalışma zamanında Err üretiyor. Bu, kuralın ortadan kalktığı anlamına gelmiyor: derleme zamanında kaçınılan bir hata, çalışma zamanında bir sonuca dönüşüyor. Yukarıdaki koşumun false basan yazma denemesi satırı, Ödünç Denetleyicisi dersinde hiç derlenmeyecek türden bir ihlalin burada derlendiğini ve çalışma zamanında yakalandığını gösteriyor — program çökmüyor, çünkü redd try_borrow_mut ile panik yerine bir Resulte dönüştürülüyor.

Bu değiş tokuşun bedeli şudur: derleme zamanındaki denetim, bir yazımı çalıştırmadan önce eler; çalışma zamanındaki denetim, aynı ihlali ancak o satır çalıştığında yakalar. Yanlış kod artık derlenmiyor değil, çalışırken kendi başarısızlığını bildiriyor — ama bu bildirim ancak o satıra gelindiğinde olur; ihlale hiç uğramayan bir çalıştırma, sorunu hiç göstermez. Ödünç Denetleyicisi dersinde ölçülen “denetleyici doğruluğu değil, sahiplik disiplinini güvence altına alır” ilkesi burada bir adım daha ileri gidiyor: çalışma zamanı denetimi de yalnızca çalışan yolu sınıyor, kodun hiç uğramadığı bir satırdaki ihlali göremiyor.

Defterin Kendisi Bu Derste Açıklanıyor

Değişkenler ve Değişmezlik dersinin K02’ye bıraktığı soru tam olarak buydu: aynı veri başka bir bağlama ya da referans üzerinden paylaşıldığında, değişmezlik oradan da geçerli mi kalıyor? Bu kursun bütün derslerinde kullanılan defter, bu sorunun yanıtını baştan beri örtük olarak taşıyordu. defter, Rc<RefCell<Vec<String>>> tipinde bir değerdi. Referans Sayımı dersi Rc kısmını, bu ders RefCell kısmını açıklıyor: defter paylaşılıyordu (Rc) ve içeriği değiştirilebiliyordu (RefCell) — iki kap iç içe kullanılarak hem paylaşım hem değişebilirlik aynı anda sağlanıyordu.

PS12. — “bir işlevin &Defter alıp içeriği değiştirebildiği” iddiası, aşağıdaki koşumda kayit_ekle işlevinin parametresinin &Defter olmasına (değiştirilebilir referans değil) rağmen kayit sayisi: 2 satırının basılmasıyla doğrulanıyor.

// defterin_ic_degisebilirligi.rs — bu kurs boyunca kullanilan defter tipi boyle calisiyordu
use std::cell::RefCell;
use std::rc::Rc;

type Defter = Rc<RefCell<Vec<String>>>;

struct Iz {
    ad: String,
    defter: Defter,
}

impl Iz {
    fn yeni(ad: &str, defter: &Defter) -> Iz {
        Iz {
            ad: ad.to_string(),
            defter: Rc::clone(defter),
        }
    }
}

impl Drop for Iz {
    fn drop(&mut self) {
        self.defter.borrow_mut().push(self.ad.clone());
    }
}

fn kayit_ekle(defter: &Defter, metin: &str) {
    // defter parametresi & ile alindi, mut degil; yine de icerik degisiyor.
    defter.borrow_mut().push(metin.to_string());
}

fn main() {
    let defter: Defter = Rc::new(RefCell::new(Vec::new()));
    kayit_ekle(&defter, "elle-eklenen");
    {
        let _olcum = Iz::yeni("birakildiginda-eklenen", &defter);
    }
    let kayit = defter.borrow();
    println!("kayitlar: {}", kayit.join(", "));
    println!("kayit sayisi: {}", kayit.len());
}
kayitlar: elle-eklenen, birakildiginda-eklenen
kayit sayisi: 2

kayit_ekle işlevi defter: &Defter alıyor — &Rc<RefCell<Vec<String>>>, yani sıradan bir paylaşımlı referans. Buna rağmen defter.borrow_mut().push(...) çağrısı listeye gerçekten bir eleman ekliyor. Bu, kursun ilk dersinden beri her Iz::drop çağrısının yaptığı şeyin ta kendisidir: drop işlevi &mut self alsa bile, self.defter bir Rc olduğu için doğrudan değiştirilemiyordu — self.defter.borrow_mut().push(...) satırı, &mut selfin kendi verdiği erişimi değil, RefCellin verdiği çalışma zamanı erişimini kullanıyordu. Kursun başından beri koşan her koşum, aslında bu dersin ölçtüğü düzeneği arka planda kullanıyordu — yalnızca bu ders o düzeneği açıkça adlandırıyor.

Bu, defterin neden Vec<String> değil de Rc<RefCell<Vec<String>>> olarak tanımlandığını da açıklıyor. Vec<String> tek başına ne paylaşılabilir ne de birden çok yerden değiştirilebilir olurdu — bir işlevin ya sahibi olması ya da ona sıradan bir referans vermesi gerekirdi ve sıradan bir referansla push çağırmak &mut gerektirirdi. Rc paylaşımı, RefCell değişebilirliği getirdi; ikisi birlikte, Iz::yeni her çağrıldığında yalnızca &Defter geçirilmesine rağmen her Iz kendi bitişini aynı listeye yazabildi. Kursun ilk üç dersi bu iç içe kabın yalnızca Rc yarısını konu etti; bu ders öbür yarısını tamamlıyor.

Erişim Şeffaf Değil, Açık Bir Çağrı Gerektiriyor

Öbekte Tekil Sahiplik dersi, bir Box içindeki alanlara doğrudan erişilebildiğini gösterdi: nokta.x yazımı, değer öbekte olsa da olmasa da aynıydı. Atomik Referans Sayımı dersinde de paylasilan.deger doğrudan okunabiliyordu. RefCell bu şeffaflığı sürdürmüyor: içindeki değere doğrudan hucre.alan yazılarak erişilemez, önce borrow() ya da borrow_mut() çağrılıp bir koruyucu değer alınması gerekir. Bu fark rastlantı değil — Box, Rc ve Arc içindeki değere erişimin her zaman güvenli olduğunu derleme zamanında biliyordu, bu yüzden erişimi gizleyebiliyordu. RefCell ise güvenliği ancak çalışma zamanında, her çağrıda yeniden sınıyor; bu sınamanın kaynakta görünür olması gerekiyor, çünkü sınama başarısız da olabilir.

Bu görünürlük, try_borrow_mutin döndürdüğü Result değerinde somutlaşıyor: bir Box alanına yazmak asla başarısız olmayan bir işlemken, bir RefCelle yazmak denenip sonucu kontrol edilmesi gereken bir işlemdir. Kod bu farkı gizleyemiyor — çağıran taraf, isteğinin kabul edilip edilmediğini bir sonraki satırda ele almak zorunda kalıyor. Bu, kursun üç ölçüm boyunca izlediği bir düzenin son adımıdır: bir işlemin ne zaman kesin, ne zaman koşullu olduğu, o işlemin döndürdüğü tipten okunabiliyor — () her zaman başarır, Result başarabilir ya da başarısız olabilir.

Dört ölçüm bir araya geldiğinde konunun bütün resmi ortaya çıkıyor. Öbekte Tekil Sahiplik, bir değerin öbekte durabildiğini ama sahibinin tek kaldığını kurdu. Referans Sayımı, bu tekliği sayılabilir bir çokluğa açtı. Atomik Referans Sayımı, bu sayımın farklı bağlamlarda da aynı sonucu verdiğini, ama paylaşılan verinin hâlâ değiştirilemez kaldığını gösterdi. Bu ders, o son kısıtı bir bedelle kaldırdı: değiştirme artık mümkün, ama artık her deneme bir sonuç döndürüyor ve o sonucun kontrol edilmesi gerekiyor. Konu boyunca hiçbir adımda belirlenimcilik kaybolmadı — her bitiş, her sayaç değişimi, her ödünç denemesi, koşumda aynı sırayla, aynı sonuçla üretildi.

Özet

  • RefCell<T>, “bir yazan ya da çok okuyan” kuralını derleme zamanı yerine çalışma zamanında, bir sayaçla sınayan bir kaptır; içindeki değerin ömrü kabıyla birlikte bitiyor.
  • Bir bağlamanın kendisi mut olmasa da, RefCell ile içindeki değer değişebilir; adın kendisi hâlâ yeniden bağlanamaz, değişim ayrı bir kapıdan geçer.
  • Aynı veri birden çok bağlama (Rc kopyası) arasında paylaşıldığında da RefCell ile değiştirilebilir; bu, K01’in K02’ye bıraktığı “paylaşımda değişmezlik ne olur” sorusunu yanıtlar — kursun başından beri kullanılan defter tipi tam olarak bu düzenekti.
  • Başarısız bir ödünç denemesi panikle değil, try_borrow/try_borrow_mutin döndürdüğü bir sonuçla ölçülür.
  • Kural kaybolmaz, yer değiştirir: derleme zamanında kaçınılan bir ihlal, çalışma zamanında bir sonuca (Err) dönüşür.
  • RefCell üzerinden erişim şeffaf değildir; her erişim açık bir borrow çağrısı gerektirir, çünkü çağrı her zaman başarılı olmayabilir.

Sonraki Adım

Bu konunun dört dersi boyunca bir değerin sahipliği çoğaldı, sayıldı, atomikleşti ve en sonunda paylaşılırken değiştirilebilir hâle geldi — ama her seferinde tek bir varsayım hiç sorgulanmadı: paylaşan değerler birbirinden bağımsızdı, hiçbiri bir ötekini tutmuyordu. Her sayaç, kendi değerine ait kalan kopyaları sayıyordu; hiçbir kopya, sayılan değerin kendisini geriye doğru tutmuyordu. Kursun son sorusu tam bunu soruyor: ya iki değer, RefCell üzerinden birbirinin içine birer Rc koyarak birbirini tutarsa? Sayaç o zaman hâlâ sıfıra inebilir mi, yoksa hiç inemez mi?

İ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