Ders 03 / 14
Ödünç Alma
Bir değer sahipliği hiç vermeden bir referans üzerinden okunabilir ve yazılabilir: ödünç sürerken defterde hiçbir kayıt oluşmaz, ödünç kapsamı kapandığında değer hâlâ sahibinde erişilebilir durumdadır. Paylaşımlı ve değiştirilebilir referans birbirini dışlar; bir referansın kapsamı, kaynağın kapsamının her zaman içinde kalmak zorundadır.
Yayın Güncelleme
Bu derste
- Ödünç alınan bir değerin ödünç sürerken hiçbir yere bitmediğini ve ödünç kapsamı kapandıktan sonra hâlâ sahibinde erişilebilir olduğunu koşturarak gösterir
- Paylaşımlı ve değiştirilebilir referans arasındaki farkı örnekleyerek ayırt eder
- Bir referansın kapsamının kaynağın kapsamı içinde kalmak zorunda olduğunu iç içe kapsamlarla kanıtlar
İçindekiler
Önceki ders sahiplik devrettiğinde bitiş noktasının nasıl taşındığını ölçtü — taşınan değerin eski adı geçerliliğini kaybediyordu. Bu ders sahiplik hiç devredilmeden bir değere nasıl erişildiğini ölçüyor: ödünç alma. Soru şu: bir değeri ödünç veren ad değeri kaybediyor mu ve ödünç alınan değer ödünç sürerken bir yere bitiyor mu? Eşzamanlılık kursunun (M09/K03) “bir goroutine nasıl duracak” sorusuyla karışmaz: orada koşan bir birim durur, burada bir değer biter.
Önceki dersin son bölümü, bir değerin bir işleve tek yönlü ve geri dönüşsüz taşınabileceğini gösterdi — ya da sahip zincir boyunca değişse de değer sonunda mutlaka bir yerde bitti. Bu ders aynı işlevlere geçen değerlerde farklı bir yol açıyor: bir değeri hiç taşımadan, yalnız ona geçici bir erişim vererek de aynı işi yapabiliyoruz. İki yol da geçerli, ama sonuçları kaynakta ayrı ayrı okunabiliyor.
Programlama Temelleri kursu (M01/K02) bir değerin bir işleve referans olarak nasıl geçtiğini kavram olarak kurmuştu. Bu derste ölçülen, Rust’ın bu geçişi iki ayrı, birbirini dışlayan biçime ayırması — paylaşımlı ve değiştirilebilir referans — ve bu ayrımı derleme zamanında sınaması.
Ödünç Almak Sahiplik Vermek Değildir
Bir referans (&T ya da &mut T), işaret ettiği değerin sahibi değildir; yalnız o değere
erişim sağlayan ayrı, küçük bir değerdir. Referansın kendisinin de bir kapsamı var ve o
kapsam kapandığında referans biter, ama referansın bitmesi işaret ettiği değeri
etkilemez — çünkü referans zaten o değerin sahibi olmamıştı, kapsamı kapanırken
Drop’u tetikleyecek bir şeyi yoktur.
Rust iki tür referansı ayırıyor. Paylaşımlı referans (&T) aynı değere aynı anda
birden çok kez alınabilir ve yalnız okumaya izin verir. Değiştirilebilir referans
(&mut T) aynı anda yalnız bir tane olabilir ve değeri okumanın yanında değiştirmeye de
izin verir. İkisi aynı anda, aynı değer için bir arada bulunamaz.
Bu iki tür bir seçenek değil, bir zorunluluk. Bir değere erişmek isteyen her kod parçası —
bir işlev parametresi, bir yerel bağlama, bir yapı yöntemi — hangi türü istediğini önceden
söylemek zorunda; söz dizimindeki tek fark bir mut anahtar sözcüğü olsa da, ürettiği
yetki tamamen farklı. Bu ayrım kaynakta görünür olduğu için, bir okuyucu bir referansın
değeri değiştirip değiştiremeyeceğini, o referansın alındığı satıra bakarak söyleyebiliyor
— değerin kendi tanımına ya da nasıl kullanıldığına bakmadan.
SK7. Bir referans kendi başına bir değerdir ve kendi kapsamı vardır, ama Iz gibi
Drop uygulayan bir tipin referansı Drop’u uygulamaz. &Iz ya da &mut Iz tipinde bir
değer kapsamının sonunda sessizce yok olur; deftere hiçbir şey yazmaz, çünkü yazma işini
yapan kod yalnız Izin kendi drop yönteminde ve o yöntem yalnız sahip olunan bir Iz
bittiğinde çağrılır.
Bu ayrım önceki dersle doğrudan karşıtlık kuruyor. Sahiplik ve Taşıma dersinde bir işleve
geçirilen Iz değeri, işlevin parametresine taşınıyordu — işlev bitince değer de
bitiyordu ve aynı değeri ikinci bir çağrıya geçirmek mümkün değildi, çünkü ilk çağrıdan
sonra eski ad geçersiz kalıyordu. Bir işlev değeri ödünç alıyorsa bu kısıt ortadan
kalkıyor: işlev değeri kullanıp bırakıyor, sahiplik hiç el değiştirmiyor, çağıran taraf
aynı değeri istediği kadar tekrar kullanabiliyor.
Bu dersin bütün ölçümleri önceki dersin çekirdeğinden aynı kesiti kullanıyor — Iz, Drop
uygulaması ve gözlem defteri; yardımcı kısım ayrı bir dosyaya konuyor ki sonraki örnekler
onu tekrar tekrar mod ile çekebilsin.
// iz.rs — bir degerin bitisini kaydeden olcum yardimcisi
use std::cell::RefCell;
use std::rc::Rc;
pub type Defter = Rc<RefCell<Vec<String>>>;
pub struct Iz {
pub ad: String,
defter: Defter,
}
impl Iz {
pub 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());
}
}
pub fn yeni_defter() -> Defter {
Rc::new(RefCell::new(Vec::new()))
}
pub fn okunan(defter: &Defter) -> Vec<String> {
defter.borrow().clone()
}
// odunc_islev.rs — bir islevin degeri odunc alip iade etmesi, tasima ile karsitligi
mod iz;
use iz::{Iz, yeni_defter, okunan};
fn oku(referans: &Iz) -> usize {
referans.ad.len()
}
fn main() {
let defter = yeni_defter();
let deger = Iz::yeni("paylasilan", &defter);
let uzunluk1 = oku(°er);
let uzunluk2 = oku(°er);
println!("ilk okuma: {}, ikinci okuma: {}", uzunluk1, uzunluk2);
println!("iki cagridan sonra defter: [{}]", okunan(&defter).join(", "));
}
ilk okuma: 10, ikinci okuma: 10 iki cagridan sonra defter: []
oku işlevi degeri iki kez, art arda ödünç alıyor. Sahiplik ve Taşıma dersindeki yut
işlevi aynı değeri ikinci kez alacak bir çağrıyla derlenemezdi, çünkü ilk çağrı zaten
sahipliği tüketmişti. Burada iki çağrı da geçerli, çünkü oku hiçbir zaman sahiplik
istemiyor — parametre tipi &Iz, Iz değil. İki çağrıdan sonra bile defter boş: deger
mainin sonuna kadar sahibinde yaşamaya devam ediyor.
oku(°er) çağrısındaki & işareti burada zorunlu — işlev bir referans istiyor, kaynak
bunu açıkça vermek zorunda. Kodun başka yerlerinde (a.ad, deger.ad) aynı zorunluluk
görünmüyor: bir alana . ile erişmek ya da bir yöntem çağırmak, gerektiğinde derleyici
tarafından örtük olarak ödünç alınıyor. referans.ad.len() satırı da böyle — referans
zaten bir &Iz, ama .ad erişimi ile .len() çağrısı ayrı bir & yazmayı gerektirmiyor.
Örtük ödünç alma yalnız kaynağın okunabilirliğini kolaylaştırıyor; sınırları (paylaşımlı mı
değiştirilebilir mi, ne kadar sürüyor) aynı kurallara tabi.
Ölçüm: Ödünç Sürerken Defter Boş
Aşağıdaki ölçüm üç kapsam seviyesini iç içe koyuyor: en dışta main, onun içinde degerin
kendi sahiplik kapsamı, onun içinde de sırayla paylaşımlı ve değiştirilebilir ödünç
kapsamları. Yardımcı dosya (iz.rs) zaten çalışma dizininde; bu blok onu tekrar mod iz;
ile çekiyor.
// odunc.rs — odunc alinan deger, odunc surerken hicbir yere bitmiyor
mod iz;
use iz::{Iz, yeni_defter, okunan};
fn main() {
let defter = yeni_defter();
{
let mut deger = Iz::yeni("odunc-1", &defter);
{
let a = °er;
let b = °er;
println!("paylasimli okuma: {} ve {}", a.ad, b.ad);
println!("odunc surerken defter: [{}]", okunan(&defter).join(", "));
}
println!("paylasimli odunc bittikten sonra defter: [{}]", okunan(&defter).join(", "));
{
let m = &mut deger;
m.ad.push_str("-not");
println!("degistirilebilir odunc icinde ad: {}", m.ad);
}
println!("degistirilebilir odunc bittikten sonra defter: [{}]", okunan(&defter).join(", "));
println!("deger hala erisilebilir, kendi adi: {}", deger.ad);
}
println!("dis kapsam bittikten sonra defter: [{}]", okunan(&defter).join(", "));
}
paylasimli okuma: odunc-1 ve odunc-1 odunc surerken defter: [] paylasimli odunc bittikten sonra defter: [] degistirilebilir odunc icinde ad: odunc-1-not degistirilebilir odunc bittikten sonra defter: [] deger hala erisilebilir, kendi adi: odunc-1-not dis kapsam bittikten sonra defter: [odunc-1-not]
Beş ayrı ölçüm noktasının dördü defterin boş olduğunu gösteriyor — paylaşımlı ödünç
sürerken, paylaşımlı ödünç bittikten sonra, değiştirilebilir ödünç sürerken (satır kendi
içinde basıldığı için ayrıca görünmüyor ama bir sonraki satır bunu doğruluyor) ve
değiştirilebilir ödünç bittikten sonra. deger yalnız en dıştaki kapsam (main’in kendi
gövdesi) kapandığında bitiyor ve defter ancak o zaman odunc-1-not yazısını alıyor. a
ve bnin ikisinin de aynı anda var olabilmesi — ikisi de degeri yalnız okuyor, birbirine
karışmıyor — paylaşımlı referansın çoğulluğunu gösteriyor.
Çıktıdaki son satır (odunc-1-not) bunun da ötesinde bir şey kanıtlıyor: degerin kendisi
hiçbir zaman yeniden oluşturulmadı. m üzerinden yapılan değişiklik, degerin kendi
ad alanına yazıldı; deger bittiğinde deftere düşen isim, oluşturulduğu andaki
odunc-1 değil, ödünç sırasında değiştirilmiş hâli. Bu, ödünç almanın salt-okunur bir
görüntü değil, gerçek bir erişim yolu olduğunu gösteriyor — paylaşımlı referans yalnız
okurken, değiştirilebilir referans kaynağın kendisini kalıcı olarak değiştirebiliyor.
Paylaşımlı ve Değiştirilebilir Referans
m.ad.push_str("-not") satırı, değiştirilebilir bir referans üzerinden degerin kendi
alanını gerçekten değiştiriyor — bu satırdan sonra hem m.ad hem de kapsam kapandıktan
sonra okunan deger.ad yeni değeri (odunc-1-not) taşıyor. Paylaşımlı referanslarla
(a, b) bu mümkün değildi: a.ad’i değiştirmeye çalışan bir satır derleyici tarafından
reddedilirdi, çünkü &T yalnız okuma izni veriyor.
SK8. Paylaşımlı referansların aynı anda birden çok olabilmesi, hiçbirinin değeri değiştirememesinden kaynaklanıyor — okuma birbirine karışmaz. Değiştirilebilir bir referansın aynı anda yalnız bir tane olabilmesi ise tam tersi bir nedenden: bir değişiklik başka bir okumanın ya da başka bir değişikliğin ortasında beklenmeyen bir sonuca yol açabilir. Kural referansların sayısına değil, aralarında bir değişikliğin var olup olmadığına bakıyor.
Bu ayrımın somut karşılığı, ödünç almanın kendisinin de iki ayrı yetki taşıması. oku
işlevi yalnız &Iz istediği için değeri değiştiremez — parametre tipinin kendisi bunu
engelliyor, işlevin gövdesine bakmaya bile gerek yok. Bir işlev değeri değiştirmek
istiyorsa parametresini &mut Iz olarak yazmak zorunda ve bu durumda o işlev aynı değere
aynı anda ödünç alınmış başka hiçbir referansla bir arada çağrılamıyor. Bu, bir işlev
imzasının yalnız hangi tipi aldığını değil, o tipe ne yapabileceğini de baştan
belirlediği anlamına geliyor — Rust’ın Tasarım Hedefleri dersinde (M10/K01) sınıfın imzadan
okunması gibi, burada da yetki imzadan okunuyor.
Redd: Aynı Anda Bir Yazan ya da Çok Okuyan
Yukarıdaki kod paylaşımlı ödünç ile değiştirilebilir ödüncü hep ayrı kapsamlarda, art arda
kullandı — hiçbir zaman ikisi birden aynı anda canlı olmadı. Bu tesadüf değil, zorunlu.
a ve b hâlâ kapsamdayken aynı satırlara let m = &mut deger; eklenseydi, derleyici
reddederdi: aynı değere aynı anda hem paylaşımlı hem değiştirilebilir bir referans birlikte
var olamaz. Aynı kural iki değiştirilebilir referans için de geçerli — let m1 = &mut deger; ile let m2 = &mut deger; aynı kapsamda birlikte yaşayamaz, çünkü ikisi de aynı
alana yazma izni taşıyor ve hangi yazmanın önce, hangisinin sonra geçerli olacağı kaynaktan
okunamaz hâle gelirdi. İki paylaşımlı referans (a ve b) için bu sorun yok, çünkü
hiçbiri yazmıyor — okumanın sırası sonucu değiştirmiyor.
Bu ret bir çalışma zamanı denetimi değil; kaynak, iki referansın kapsamları çakıştığı an hiç derlenmiyor. Kabul edilen yazım (yukarıdaki kod) bu çakışmayı zaten baştan dışlıyor: paylaşımlı kapsam kendi süslü parantezleriyle kapanıyor, değiştirilebilir kapsam ondan sonra ayrı süslü parantezlerle açılıyor — iki kapsam hiçbir noktada üst üste binmiyor.
Aynı kısıt işlev çağrılarında da geçerli. oku(°er) iki kez art arda çağrılabildi,
çünkü hiçbir çağrının referansı bir sonraki çağrı başlarken hâlâ canlı değildi — her çağrı
kendi ödünç kapsamını açıp kendi içinde kapatıyor. Aynı değeri hem okuya (&Iz) hem de
aynı anda değiştiren bir işleve (&mut Iz) birlikte geçirmeye çalışan bir yazım, iki
kapsam çakıştığı için derlenmezdi. Kural, tek bir işlevin gövdesiyle sınırlı değil; bir
kaynaktaki bütün referans kapsamları aynı denetime tabi.
Sınırlayıcı Ölçüm: Ödünç Ömrü Uzatmaz
Üç kapsam seviyesi (main, degerin kendi kapsamı, ödünç kapsamları) birbirinin içine
yerleşiyor ve bu yerleşim tek yönlü: bir ödünç kapsamı her zaman kaynağın kapsamının
içinde kalıyor, hiçbir zaman dışına taşmıyor. a ve b, degerin kapsamı kapanmadan
önce kendi kapsamlarını kapatıyor; m de öyle. Hiçbir referans, işaret ettiği değerden
daha uzun yaşamıyor.
SK9. Bir referansın geçerli olduğu aralık, kaynağının kapsamını hiçbir zaman aşamaz —
bu, ödünç almanın ömrü uzatmadığının somut karşılığı. degerin kendisi mainin
gövdesi bitene kadar yaşıyor; içindeki hiçbir referans bundan daha uzun bir aralık talep
edemiyor, çünkü edebilseydi, o referans kaynağı zaten bitmiş bir değere işaret ederdi.
Bu kısıt burada iç içe kapsamlarla, doğal bir yazımla sağlandı: her ödünç kendi süslü
parantezleri içinde açılıp kapandı ve degerin kendi kapsamı bunların hepsini kapsadı.
Bir referansı bu iç içe geçmenin dışına, örneğin degerin kapsamından sonra hâlâ geçerli
olacak bir yere taşımaya çalışan bir yazım derleyici tarafından reddedilir — bu aralığın
kendi adı ve kuralları sıradaki bölümde ele alınacak.
Bu kısıtın neden zorunlu olduğu, tersini düşününce netleşiyor. m adlı değiştirilebilir
referans degerin kapsamından daha uzun yaşayabilseydi, deger bitip deftere yazıldıktan
sonra bile m üzerinden bir okuma ya da yazma denenebilirdi — ama artık işaret
edilecek geçerli bir Iz kalmamış olurdu. Derleyici bu senaryoyu, referansın geçerli
olduğu aralığı kaynağın kapsamıyla sınırlayarak baştan engelliyor. oku işlevindeki
&Iz parametresi de aynı kısıta tabi: işlev kendi gövdesi içinde bu referansı ne kadar
kullanırsa kullansın, referansın aralığı oku çağrısının süresini aşamıyor — çağıran
taraftaki deger hâlâ sahibinde, iki çağrının arasında bile referans yeniden alınıyor.
Bu derste ölçülen üç şey birbirine bağlı. Ödünç almak sahiplik vermediği için ödünç alınan
değer ödünç sürerken hiçbir yere bitmiyor; referansın kendisi kaynaktan ayrı, kendi
kapsamına sahip küçük bir değer olduğu için kaynağın bitişini tetiklemiyor; ve bir
referansın kapsamı kaynağın kapsamını hiçbir zaman aşamadığı için “hâlâ yaşayan bir
değere işaret etme” garantisi baştan kuruluyor. Paylaşımlı ve değiştirilebilir ayrımı bu
üç ölçümün üzerine binen dördüncü bir kısıt: aynı anda birden çok okuma güvenli, ama bir
değişiklik başka hiçbir erişimle aynı anda olamıyor. Sahiplik ve Taşıma dersindeki “tek
sahip” ilkesi burada “tek yazan, çok okuyan” biçiminde genişliyor — sahiplik hâlâ tek bir
adda, ama o adın izniyle birden çok geçici erişim aynı anda var olabiliyor. Bu genişleme,
sahibin kendisinin hiçbir zaman devre dışı kalmadığı anlamına da geliyor: deger, kaç
referans dağıtırsa dağıtsın, kendi kapsamı içinde tek gerçek sahip olarak kalmaya devam
ediyor.
Özet
- Bir referans, işaret ettiği değerin sahibi değildir; kendi kapsamı kapandığında yalnız kendisi biter, kaynağı etkilemez.
- Ödünç sürerken defterde hiçbir kayıt oluşmuyor; ödünç alınan değer, ödünç kapsamı kapandıktan sonra da sahibinde erişilebilir kalıyor. Ödünç verilen değerin ömrü kapsam sonunda bitiyor, ödünç bittiğinde değil.
- Paylaşımlı referans (
&T) aynı anda birden çok olabilir ve yalnız okur; değiştirilebilir referans (&mut T) aynı anda yalnız bir tane olabilir ve değiştirebilir. - Aynı değere aynı anda hem paylaşımlı hem değiştirilebilir, ya da iki değiştirilebilir referans birlikte var olamaz; bu kural derleme zamanında sınanır.
- Bir referansın kapsamı, kaynağının kapsamının her zaman içinde kalmak zorundadır — ödünç almak ömrü uzatmaz.
- Aynı değer, ödünç alan bir işleve arka arkaya birden çok kez geçirilebilir; bu, sahiplik
isteyen bir işlevin (
yutgibi) yalnız bir kez çağrılabilmesiyle doğrudan karşıt. Bir alana.ile erişmek ya da bir yöntem çağırmak da gerektiğinde örtük olarak ödünç alır.
Sonraki Adım
Bu derste bir değerin tamamına ödünç verilen referanslar ölçüldü — &Iz ya da
&mut Iz, hep bütün değere. Sıradaki ders aynı sahiplik-vermeden-erişim ilkesini bir
değerin yalnız bir parçasına uyguluyor: bir dizginin ya da bir büyüyen dizinin
belirli bir aralığını, kaynağı hiç bitirmeden nasıl görüntüleyebiliriz? Ödünç almanın
burada ölçülen bütün kuralları (kaynak bitmiyor, ömür uzamıyor, paylaşımlı/değiştirilebilir
ayrımı) aynen geçerli kalacak; değişen yalnız görüntülenen kısmın büyüklüğü.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.