Ders 04 / 14
Dilimler
Bir dilim, sahiplik almadan bir dizginin ya da bir büyüyen dizinin belirli bir aralığını görüntüler: dilim kullanılır, dilim biter, ama kaynak hiç bitmez. Dilimin uzunluğu kaynaktan türer ve boş bir dilim geçerlidir; dilim yaşarken kaynak değiştirilemez, dilim bittikten sonra değişiklik sorunsuz geçer.
Yayın Güncelleme
Bu derste
- Bir dilimin sahiplik almadan bir dizgiyi ya da büyüyen diziyi görüntülediğini ve kaynağın dilim sürerken hiç bitmediğini koşturarak gösterir
- Dilimin uzunluğunun kaynaktan türediğini ve boş bir dilimin geçerli olduğunu doğrular
- Dilim yaşarken kaynağın değiştirilemediğini, dilim bittikten sonra değişikliğin sorunsuz geçtiğini kanıtlar
İçindekiler
Önceki ders bir değerin tamamına ödünç verilen referansları ölçtü. Bu ders aynı sahiplik-vermeden-erişim ilkesini bir değerin yalnız bir parçasına uyguluyor: dilim (slice), bir dizginin ya da bir büyüyen dizinin ardışık bir aralığını, sahiplik almadan görüntüleyen bir referans türü. Ölçülen yine bir değerin bitişidir, koşan bir birimin durması değil — Eşzamanlılık kursunun (M09/K03) goroutine sorusu bu kursun ekseninin dışında kalıyor.
Dilim kavramının kendisi Eşzamanlılık kursunda (M09) zaten kurulmuştu — bir dizinin ardışık
bir alt aralığını temsil eden, kendi uzunluğunu taşıyan bir görünüm. Bu derste ölçülen,
Rust’ın dilimi bir aralık söz dizimiyle ([baslangic..bitis]) kurması ve önceki üç dersin
kurallarının (sahiplik alınmıyor, kaynak bitmiyor, ödünç ömrü kaynağı aşamıyor) buraya
aynen uygulanması.
Önceki üç ders bir referansın hep tek bir değere işaret ettiğini varsaydı: &Iz,
&mut Iz. Dilim bu varsayımı gevşetiyor — bir referans artık birden çok ardışık değere
birden işaret edebiliyor, ama bunu yaparken sahiplik, kapsam ve paylaşımlı/değiştirilebilir
kurallarının hiçbirini değiştirmiyor. Bu derste ölçülen, kuralların aynı kaldığı, yalnız
neyin ödünç alındığının değiştiği.
Bir Değerin Bir Parçasını Görüntülemek
Bir dilim, &T’nin bir çeşidi: &[Iz] bir büyüyen diziden alınan bir görünüm, &str bir
dizgiden alınan bir görünüm. İkisi de kaynağın kendi verisine işaret ediyor — veriyi
kopyalamıyor — ve kendi uzunluğunu ayrıca taşıyor. Bir dilim aldığında kaynağın sahibi
değişmiyor, yalnız o aralığa geçici bir erişim kuruluyor.
Aralık söz dizimi [baslangic..bitis] biçiminde: baslangic dahil, bitis hariç. En
basit hâliyle, sahiplenme sorusu hiç devreye girmeden, aralığın kendisi şöyle okunuyor.
// temel_dilim.rs — dilim sozdizimi: baslangic..bitis araligi
fn main() {
let sayilar = [10, 20, 30, 40, 50];
let orta = &sayilar[1..4];
println!("kaynak: {:?}", sayilar);
println!("dilim: {:?}", orta);
println!("dilim uzunlugu: {}", orta.len());
}
kaynak: [10, 20, 30, 40, 50] dilim: [20, 30, 40] dilim uzunlugu: 3
&sayilar[1..4] indeks 1’den başlayıp indeks 4’e (dahil değil) kadar olan üç elemanı
görüntülüyor — 20, 30, 40. sayilar’ın kendisi burada i32 gibi kopyalanabilir bir
tip taşıdığı için sahiplik sorusu görünmüyor; asıl soru, kaynağın kendisi kopyalanamayan,
Drop uygulayan bir tip taşıdığında ortaya çıkıyor — tam da bu dersin geri kalanının
ölçtüğü durum. [1..4] yazımının kendisi sayiların türünü değiştirmiyor; sayilar hâlâ
sabit uzunluklu bir dizi, orta ise ayrı bir tip — belirli bir uzunluğu olmayan, kaynaktan
türeyen bir dilim.
Ölçüm: Kaynak Hiç Bitmez
Aşağıdaki ölçüm üç Iz değeri tutan bir büyüyen diziden ilk iki elemanı görüntüleyen bir
dilim alıyor, dilimi kullanıyor, dilimin kapsamını kapatıyor ve ardından kaynağı
değiştiriyor.
// 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()
}
// dilim_omru.rs — dilim sahiplik almadan gorunturur, kaynagi bitirmez ama kilitler
mod iz;
use iz::{Iz, yeni_defter, okunan};
fn main() {
let defter = yeni_defter();
let mut liste = vec![
Iz::yeni("bir", &defter),
Iz::yeni("iki", &defter),
Iz::yeni("uc", &defter),
];
let once = okunan(&defter).len();
{
let goruntu = &liste[0..2];
println!("dilim uzunlugu: {}, ilk ad: {}", goruntu.len(), goruntu[0].ad);
println!("dilim yasarken defter: [{}]", okunan(&defter)[once..].join(", "));
}
println!("dilim bittikten sonra defter: [{}]", okunan(&defter)[once..].join(", "));
liste.push(Iz::yeni("dort", &defter));
println!("dilim bittigi icin liste degistirildi, uzunluk: {}", liste.len());
}
dilim uzunlugu: 2, ilk ad: bir dilim yasarken defter: [] dilim bittikten sonra defter: [] dilim bittigi icin liste degistirildi, uzunluk: 4
goruntu yalnız listenin ilk iki elemanını görüntülüyor, ama üç elemanın da sahibi hâlâ
liste. Dilim yaşarken de, dilimin kapsamı kapandıktan sonra da defter boş — bir, iki
ve uc değerlerinin hiçbiri bitmiyor, çünkü dilim onlara sahip olmadı, yalnız onlara
baktı. listenin kendisi bu ölçümde hiç bitmiyor (kod dosyanın sonunda kapanmadığı için);
görülen tek bitiş, dilimin kendi kapsamının kapanması — ki bu da deftere hiçbir şey
yazmıyor, çünkü &[Iz] tipinde bir dilim Drop uygulamıyor.
Burada dikkat edilmesi gereken bir ayrıntı var: goruntu yalnız ilk iki elemanı (bir,
iki) görüntülüyor, üçüncü eleman (uc) dilimin dışında kalıyor. Buna rağmen ölçüm
listenin tamamının hâlâ sahibinde durduğunu gösteriyor — dilimin kapsamadığı uc da,
kapsadığı bir ve iki de aynı şekilde etkilenmiyor. Dilim, kaynağın yalnız bir alt
kümesini okumak için bir pencere açıyor; kaynağın geri kalanının sahipliğine ya da
ömrüne hiçbir şekilde karışmıyor.
goruntu[0].ad erişimi de bunu somutlaştırıyor: goruntu[0] dilimin ilk elemanına
işaret eden bir &Iz üretiyor (sahiplik değil, yeni bir ödünç) ve .ad bu ödünç
üzerinden okunuyor. Aynı satırda üç ayrı ödünç iç içe geçmiş durumda — listeden
goruntuya, goruntudan goruntu[0]’a, goruntu[0]’dan .ad’a — ama hiçbiri
sahiplik istemiyor, hepsi aynı temel Iz değerlerine kadar geri gidiyor.
SK10. Bir dilim, aynı &T referansı gibi, işaret ettiği elemanların sahibi değil.
Kapsamı kapandığında yalnız kendisi (dilimin kendi başlangıç, uzunluk çifti) yok olur;
elemanların hiçbiri bundan etkilenmez. Önceki dersteki “referans kaynağı bitirmez” kuralı
burada aynen geçerli, yalnız görünürlük tek bir değer yerine ardışık bir aralık.
Sınırlayıcı Ölçüm: Dilim Kaynağı Kilitler
liste.push(...) satırı goruntunun kapsamı kapandıktan sonra çalışıyor. Bu sıralama
zorunlu: goruntu hâlâ kapsamdayken aynı satır eklenseydi, derleyici reddederdi. goruntu
listenin paylaşımlı bir ödüncü ve push çağrısı listeyi değiştirmek için
değiştirilebilir bir ödünç istiyor — Ödünç Alma dersinde kurulan “aynı anda bir yazan ya da
çok okuyan” kuralı burada da geçerli, yalnız ödünç aldığı şey artık bütün değer değil,
onun bir parçası.
SK11. Bir dilim kaynağının tamamını kilitliyor, yalnız görüntülediği aralığı değil.
goruntu yalnız ilk iki elemanı görüntülese de, listenin herhangi bir yerine (örneğin
sona) eleman eklemeyi de engelliyor — çünkü bir ekleme büyüyen dizinin kendi iç düzenini
yeniden ayarlayabilir ve dilimin işaret ettiği konumları geçersiz kılabilir. Kısıt dilimin
kapsadığı elemanlarla sınırlı değil, kaynağın bütününe yayılıyor.
Bu, “dilim kaynağın yalnız bir parçasını görüntüler” cümlesiyle ilk bakışta çelişiyor gibi görünebilir — görüntülenen parça küçük, ama kilitlenen yüzey büyük. Çelişki yok: dilim yalnız kendi aralığındaki elemanları okumaya izin veriyor, ama kaynağın kendi düzeninde (kaç eleman tuttuğu, elemanların nerede durduğu) herhangi bir değişiklik, dilimin dayandığı varsayımı bozabileceği için, o değişiklik dilim yaşarken hiç başlatılamıyor. Görüntülenen kısım dar, korunan varsayım geniş.
Kabul edilen yazım bu sırayı zaten koruyor: dilim önce kendi dar kapsamında kullanılıp
bırakılıyor, push ancak ondan sonra, listeye tek başına erişimin olduğu bir noktada
çağrılıyor. Aynı işi ters sırayla yazmaya çalışmak — önce değiştirip sonra eski dilimi
okumaya devam etmek — derleyici tarafından hiç derlenmez.
Bu kısıtın neyi önlediği somut: push bir büyüyen dizinin arka planındaki depolama
alanını gerektiğinde yeniden ayırabilir — dizi büyürken bütün elemanlar yeni bir konuma
taşınabilir. goruntu eski konumlara işaret ediyor olsaydı ve push bu taşımayı
tetikleseydi, goruntu artık geçerli olmayan bir bellek bölgesine işaret ediyor olurdu.
Derleyici bu senaryoyu, dilim yaşarken kaynağı değiştiren hiçbir çağrıya izin vermeyerek
baştan kapatıyor — pushun gerçekten yeniden ayırma yapıp yapmayacağını bilmesi bile
gerekmiyor, kural bütün değiştirici çağrılar için aynı.
Aynı kısıt tek bir eleman okumak için de geçerli: liste[2] gibi bir doğrudan indeksleme
goruntu kapsamdayken sorun çıkarmaz, çünkü o da yalnız okuyor — paylaşımlı iki ödünç bir
arada durabiliyor. Sorun yalnız bir değişiklik aynı anda talep edildiğinde başlıyor.
Dilim, Bir İşleve Kaynağı Taşımadan Geçirilir
Ödünç Alma dersindeki oku işlevi tek bir değeri, sahiplik istemeden alıyordu. Aynı
düzenek bir dilim için de geçerli: bir işlev parametresini &[Iz] olarak yazarsa, hem
kaynağın tamamını hem de yalnız bir aralığını, sahiplik istemeden kabul edebiliyor.
// dilim_parametre.rs — bir islev dilim alarak kaynagi tasimadan isler
mod iz;
use iz::{Iz, okunan, yeni_defter};
fn adlari_birlestir(dilim: &[Iz]) -> String {
let mut sonuc = String::new();
for (i, iz) in dilim.iter().enumerate() {
if i > 0 {
sonuc.push('+');
}
sonuc.push_str(&iz.ad);
}
sonuc
}
fn main() {
let defter = yeni_defter();
let liste = vec![
Iz::yeni("a", &defter),
Iz::yeni("b", &defter),
Iz::yeni("c", &defter),
];
println!("tum liste: {}", adlari_birlestir(&liste));
println!("ilk iki: {}", adlari_birlestir(&liste[..2]));
println!("cagrilardan sonra defter: [{}]", okunan(&defter).join(", "));
}
tum liste: a+b+c ilk iki: a+b cagrilardan sonra defter: []
adlari_birlestir işlevi hem &liste (tüm kaynak) hem &liste[..2] (bir alt aralık) ile
çağrılabiliyor — parametre tipi &[Iz] olduğu için ikisi de aynı işleve uyuyor. &liste
burada bir dilime dönüştürülüyor: büyüyen dizinin tamamına bir referans, otomatik
olarak baştan sona bir dilime karşılık geliyor. İki çağrıdan sonra da defter boş —
adlari_birlestir hiçbir zaman listenin elemanlarına sahip olmadı, yalnız onları okudu.
Bu, bir işlevin sahiplik istemeden hem bütünü hem parçayı aynı imzayla kabul
edebilmesinin somut örneği.
Değiştirilebilir Dilim, Kapsadığı Elemanları Değiştirir
Buraya kadar görülen dilimler hep paylaşımlıydı (&[Iz]). Bir dilim de &mut [Iz]
olarak alınabilir ve bu durumda kapsadığı elemanlar üzerinde yazma izni de taşır —
Ödünç Alma dersindeki paylaşımlı/değiştirilebilir ayrımı burada da aynen geçerli.
// dilim_degistirilebilir.rs — degistirilebilir bir dilim, kapsadigi elemanlari degistirebilir
mod iz;
use iz::{Iz, yeni_defter, okunan};
fn main() {
let defter = yeni_defter();
let mut liste = vec![
Iz::yeni("x", &defter),
Iz::yeni("y", &defter),
Iz::yeni("z", &defter),
];
{
let goruntu = &mut liste[0..2];
for iz in goruntu.iter_mut() {
iz.ad.push_str("-yeni");
}
}
println!("degisiklikten sonra adlar: {}, {}, {}", liste[0].ad, liste[1].ad, liste[2].ad);
println!("hicbir sey bitmedi, defter: [{}]", okunan(&defter).join(", "));
}
degisiklikten sonra adlar: x-yeni, y-yeni, z hicbir sey bitmedi, defter: []
goruntu burada listenin ilk iki elemanını değiştirilebilir olarak görüntülüyor;
döngü içindeki iz.ad.push_str(...) bu iki elemanın ad alanını gerçekten değiştiriyor.
Üçüncü eleman (z) dilimin dışında kaldığı için değişmiyor — çıktı bunu doğruluyor.
Kapsam kapandığında hiçbir değer bitmiyor, çünkü değiştirilebilir bir dilim de, tıpkı
paylaşımlı bir dilim gibi, sahiplik almıyor; yalnız o aralığa yazma izni taşıyor.
Bu örnekte de aynı anda yalnız bir değiştirilebilir dilim var; goruntu kapsamdayken
listeye başka hiçbir erişim (ne paylaşımlı ne değiştirilebilir) açılamazdı. goruntunun
kapsamı kapandıktan sonra liste[0].ad gibi doğrudan indekslemeler serbestçe kullanılabiliyor
— tıpkı Sınırlayıcı Ölçüm bölümünde görülen push örneğinde olduğu gibi, kısıt yalnız
dilimin kendi kapsamı boyunca geçerli.
Uzunluk Kaynaktan Türer, Boş Dilim Geçerlidir
Dilimin uzunluğu kendi başına bir bilgi taşımıyor, kaynaktan hesaplanıyor. Bir dizgi üzerinde de aynı aralık söz dizimi geçerli.
// dizgi_dilimi.rs — dilimin uzunlugu kaynaktan turer, bos dilim de gecerlidir
fn main() {
let metin = String::from("olcum");
let tam = &metin[..];
let bos = &metin[0..0];
println!("kaynak uzunluk: {}", metin.len());
println!("tam dilim uzunluk: {}", tam.len());
println!("bos dilim uzunluk: {}", bos.len());
println!("bos dilim gecerli mi: {}", bos.is_empty());
}
kaynak uzunluk: 5 tam dilim uzunluk: 5 bos dilim uzunluk: 0 bos dilim gecerli mi: true
&metin[..] bütün aralığı, &metin[0..0] ise hiçbir karakter kapsamayan boş bir aralığı
görüntülüyor. tam.len() kaynağınkiyle aynı çıkıyor çünkü aralık baştan sona; bos.len()
sıfır, ama bos geçersiz bir değer değil — kendi başına geçerli bir dilim, yalnız hiçbir
şey kapsamıyor. is_empty() çağrısı bunu doğruluyor: boş bir dilim üzerinde çağrılan
sıradan bir yöntem, hiçbir özel durumla karşılaşmadan çalışıyor.
metin.len()in burada verdiği sayı dizginin bayt uzunluğu — bu, dizginin kendisinin bir
bayt dizisi olduğu ve karakter sayısından ayrıştığı Koleksiyonlar dersinde (M10/K01) zaten
ele alınmıştı. Bu dersin payı ayrı: aralığın kendisi nasıl kuruluyor ve ne zaman geçerli
sayılıyor. metinin içeriği yalnız ASCII karakterler taşıdığı için burada bayt uzunluğu
ile karakter sayısı çakışıyor; aradaki farkın kendisi bu dersin konusu değil.
SK12. Bir dilimin başlangıç == bitiş olması bir hata durumu değil, sıfır uzunluklu
geçerli bir dilim üretiyor. Bu, bir işlevin örneğin “eşleşen kısım” olarak boş bir aralık
döndürebilmesini, çağıranın bunu ayrı bir özel durum olarak ele almak zorunda kalmadan
işleyebilmesini sağlıyor. Boş bir dilim üzerinde len(), is_empty(), hatta bir döngüyle
gezinme (sıfır kez dönerek) sorunsuz çalışıyor — kaynağın kendisi boş olmasa bile.
Dört ders boyunca ödünç almanın taşıdığı üç soru burada da aynı yanıtları veriyor: kaynak
bitiyor mu (hayır), ömür uzuyor mu (hayır, dilimin kapsamı kaynağınkini hiçbir zaman
aşamıyor) ve paylaşımlı/değiştirilebilir ayrımı geçerli mi (evet — bir &mut [Iz] dilimi
de alınabilirdi, o zaman kısıtlar Ödünç Alma dersindeki değiştirilebilir referansla
birebir aynı olurdu). Dilim, bu üç soruya yeni bir yanıt getirmiyor; sahip olduğu tek yeni
şey, görüntülediği aralığın boyutu — tek bir değer yerine ardışık bir küme.
Özet
- Bir dilim (
&[T],&str), kaynağın ardışık bir aralığını sahiplik almadan görüntüler; kaynağın kendisi dilim yüzünden hiçbir zaman bitmiyor — kaynağın ömrü kendi kapsam sonunda bitiyor. - Dilim yalnız işaret ettiği aralığı değil, kaynağın bütününü kilitliyor — dilim yaşarken kaynak değiştirilemiyor, dilim bittikten sonra değişiklik sorunsuz geçiyor.
- Dilimin uzunluğu kaynaktan türüyor; bir dilim kendi başına bağımsız bir uzunluk bilgisi taşımıyor.
başlangıç == bitişolan bir dilim geçerlidir ve sıfır uzunluk taşır; boş bir dilim özel bir durum değildir.- Dilim de bir referans olduğu için önceki iki dersin bütün kuralları (paylaşımlı/ değiştirilebilir ayrımı, kapsamın kaynağı aşamaması) burada da geçerli.
- Bir işlev parametresini
&[T]ya da&mut [T]yazmak, o işlevin hem kaynağın tamamını hem yalnız bir aralığını, sahiplik almadan aynı imzayla kabul etmesini sağlar.
Sonraki Adım
Bu dört ders boyunca ödünç almanın kuralları tek tek ölçüldü — bütün değer, bir parça, paylaşımlı ya da değiştirilebilir; her seferinde kaynak bitmedi, ömür kaynağı aşmadı ve aynı anda bir yazan ya da çok okuyan kısıtı korundu. Bu kurallar dört ayrı ders boyunca hiç çelişmeden aynı kaldı, yalnız uygulandıkları yüzey değişti. Sıradaki ders bu kuralları uygulayan mekanizmanın kendisine dönüyor: ödünç denetleyicisi, derleme zamanında tam olarak neyi sınıyor ve bu denetim aynı işi yapan farklı yazımlar arasında nasıl bir seçim bırakıyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.