Skip to content

Commit 1da97bf

Browse files
committed
Updated readme document.
1 parent 26bf879 commit 1da97bf

File tree

1 file changed

+89
-65
lines changed

1 file changed

+89
-65
lines changed

Lesson_10/Readme.md

Lines changed: 89 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,48 @@
11
# Ders 10: Smart Pointers
22

33
Verinin bellekteki temsili sırasında şartlara göre farklı senaryolar için farklı enstrümanlar kullanılabilir. Bu nedenle
4-
**Smart Pointer** kullanımını bilmek önemlidir. Smart Pointer türlerine geçmeden önce bazı temek kavramlar üzerinde
5-
duralım. Rust dilinde birçok smart pointer vardır. Box, RefCell, Rc, Arc gibi
6-
7-
- Pointer
4+
**Smart Pointer** türlerini bilmek önemlidir. Rust dilinde birçok smart pointer bulunur. Box, RefCell, Rc, Arc gibi Çoğu
5+
durumda derleme zamanında verinin nerede tutulacağı ve ne kadar yer kaplayacağına dair Rust'ın öngörüleri vardır. Bazı
6+
hallerde Stack odaklı verilerin kasıtlı olarak heap'e alınması istenebilir ya da heap'te yer alan verinin farklı
7+
thread'ler tarafından güvenli bir şekilde kullanılabilmesi ve üstelik mutable olarak ele alınması da gerekebilir. Aynı
8+
veriye birden fazla sahipliğin gelebilme ihtimali ve ek olarak mutable erişim ihtiyaçları akıllı işaretçiler tarafından
9+
daha kolay yönetilebilir.
10+
11+
- **Pointer**
812
- Bellek üzerindeki bir veri içeriğini işaret eden adres bilgisini taşıyan değişken olarak düşünülebilir.
9-
- Farkında olmadan şu ana kadar kullandığımız bir pointer vardır (&) ve datayı referans etmekte kullanılır.
10-
- Smart Pointer
11-
- Pointer adreslerine ek metadata bilgileri veya kabiliyetler ekler. Rust diline özel bir kavram değildir
12-
esasında C++ orijinlidir.
13-
- Referanslar veriyi işaret ederken Smart Pointer’ lar genellikle sahipliğini de alır. String ve Vec<T>
14-
türleri smart pointer olarak da geçerler, zira belli bir bellek adresindeki verinin sahipliğini alırlar ve onu
15-
manipüle etmemize izin verirler.
16-
- **Deref** ve **Drop** trait’lerini implemente eden struct türleri olarak tasarlanabilirler _(Yani kendi Smart
13+
- Farkında olmadan şu ana kadar kullandığımız bir pointer vardır **(&)** ve veriyi referans etmekte kullanılır.
14+
- **Smart Pointer**
15+
- **Pointer** adreslerine ek metadata bilgileri veya kabiliyetler ekler. Rust diline özel bir kavram değildir
16+
ve **C++** orijinlidir.
17+
- Referanslar veriyi işaret ederken Smart Pointer’ lar genellikle sahipliği _(Ownership)_ de alır. **String** ve
18+
**Vec<T>** türleri smart pointer olarak da geçerler, zira belli bir bellek adresindeki verinin sahipliğini
19+
alırlar ve onu değiştirmemize izin verirler.
20+
- **Deref** ve **Drop** trait’lerini implemente eden veri yapıları olarak da tasarlanabilirler _(Yani kendi Smart
1721
Pointer modellerimizi tasarlayabiliriz)_
1822

1923
## Boxing
2024

21-
Bir veriyi Stack yerine Heap üzerinde konuşlandırmanın en basit hali Box enstrümanını kullanmaktır. Aşağıda bu kullanıma
22-
ait basit bir fonksiyon yer almaktadır.
25+
Bir veriyi **Stack** yerine **Heap** üzerinde konuşlandırmanın en basit hali generic **Box** türevini kullanmaktır.
26+
Aşağıda bu kullanıma ait basit bir fonksiyon yer almaktadır.
2327

2428
```rust
2529
pub fn simple_boxing() {
26-
let value = 23; // Normalde stack' de saklanır
27-
let boxed_value = Box::new(value); // Şimdi heap'e alındı ama boxed_value hala stack'te zira adres göstermekte
30+
let value = 23;
31+
let boxed_value = Box::new(value);
2832
println!("Boxed value is {}", boxed_value);
2933

30-
let identity = ("John Smith", 23, true); // tuple veriyi stack'ta saklar
31-
let boxed_identity = Box::new(identity); // Şimdi heap' te
34+
let identity = ("John Smith", 23, true);
35+
let boxed_identity = Box::new(identity);
3236
println!("Boxed identity is {:?}", boxed_identity);
3337
}
3438
```
3539

36-
value isimli değişken sayısal bir değerdir ve normal şartarda Stack'te tutulur. Box ile söz konusu değişken verisi
37-
heap'e alınır ve stack'de onu işaret eden bir işaretçi bırakılır. Benzer bir kullanım şeklide Tuple veri türü içinde ele
38-
alınmıştır. Box türünün kullanımı için sıkça vurgulanan senaryolardan birisi ağaç boğum modelleridir _(Tree Nodes)_
39-
Aşağıdaki örnek kod parçasını göz önüne alalım.
40+
**value** isimli değişken sayısal bir değerdir ve normal şartarda **Stack**'te tutulur. **Box** ile söz konusu değişken
41+
verisi heap'e alınır ve stack'de onu işaret eden bir işaretçi bırakılır. Benzer bir kullanım şekli **Tuple** veri türü
42+
içinde ele alınmıştır.
43+
44+
Box türünün kullanımı için sıkça vurgulanan senaryolardan birisi ağaç boğum modelleridir _(Tree Nodes)_ Aşağıdaki örnek
45+
kod parçasını göz önüne alalım.
4046

4147
```rust
4248
fn main() {
@@ -54,17 +60,18 @@ pub fn recursive_data_model_with_error() {
5460
}
5561
```
5662

57-
Bu kod parçası derlenmeyecektir ve şöyle bir hata mesajı verecektir.
63+
Tree veri yapısı yine kendi türünden ikili dal şeklinde alt boğumlarını tutabilen bir veri yapısıdır. Lakin bu kod
64+
parçası derlenmeyecektir ve şöyle bir hata mesajı alınacaktır.
5865

5966
```text
6067
error[E0072]: recursive type `Tree` has infinite size
6168
```
6269

63-
Buradakine benzer recursive veri modellerinden datanın ne kadar yer kaplayacağı derleme zamanında bilinemez. Senaryoda
64-
enum türü kullanıldığı için de stack önceklikli bir yer ayırma durumu söz konusudur. Ne kadar boyut kaplanacağının
65-
bilinmemesi taşma hatalarına sebebiyet verebilir. Bir düğüm kendisine referans verdikçe bu sonsuz boyutlamaya doğru
66-
gidebilir. Dolayısıyla veriyi Heap üzerinde konuşlandırmak daha mantıklıdır. Benzer bir senaryoyu bu sefer aşağıdaki
67-
gibi tasarlayarak devam edelim.
70+
Bu senaryodaki gibi recursive veri modellerinde datanın ne kadar yer kaplayacağı derleme zamanında bilinemez. Senaryoda
71+
**enum** türü kullanıldığı için de **stack** önceklikli bir yer ayırmas durumu söz konusudur. Ne kadar boyut
72+
ayrılması gerektiğinin bilinmemesi taşma hatalarına _(Stack Overflow)_ sebebiyet verebilir. Bir düğüm kendisine referans
73+
verdikçe bu sonsuz boyutlamaya doğru gidebilir. Dolayısıyla veriyi **Heap** üzerinde konuşlandırmak daha mantıklıdır.
74+
Benzer bir senaryoyu bu sefer aşağıdaki kod parçasında olduğu gibi tasarlayarak devam edelim.
6875

6976
```rust
7077
use std::fmt::{Display, Formatter};
@@ -115,15 +122,21 @@ pub fn recursive_sample() {
115122
}
116123
```
117124

118-
Server isimli enum türünün Node alanı içerisinde Box edilen kendi türleri tutulmakta. Burada Box işlemi söz konusu
119-
olduğu için bağlı liste Heap üzerinde konuşlandırılacak.
125+
Server isimli **enum** türü, **Node** alanı içerisinde **Box** edilen kendi türlerini tutulmakta. Şirket sunucuları
126+
arasındaki master-child ilişkileri tutmak için kullanılabilecek ilkel bir veri yapısı söz konusu. Server veri
127+
yapısındaki node'lar Box edilerek tutulduğundan Heap alanında konuşlandırılacaklar. Dolayısıyla az önce karşılaşılan
128+
hata ortadan kalkacak. Zira yine de temkinli olmakta fayda var. Veri modelinin kontrolsüz şekilde büyüme ihtimalini de
129+
göz önüne almak gerekmekte. Bu açıdan bakıldığında yük testler, entegrasyon testleri gibi kontrol mekanizmaları ile kodu
130+
kontrol etmek mühim.
120131

121132
# Reference Counting
122133

123-
Bilinçli bir şekilde Heap üzerine alınan verilerde birden fazla sahipliğin söz konusu olduğu durumlarda referans
124-
değerlerin sayımı tutulur. Eğer paylaşımlı thread'ler söz konusu değilse Rc türü ihtiyacı karşılar. Birden fazla thread
125-
aynı veri alanı üzerinde çalışma gerektiği durumlarda ise tip güvenliğini gözeten Atomic Reference Counting yani Arc
126-
kullanılır. Aşağıdaki kod parçasında en basit haliyle Rc türünden bir smart pointer kullanımı işlenmektedir.
134+
Bilinçli bir şekilde **Heap** üzerine alınan verilerde birden fazla sahipliğin söz konusu olduğu durumlarda söz konusu
135+
olabilir. Böyle bir senaryoda çalışma zamanının referansların hangi sırayla bellekten düşürüleceğini bilmesi de gerekir.
136+
Buna özel yazılmış olan smart pointer türleri oluşturulan referanslası sayar ve eğer paylaşımlı thread'ler söz konusu
137+
değilse **Rc** türü ihtiyacı karşılar. Birden fazla **thread'in** aynı veri alanı üzerinde çalışması gerektiği
138+
durumlarda ise tip güvenliğini gözeten **Atomic Reference Counting** yani generic **Arc** türü kullanılır. Aşağıdaki kod
139+
parçasında en basit haliyle generic **Rc** türünden bir smart pointer kullanımı işlenmektedir.
127140

128141
```rust
129142
use std::rc::Rc;
@@ -143,8 +156,8 @@ pub fn hello_rc() {
143156
}
144157
```
145158

146-
Bu örnekte yer alan p1, p2 ve p3 değişkenleri aynı string veriyi içeren işaretçilerdir. Rc türüne olan ihtiyacı daha iyi
147-
anlamak için aşağıdaki basit örneğe bakalım.
159+
Bu örnekte yer alan p1, p2 ve p3 değişkenleri aynı string veriyi içeren işaretçilerdir. Senaryoyu biraz daha
160+
zorlaştıralım.
148161

149162
```rust
150163
fn main() {
@@ -193,13 +206,16 @@ pub fn run_rc_with_error() {
193206
}
194207
```
195208

196-
Bir oyuncunun arkadaşlarını da yine kendi türünden Vector olarak tutan Player isimli bir veri yapısı mevcut. add_friend
197-
metodu ile bir oyuncuya başka Player örnekleri ekleyebiliyoruz. Player'ın sahip olduğu veri üzerinde değişiklik söz
198-
konusu. Temsili run metoduna baktığımızda son satırdaki println! çağrısında value moved here hatası alırız. Bu son
199-
derece doğaldır zira steve değişkeni üzerinden yapılan ilk add_friend çağrısı sırasında lord değişkeninin sahipliği de
200-
taşınır. Dolayısıyla add_friend sonrası lord değişkenine tekrardan erişilemez. Bu tip bir senaryoyu yönetmek için Rc
201-
smart pointer kullanılabilir. Ancak mutable olma zorunluluğuna dikkat etmek gerekir. Bunu daha iyi anlamak için örneği
202-
aşağıdaki haliyle değiştirelim.
209+
Bir oyuncunun arkadaşlarını da yine kendi türünden bir **Vector** üzerinde tutan **Player** isimli bir veri yapısı
210+
mevcut. **add_friend** metodu ile bir oyuncuya başka **Player** örnekleri eklenebilmekte. Player türünden bir değişken
211+
oluşturulduğunda ve add_friends ile kendi türünden bir değişken eklenmek istendiğin işler biraz karışır. Zira Player'ın
212+
sahipliğini aldığı bir vector üzerinde değişiklik söz konusudur. Vector türü referans türevlidir. Temsili run metodu
213+
çağırıldığında son satırda yer alan **println!** makro çağrısında **value moved here** hatası alınır. Bu son
214+
derece doğaldır zira **steve** değişkeni üzerinden yapılan ilk **add_friend** çağrısı sırasında **lord** değişkeninin
215+
sahipliği de bu metoda taşınır. Dolayısıyla **add_friend** sonrası **lord** değişkenine tekrardan erişilemez. _(Burada
216+
klonlama gibi bir yöntemi deneyerek sonucun değişip değişmeyeceğini irdelemenizi öneririm_) Bu tip bir
217+
senaryoyu yönetmek için **Rc smart pointer** kullanılabilir. Ancak **mutable** olma zorunluluğuna dikkat etmek gerekir.
218+
Durumu daha iyi anlamak için örneği aşağıdaki gibi değiştirelim.
203219

204220
```rust
205221
#[derive(Debug)]
@@ -244,20 +260,23 @@ pub fn run_rc_with_error_2() {
244260
}
245261
```
246262

247-
Bu sefer add_friend metodu içerisindeki self.friends.push metodunda bir hata alınır.
263+
Player veri yapısındaki friends isimli alanda Rc kullanılmıştır. Bu sayede Player değişkenlerinin referanslarının
264+
sayılması ve dolayısıyla birden fazla sahipliğin mümkün kılınması hedeflenmektedir. Ancak bu kez de **add_friend**
265+
metodu içerisindeki **self.friends.push** çağrımı bir hataya sebebiyet verir.
248266

249267
```text
250268
cannot borrow data in an `Rc` as mutable [E0596]
251269
cannot borrow as mutable
252270
Help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<Player>`
253271
```
254272

255-
Player veri yapısı kendi içerisinden kendi türünden bir Vector kullanmaktadır.İlk hata sebebiyle Vec'ün Rc<Player>
256-
şeklinde kullanılması tercih edilebilir. Ancak bu özellikle add_friends metodunda vektör içeriğine mutable erişmeyi
257-
gerektirir. Bu nedenle vektöre referansının da mutable olarak ele alınabilmesi gerekir. Normalde bir veriye erişen
258-
birden fazla sahip varken mutable kullanım derleme hatasına yol açabilir. **RefCell** smart pointer kullanımı ile bunu
259-
çalışma zamanına taşırız. Yani ownership kontrolünü runtime tarafında işletilmesini sağlarız. Dolayısıyla örnek kodları
260-
aşağıdaki şekilde değiştirerek ilerleyebiliriz.
273+
Player veri yapısı kendi içinde kendi türünden bir Vector kullanmaktadır. İlk hata sebebiyle Vectörün **Rc<Player>**
274+
şeklinde kullanılması tercih edilebilir. Ancak **add_friends** metodunda vektör içeriğine mutable olarak erişebilmemiz
275+
de gerekmektedir ancak Rust sahiplik ilkeleri ve mutable kuralları buna müsaade etmez. Normalde bir veriye
276+
birden fazla immutable erişim söz konusu olabilir. Böyle bir senaryoda tek bir kişiye mutable izni verilir. Ancak
277+
Rust'ın bazı akıllı işaretçileri bu gibi durumları es geçerek kararın çalışma zamanı tarafından verilmesine olanak
278+
sağlar. **RefCell** smart pointer kullanımı ile bu mümkündür. Yani ownership kontrolünün **runtime** tarafında
279+
işletilmesi sağlanabilir. Buna göre örnek kodları aşağıdaki şekilde değiştirerek devam edebiliriz.
261280

262281
```rust
263282
use std::cell::RefCell;
@@ -309,33 +328,38 @@ pub fn run_rc() {
309328
}
310329
```
311330

312-
İlk dikkat edilmesi gereken nokta Player veri yapısındaki friends alanının türüdür. Player nesneleri için bir referans
313-
sayacı kullanılırken değiştirilebilir olmasını sağlama işi RefCell ile çalışma zamanına bırakılmıştır. new metodu
314-
içerisinde RefCell nesnesnin nasıl kullanıldığına da dikkat edelim. Ayrıca friends vektörünü üzerinde değişiklik yapmak
315-
üzere kullanacaksak aynen add_friend metodunda olduğu gibi borrow_mut fonksiyonu ile mutable olarak ödünç alınmasını
316-
sağlamalıyız. Eğer sadee okuma amaçlı kullanacaksak bu durumda da borrow metodunu kullanmalıyız.
331+
İlk dikkat edilmesi gereken nokta **Player** veri yapısındaki **friends** alanının türüdür. **Player** nesneleri için
332+
bir referans sayacı kullanılırken değiştirilebilir olmasını sağlama işi **RefCell** ile çalışma zamanına bırakılmıştır.
333+
Bir Player değişkenini oluşturmayı kolaylaştıran **new** metodu _(C# tarafındakiler için Constructor)_ içerisinde
334+
RefCell nesnesnin nasıl kullanıldığına da dikkat edelim. Ayrıca **friends** vektörünü üzerinde değişiklik yapmak
335+
üzere kullanacaksak aynen **add_friend** metodunda olduğu gibi **borrow_mut** fonksiyonu ile mutable olarak ödünç
336+
alınmasını sağlanmalıdır. Eğer sadece okuma amaçlı bir kullanım söz konusu ise borrow metodu kullanılmalıdır.
317337

318338
Bu senaryoya göre farklı kullanım şekilleri de söz konusu olabilir.
319339

320-
- Sadece bir vektör üzerinde çalışılacaksa RefCell<Vec<Player>> kullanımı yeterlidir.
321-
- Vektörün paylaşımı söz konusu ise Rc<RefCell<Vec<Player>>> daha uygun bir çözüm olabilir.
322-
- Hem vektörü hem de içindeki elemanların paylaşışması gerekiyorsa Rc<Vec<RefCell<Player>>>
340+
- Sadece bir vektör üzerinde çalışılacaksa **RefCell<Vec<Player>>** kullanımı yeterlidir.
341+
- Vektörün paylaşımı söz konusu ise **Rc<RefCell<Vec<Player>>>** daha uygun bir çözüm olabilir.
342+
- Hem vektörü hem de içindeki elemanların paylaşılması gerekiyorsa **Rc<Vec<RefCell<Player>>>**
323343
daha iyi bir çözüm olabilir.
324344

325-
Şunu da unutmamamak gerekir hem Rc hem de RefCell kullanımının çalışma zamanı maliyetleri daha yüksektir _(Zira referans
326-
sayımı ve mutasyon kontrolleri yapılmaktadır)_
345+
Unutmamamak gerekir ki gerek **Rc** gerek **RefCell** kullanımlarının çalışma zamanı maliyetleri daha yüksektir _(Zira
346+
referans sayımı ve mutasyon kontrolleri yapılmaktadır)_
327347

328348
Buraya kadar gördüğümüz Smart Pointer türlerini aşağıdaki grafikle özetleyebiliriz.
329349

330350
![Smart Pointers.png](smrt_ptrs.png)
331351

332352
## Atomic Reference Counting
333353

354+
Referans sayımları için Rc kullanımı yeterlidir ancak veriye farklı thread'lerden erişim söz konusu ise generic Arc türü
355+
ve kilitleme _(locking)_ mekanizmaları ele alınmalıdır.
356+
334357
// Thread'lerin işlendiği bölümde ele alınacaktır
335358

336-
## Hangisi ne zaman?
359+
## Hangisi Ne Zaman?
337360

338-
**Box** ve **RefCell** birden fazla sahipliği tek bir veri üzerinde sağlarken, Rc aynı veri üzerinden birden fazla
339-
sahiplik sunar. Box immutable veya mutable ödünç alma _(borrowing)_ için derleme zamanında kontroller sağlar. Rc sadece
340-
immutable borrowing için derleme zamanında kontrol sağlar. RefCell immutable veya mutable ödünç alma için runtime'da
341-
kontrol sağlar.
361+
- **Box** ve **RefCell** birden fazla sahipliği tek bir veri üzerinde sağlarken, **Rc** aynı veri üzerinden birden fazla
362+
sahiplik sunar.
363+
- Box **immutable** veya **mutable** ödünç alma _(borrowing)_ için derleme zamanında kontroller sağlar.
364+
- **Rc** sadece **immutable borrowing** için derleme zamanında kontrol sağlar.
365+
- **RefCell** türü **immutable** veya **mutable** ödünç alma için runtime'da kontrol sağlar.

0 commit comments

Comments
 (0)