İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat