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
mutolmasa da,RefCellile 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 (
Rckopyası) arasında paylaşıldığında daRefCellile 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ılandeftertipi 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 birborrowç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.