Yığındaki Veriyi İşaret Etmek İçin Box<T> Kullanmak
En sade akıllı işaretçi kutudur; türü Box<T> diye yazılır. Kutular
(boxes), veriyi yığın yerine öbekte (heap) saklamanızı sağlar. Yığında kalan
şey, öbekteki veriyi gösteren işaretçinin kendisidir. Yığın ile öbek arasındaki
farkı tazelemek için 4. bölüme bakabilirsiniz.
Kutuların, veriyi yığın yerine öbekte tutmak dışında belirgin bir ek maliyeti yoktur. Ama ek yetenekleri de çok fazla değildir. Genellikle şu durumlarda kullanırsınız:
- Boyutu derleme zamanında bilinemeyen bir türünüz vardır ve bu türün değerini, tam boyut gerektiren bir bağlamda kullanmak istersiniz.
- Büyük miktarda veriniz vardır; sahipliği taşımak istersiniz ama bunu yaparken verinin kopyalanmamasını istersiniz.
- Belirli bir somut türü değil, belli bir trait’i uygulayan herhangi bir türü sahiplenmek istersiniz.
İlk durumu “Kutularla Özyineli Türleri Mümkün Kılmak” bölümünde göstereceğiz. İkinci durumda, büyük verinin sahipliğini taşımak uzun sürebilir; çünkü veri yığın üzerinde kopyalanır. Bu performansı iyileştirmek için veriyi kutu içinde öbekte tutabiliriz. Böylece yığında yalnızca küçük işaretçi verisi kopyalanır, asıl veri ise öbekte aynı yerde kalır. Üçüncü durumaysa trait nesnesi (trait object) denir; 18. bölümdeki [“Trait Nesneleriyle Ortak Davranışı Soyutlamak”][trait-objects] başlığı tamamen buna ayrılmıştır. Burada öğrenecekleriniz orada da işinize yarayacak!
Veriyi Öbekte Saklamak
Box<T> için öbekte saklama kullanım senaryosuna geçmeden önce sözdizimine ve
Box<T> içindeki değerlerle nasıl etkileşim kurulduğuna bakalım.
Liste 15-1, bir kutunun i32 değerini öbekte saklamak için nasıl
kullanıldığını gösteriyor.
fn main() {
let k = Box::new(5);
println!("k = {k}");
}
i32 degerini kutu kullanarak obekte saklamakk değişkenine, öbekte ayrılmış 5 değerini işaret eden bir Box atıyoruz.
Program k = 5 yazdırır; bu durumda kutudaki veriye, veri yığındaymış gibi
erişebiliriz. Her sahip olunan değer gibi, kutu kapsam dışına çıktığında
serbest bırakılır. Bu serbest bırakma hem yığındaki kutunun kendisi hem de
öbekte işaret ettiği veri için gerçekleşir.
Tek bir değeri öbeğe koymak çok heyecan verici değildir; bu yüzden kutuları çoğu
zaman tek başlarına bu şekilde kullanmazsınız. Tek bir i32 gibi varsayılan
olarak yığında duran değerler çoğu durumda zaten daha uygundur. Şimdi kutuların
olmasaydı tanımlayamayacağımız türleri nasıl mümkün kıldığına bakalım.
Kutularla Özyineli Türleri Mümkün Kılmak
Özyineli tür (recursive type) değerleri, kendi içinde aynı türden başka bir
değeri barındırabilir. Bu türler sorun yaratır çünkü Rust’ın bir türün ne kadar
yer kapladığını derleme zamanında bilmesi gerekir. Oysa özyineli türlerde iç içe
geçme teorik olarak sonsuza dek sürebilir; dolayısıyla Rust değer için gereken
alanı bilemez. Box<T> bilinen bir boyuta sahip olduğu için, özyineli tür
tanımına kutu ekleyerek bu sorunu çözeriz.
Örnek olarak cons list türüne bakalım. Bu veri türü fonksiyonel programlama dillerinde yaygındır. Tanımlayacağımız cons list, özyineleme dışında oldukça basittir; bu yüzden burada gördüğümüz fikirler, ileride daha karmaşık özyineli durumlarda da işinize yarar.
Cons Listeyi Anlamak
Cons liste, Lisp programlama dili ve türevlerinden gelen, iç içe çiftlerden
oluşan ve bağlı listenin Lisp dünyasındaki karşılığı olan bir veri yapısıdır.
Adını, Lisp’teki cons fonksiyonundan alır; bu ad aslında construct
function’ın kısaltmasıdır. İki argümandan yeni bir çift oluşturur. Bir değerle
başka bir çiftten oluşan yapılar üzerinde cons çağrıları yaparak özyineli
çiftlerden oluşan cons listeler kurabiliriz.
Örneğin 1, 2, 3 listesini içeren bir cons listenin sözde kod gösterimi şöyledir:
(1, (2, (3, Nil)))
Cons listedeki her öğe iki parçadan oluşur: mevcut öğenin değeri ve bir sonraki
öğenin değeri. Listenin son öğesi, sonraki öğe olmadan yalnızca Nil denilen
değeri taşır. Cons liste, cons fonksiyonunun özyineli biçimde çağrılmasıyla
üretilir. Özyinelemenin taban durumu için geleneksel ad Nildir. Bunun, 6.
bölümde gördüğümüz geçersiz ya da eksik değer anlamındaki “null” veya “nil”
fikriyle aynı şey olmadığını unutmayın.
Cons liste Rust’ta pek yaygın değildir. Rust’ta çoğu durumda öğe listesi
gerekiyorsa Vec<T> daha iyi seçimdir. Ama daha karmaşık özyineli veri türleri
çeşitli durumlarda kullanışlıdır; bu bölümde cons listeyle başlamak, dikkat
dağıtıcı ayrıntılara boğulmadan kutuların özyineli veri türünü nasıl mümkün
kıldığını görmemizi sağlar.
Liste 15-2, cons listeyi temsil edecek bir enum tanımı içeriyor. Bu kod henüz
derlenmeyecek; çünkü birazdan göstereceğimiz gibi Liste türünün bilinen bir
boyutu yoktur.
enum Liste {
Dugum(i32, Liste),
Bos,
}
fn main() {}
i32 degerlerinden olusan bir kons liste veri yapisini temsil etmek icin enum tanimlamaya ilk denemeNot: Bu örnekte yalnızca
i32değerleri tutan bir cons liste gerçekliyoruz. 10. bölümde gördüğümüz gibi bunu jeneriklerle de yapabilir ve her türden değeri saklayabilen bir cons liste tanımlayabilirdik.
1, 2, 3 listesini Liste türüyle saklamak, Liste 15-3’teki gibi görünür.
enum Liste {
Dugum(i32, Liste),
Bos,
}
// --snip--
use crate::Liste::{Bos, Dugum};
fn main() {
let liste = Dugum(1, Dugum(2, Dugum(3, Bos)));
}
1, 2, 3 listesini saklamak icin Liste enumunu kullanmakİlk Dugum değeri 1 ve başka bir Liste değeri taşır. Bu Liste, 2 ve
başka bir Liste taşıyan başka bir Dugumdur. Sonraki Liste de 3 ve
sonunda listenin bittiğini bildiren özyineli olmayan Bos varyantını tutar.
Liste 15-3’teki kodu derlemeye çalışırsak, Liste 15-4’teki hatayı alırız.
$ cargo run
Compiling kons-liste v0.1.0 (file:///projects/kons-liste)
error[E0072]: recursive type `Liste` has infinite size
--> src/main.rs:1:1
|
1 | enum Liste {
| ^^^^^^^^^
2 | Dugum(i32, Liste),
| ---- recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
2 | Dugum(i32, Box<Liste>),
| ++++ +
error[E0391]: cycle detected when computing when `Liste` needs drop
--> src/main.rs:1:1
|
1 | enum Liste {
| ^^^^^^^^^
|
= note: ...which immediately requires computing when `Liste` needs drop again
= note: cycle used when computing whether `Liste` needs drop
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
Some errors have detailed explanations: E0072, E0391.
For more information about an error, try `rustc --explain E0072`.
error: could not compile `kons-liste` (bin "kons-liste") due to 2 previous errors
Hata bu türün “sonsuz boyuta” sahip olduğunu söyler. Bunun sebebi, Listeyi
kendi türünden başka bir değeri doğrudan içinde taşıyan özyineli bir varyantla
tanımlamış olmamızdır. Sonuç olarak Rust, bir Liste değerini saklamak için ne
kadar yer gerektiğini hesaplayamaz. Şimdi bunun neden böyle olduğunu açalım.
Önce Rust’ın özyineli olmayan bir türün boyutunu nasıl hesapladığını görelim.
Özyineli Olmayan Türün Boyutunu Hesaplamak
- bölümde enum tanımlarını anlatırken Liste 6-2’de tanımladığımız
Messageenumunu hatırlayın:
enum Mesaj {
Cik,
Tasi { x: i32, y: i32 },
Yaz(String),
RenkDegistir(i32, i32, i32),
}
fn main() {}
Rust, Message için ne kadar alan ayıracağını belirlemek adına her varyanta
bakar ve en fazla alan gerektiren varyantı bulur. Message::Quit hiç alana
ihtiyaç duymaz, Message::Move iki i32 saklayacak kadar alana ihtiyaç duyar,
vb. Aynı anda yalnızca tek bir varyant kullanılacağı için, Message değerinin
gerektirdiği en büyük alan, en büyük varyantın kapladığı alandır.
Bunu, Liste 15-2’deki Liste enumu gibi özyineli bir türde Rust ne yapmaya
çalıştığıyla karşılaştırın. Derleyici önce Dugum varyantına bakar; içinde bir
i32 ve bir Liste vardır. Demek ki Dugum, i32 boyutu artı Liste
boyutuna ihtiyaç duyar. Listenin boyutunu bulmak için yine varyantlara bakar
ve yine Dugume gelir. Bu süreç sonsuza kadar sürer; Şekil 15-1 bunu gösterir.
Şekil 15-1: Sonsuz Dugum varyantlarından oluşan sonsuz bir Liste
Boyutu Bilinen Özyineli Tür Elde Etmek
Rust özyineli tanımlanmış türler için ne kadar yer ayıracağını hesaplayamadığı için, derleyici şu yardımcı öneriyi içeren bir hata verir:
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
2 | Dugum(i32, Box<Liste>),
| ++++ +
Buradaki indirection, değeri doğrudan saklamak yerine ona işaret eden bir işaretçi saklamak demektir.
Box<T> bir işaretçi olduğu için Rust onun ne kadar yer kapladığını her zaman
bilir: işaretçinin boyutu, işaret ettiği verinin miktarına göre değişmez.
Demek ki Dugum varyantına başka bir Listeyi doğrudan koymak yerine
Box<T> koyabiliriz. Box<T>, bir sonraki Liste değerini Dugumün içinde
değil öbekte tutar. Kavramsal olarak yine aynı listeye sahibiz; ama bu kez
öğeler birbirinin içine değil yanına yerleştirilmiş gibidir.
Liste 15-2’deki Liste tanımını ve Liste 15-3’teki kullanımı Liste 15-5’teki
koda çevirirsek, artık kod derlenir.
enum Liste {
Dugum(i32, Box<Liste>),
Bos,
}
use crate::Liste::{Bos, Dugum};
fn main() {
let liste = Dugum(1, Box::new(Dugum(2, Box::new(Dugum(3, Box::new(Bos))))));
}
Box<T> kullanan Liste tanimiDugum varyantı şimdi bir i32 boyutu ve kutu işaretçisinin boyutunu ister.
Bos varyantıysa hiç değer taşımaz; dolayısıyla yığında Dugumden daha az yer
kaplar. Artık herhangi bir Listenin boyutunun, i32 boyutu artı bir kutu
işaretçisi boyutu kadar olacağını biliyoruz. Kutu kullanarak sonsuz özyineli
zinciri kırdık; böylece derleyici Listeyi saklamak için gereken boyutu
hesaplayabilir. Şekil 15-2 bunu gösteriyor.
Şekil 15-2: Sonsuz boyutlu olmayan bir Liste; çünkü Dugum bir Box tutuyor
Kutular yalnızca dolaylı erişim ve öbek tahsisi sağlar; ileride göreceğimiz diğer akıllı işaretçi türlerindeki gibi ek yetenekleri yoktur. Bu ek yeteneklerin getirdiği maliyetleri de taşımazlar; bu yüzden cons liste gibi, ihtiyacımız olan tek şey dolaylı erişim olduğunda çok kullanışlıdırlar. Kutular için başka kullanım alanlarını 18. bölümde yeniden göreceğiz.
Box<T> akıllı işaretçi sayılır; çünkü Deref trait’ini uygular. Bu sayede
Box<T> değerlerine referansmış gibi davranılabilir. Box<T> kapsam dışına
çıktığında, kutunun işaret ettiği öbek verisi de Drop trait’i sayesinde
temizlenir. Bu iki trait, bu bölümün geri kalanında göreceğimiz diğer akıllı
işaretçi türleri için daha da önemli olacak. Şimdi onlara bakalım.