Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Referanslar ve Ödünç Alma (References and Borrowing)

Liste 4-5’teki demet kodunun sorunu, String’in uzunlugu_hesapla fonksiyonuna taşınmış (moved) olması ve uzunlugu_hesapla çağrısından sonra bu String’i hala kullanabilmek için çağıran fonksiyona geri döndürmek zorunda kalmamızdır. Bunun yerine, String değerine bir referans sağlayabiliriz. Referans, bir işaretçiye (pointer) benzer; yani, o adreste depolanan verilere erişmek için takip edebileceğimiz bir adrestir; ancak o veriler başka bir değişkene aittir. İşaretçiden farklı olarak, bir referansın ömrü boyunca belirli bir türden geçerli bir değere işaret edeceği garanti edilir.

Bir değerin sahipliğini almak yerine nesneye referans olan bir parametreye sahip uzunlugu_hesapla fonksiyonunu nasıl tanımlayacağınız ve kullanacağınız aşağıda açıklanmıştır:

Filename: src/main.rs
fn main() {
    let metin1 = String::from("merhaba");

    let uzunluk = uzunlugu_hesapla(&metin1);

    println!("'{metin1}' metninin uzunluğu: {uzunluk}.");
}

fn uzunlugu_hesapla(metin: &String) -> usize {
    metin.len()
}

İlk olarak, değişken tanımındaki ve fonksiyon dönüş değerindeki tüm demet kodlarının gittiğine dikkat edin. İkinci olarak, uzunlugu_hesapla’ya &metin1 ilettiğimize ve tanımında String yerine &String aldığımıza dikkat edin. Bu ampersanlar (ve işaretleri, &) referansları temsil eder ve bir değerin sahipliğini almadan ona atıfta bulunmanıza olanak tanır. Şekil 4-6 bu konsepti tasvir etmektedir.

Üç tablo: metin için olan tablo yalnızca metin1 tablosuna yönelik bir işaretçi içerir. metin1 için olan tablo, metin1 için stack verilerini içerir ve heap üzerindeki string verilerine işaret eder.

Şekil 4-6: String metin1’e işaret eden &String metin diyagramı

Not: & kullanarak referans almanın (referencing) zıttı dereferencing (referansın değerini alma) olarak adlandırılır ve bu, dereference operatörü olan * ile gerçekleştirilir. Dereference operatörünün bazı kullanımlarını Bölüm 8’de göreceğiz ve detaylarını Bölüm 15’te tartışacağız.

Şimdi buradaki fonksiyon çağrısına daha yakından bakalım:

fn main() {
    let metin1 = String::from("merhaba");

    let uzunluk = uzunlugu_hesapla(&metin1);

    println!("'{metin1}' metninin uzunluğu: {uzunluk}.");
}

fn uzunlugu_hesapla(metin: &String) -> usize {
    metin.len()
}

&metin1 sözdizimi, metin1 değerine atıfta bulunan ancak ona sahip olmayan bir referans oluşturmamızı sağlar. Referans ona sahip olmadığı için, referansın kullanımı durduğunda işaret ettiği değer düşürülmeyecektir (drop edilmeyecektir).

Aynı şekilde, fonksiyonun imzası da metin parametresinin türünün bir referans olduğunu belirtmek için & kullanır. Gelin açıklayıcı birkaç not ekleyelim:

fn main() {
    let metin1 = String::from("merhaba");

    let uzunluk = uzunlugu_hesapla(&metin1);

    println!("'{metin1}' metninin uzunluğu: {uzunluk}.");
}

fn uzunlugu_hesapla(metin: &String) -> usize {
    // metin bir String'e referanstır
    metin.len()
} // Burada, metin kapsam dışına çıkar. Ancak metin referans verdiği şeyin
  // sahipliğine sahip olmadığı için String düşürülmez (drop edilmez).

metin değişkeninin geçerli olduğu kapsam, herhangi bir fonksiyon parametresinin kapsamıyla aynıdır, ancak referansın işaret ettiği değer metin kullanımı durduğunda düşürülmez, çünkü metin sahipliğe sahip değildir. Fonksiyonlar, asıl değerler yerine referansları parametre olarak aldıklarında, sahipliği geri vermek için değerleri döndürmemize gerek kalmaz, çünkü sahipliğe hiçbir zaman sahip olmadık.

Bir referans oluşturma eylemine ödünç alma diyoruz. Gerçek hayatta olduğu gibi, bir kişinin sahip olduğu bir şeyi ondan ödünç alabilirsiniz. İşiniz bittiğinde onu geri vermek zorundasınızdır. Ona sahip olmazsınız.

Peki, ödünç aldığımız bir şeyi değiştirmeye çalışırsak ne olur? Liste 4-6’daki kodu deneyin. Sürprizbozan: Çalışmıyor!

Filename: src/main.rs
fn main() {
    let metin = String::from("merhaba");

    degistir(&metin);
}

fn degistir(bir_metin: &String) {
    bir_metin.push_str(", dünya");
}
Listing 4-6: Ödünç alınan bir değeri değiştirmeye çalışmak

İşte hata:

$ cargo run
   Compiling ownership v0.1.0 ($PROJE/listings/ch04-understanding-ownership/listing-04-06)
error[E0596]: cannot borrow `*bir_metin` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
8 |     bir_metin.push_str(", dünya");
  |     ^^^^^^^^^ `bir_metin` is a `&` reference, so it cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
7 | fn degistir(bir_metin: &mut String) {
  |                         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Tıpkı değişkenlerin varsayılan olarak değiştirilemez olduğu gibi, referanslar da öyledir. Referansına sahip olduğumuz bir şeyi değiştirmemize izin verilmez.

Değiştirilebilir Referanslar (Mutable References)

Bunun yerine bir değiştirilebilir referans kullanarak sadece birkaç küçük ayarla ödünç alınan bir değeri değiştirmemize izin vermesi için Liste 4-6’daki kodu düzeltebiliriz:

Filename: src/main.rs
fn main() {
    let mut metin = String::from("merhaba");

    degistir(&mut metin);
}

fn degistir(bir_metin: &mut String) {
    bir_metin.push_str(", dünya");
}

Önce, metin’i mut olacak şekilde değiştiririz. Ardından, degistir fonksiyonunu çağırdığımız yerde &mut metin ile değiştirilebilir bir referans oluştururuz ve fonksiyon imzasını bir_metin: &mut String ile değiştirilebilir bir referans kabul edecek şekilde güncelleriz. Bu, degistir fonksiyonunun ödünç aldığı değeri değiştireceğini çok net bir şekilde belirtir.

Değiştirilebilir referansların büyük bir kısıtlaması vardır: Bir değere değiştirilebilir bir referansınız varsa, o değere başka hiçbir referansınız olamaz. metin’e iki tane değiştirilebilir referans oluşturmaya çalışan bu kod başarısız olacaktır:

Filename: src/main.rs
fn main() {
    let mut metin = String::from("merhaba");

    let r1 = &mut metin;
    let r2 = &mut metin;

    println!("{r1}, {r2}");
}

İşte hata:

$ cargo run
   Compiling ownership v0.1.0 ($PROJE/listings/ch04-understanding-ownership/no-listing-10-multiple-mut-not-allowed)
error[E0499]: cannot borrow `metin` as mutable more than once at a time
 --> src/main.rs:6:14
  |
5 |     let r1 = &mut metin;
  |              ---------- first mutable borrow occurs here
6 |     let r2 = &mut metin;
  |              ^^^^^^^^^^ second mutable borrow occurs here
7 |
8 |     println!("{r1}, {r2}");
  |                -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Bu hata, kodun geçersiz olduğunu çünkü metin’i aynı anda birden fazla kez değiştirilebilir olarak ödünç alamayacağımızı söylüyor. İlk değiştirilebilir ödünç alma r1’dedir ve println! içinde kullanılana kadar sürmelidir, ancak bu değiştirilebilir referansın oluşturulmasıyla kullanımı arasında, r1 ile aynı veriyi ödünç alan r2’de başka bir değiştirilebilir referans oluşturmaya çalıştık.

Aynı verilere aynı anda birden fazla değiştirilebilir referans yapılmasını engelleyen bu kısıtlama, değişime izin verir ancak bunu çok kontrollü bir şekilde yapar. Çoğu dil istediğiniz zaman değiştirmenize izin verdiği için bu, yeni Rust geliştiricilerinin (Rustaceans) zorlandığı bir konudur. Bu kısıtlamaya sahip olmanın faydası, Rust’ın derleme zamanında veri yarışlarını (data races) önleyebilmesidir. Veri yarışı (data race), yarış koşuluna (race condition) benzer ve şu üç davranış meydana geldiğinde ortaya çıkar:

  • İki veya daha fazla işaretçi aynı anda aynı verilere erişir.
  • İşaretçilerden en az biri verilere yazmak için kullanılmaktadır.
  • Verilere erişimi senkronize etmek için hiçbir mekanizma kullanılmamaktadır.

Veri yarışları tanımsız davranışlara (undefined behavior) neden olur ve bunları çalışma zamanında izlemeye çalışırken teşhis edip düzeltmek zor olabilir; Rust, veri yarışları içeren kodları derlemeyi reddederek bu sorunu baştan engeller!

Her zamanki gibi, yalnızca eşzamanlı (simultaneous) olmayan, birden çok değiştirilebilir referansa izin veren yeni bir kapsam oluşturmak için süslü parantezleri kullanabiliriz:

fn main() {
    let mut metin = String::from("merhaba");

    {
        let r1 = &mut metin;
    } // r1 burada kapsam dışına çıkar, bu yüzden hiçbir sorun olmadan yeni bir referans yapabiliriz.

    let r2 = &mut metin;
}

Rust, değiştirilebilir ve değiştirilemez referansları birleştirmek için de benzer bir kural uygular. Bu kod bir hatayla sonuçlanır:

fn main() {
    let mut metin = String::from("merhaba");

    let r1 = &metin; // sorun yok
    let r2 = &metin; // sorun yok
    let r3 = &mut metin; // BÜYÜK SORUN

    println!("{r1}, {r2}, ve {r3}");
}

İşte hata:

$ cargo run
   Compiling ownership v0.1.0 ($PROJE/listings/ch04-understanding-ownership/no-listing-12-immutable-and-mutable-not-allowed)
error[E0502]: cannot borrow `metin` as mutable because it is also borrowed as immutable
 --> src/main.rs:7:14
  |
5 |     let r1 = &metin; // sorun yok
  |              ------ immutable borrow occurs here
6 |     let r2 = &metin; // sorun yok
7 |     let r3 = &mut metin; // BÜYÜK SORUN
  |              ^^^^^^^^^^ mutable borrow occurs here
8 |
9 |     println!("{r1}, {r2}, ve {r3}");
  |                -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Vay canına! Aynı değere değiştirilemez bir referansımız varken, aynı zamanda değiştirilebilir bir referansımız da olamaz.

Değiştirilemez referans kullananlar, değerin aniden altlarından değişmesini beklemezler! Ancak, birden çok değiştirilemez referansa izin verilir çünkü sadece verileri okuyan hiç kimsenin, başka birinin verileri okumasını etkileme yeteneği yoktur.

Bir referansın kapsamının, tanıtıldığı yerden başladığına ve referansın son kullanıldığı zamana kadar devam ettiğine dikkat edin. Örneğin, değiştirilemez referansların son kullanımı, değiştirilebilir referans tanıtılmadan önce println! içinde olduğundan bu kod derlenecektir:

fn main() {
    let mut metin = String::from("merhaba");

    let r1 = &metin; // sorun yok
    let r2 = &metin; // sorun yok
    println!("{r1} ve {r2}");
    // r1 ve r2 değişkenleri bu noktadan sonra kullanılmayacak.

    let r3 = &mut metin; // sorun yok
    println!("{r3}");
}

Değiştirilemez referanslar r1 ve r2’nin kapsamları, en son kullanıldıkları println!’den sonra biter; bu da değiştirilebilir referans r3’ün yaratılmasından öncedir. Bu kapsamlar örtüşmez (overlap), dolayısıyla bu koda izin verilir: Derleyici, referansın kapsamın bitiminden önceki bir noktada artık kullanılmadığını anlayabilir.

Ödünç alma hataları zaman zaman sinir bozucu olsa da, bunun Rust derleyicisinin potansiyel bir bug’ı erkenden (çalışma zamanından ziyade derleme zamanında) göstermesi ve size tam olarak nerede sorun olduğunu bildirmesi olduğunu unutmayın. Böylece verilerinizin neden düşündüğünüz gibi olmadığını bulmak için iz sürmek zorunda kalmazsınız.

Sarkan Referanslar (Dangling References)

İşaretçileri olan dillerde, bazı bellekleri serbest bırakırken o belleğe giden bir işaretçiyi koruyarak yanlışlıkla bir sarkan işaretçi (dangling pointer) (muhtemelen başkasına verilmiş olan bellekteki bir konuma başvuran bir işaretçi) oluşturmak kolaydır. Rust’ta ise bunun aksine, derleyici referansların asla sarkan referanslar olmayacağını garanti eder: Bazı verilere bir referansınız varsa, derleyici verilere olan referansın kapsam dışına çıkmasından önce verilerin kapsam dışına çıkmamasını sağlayacaktır.

Rust’ın derleme zamanı hatasıyla bunları nasıl engellediğini görmek için sarkan bir referans oluşturmayı deneyelim:

Filename: src/main.rs
fn main() {
    let hicbire_referans = sarkan_isaretci();
}

fn sarkan_isaretci() -> &String {
    let metin = String::from("merhaba");

    &metin
}

İşte hata:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn sarkan_isaretci() -> &String {
  |                       ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
5 | fn sarkan_isaretci() -> &'static String {
  |                        +++++++
help: instead, you are more likely to want to return an owned value
  |
5 - fn sarkan_isaretci() -> &String {
5 + fn sarkan_isaretci() -> String {
  |

For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Bu hata mesajı henüz ele almadığımız bir özelliği ifade eder: ömürler. Ömürleri Bölüm 10’da ayrıntılı olarak tartışacağız. Ancak, ömürlerle ilgili kısımları göz ardı ederseniz, mesaj gerçekten de bu kodun neden sorun olduğuna dair temel ipucunu içerir:

this function's return type contains a borrowed value, but there is no value
for it to be borrowed from
(bu fonksiyonun dönüş türü ödünç alınan bir değer içeriyor, ancak ödünç alınacağı bir değer yok)

sarkan_isaretci kodumuzun her aşamasında tam olarak ne olduğuna daha yakından bakalım:

Filename: src/main.rs
fn main() {
    let hicbire_referans = sarkan_isaretci();
}

fn sarkan_isaretci() -> &String {
    // sarkan_isaretci, bir String'e referans döndürür

    let metin = String::from("merhaba"); // metin yeni bir String'dir

    &metin // String'e, yani metin'e bir referans döndürüyoruz
} // Burada metin kapsam dışına çıkar ve düşürülür (drop edilir), bu yüzden belleği gider.
  // Tehlike!

metin değişkeni sarkan_isaretci içinde oluşturulduğu için, sarkan_isaretci kodu bittiğinde metin’in bellekten tahsisi kaldırılacaktır (deallocated). Ancak ona bir referans döndürmeye çalıştık. Bu, bu referansın geçersiz bir String’e işaret edeceği anlamına gelir. Bu hiç iyi değil! Rust bunu yapmamıza izin vermeyecektir.

Buradaki çözüm doğrudan String’i döndürmektir:

fn main() {
    let metin = sarkmayan_isaretci();
}

fn sarkmayan_isaretci() -> String {
    let metin = String::from("merhaba");

    metin
}

Bu hiçbir sorun olmadan çalışır. Sahiplik dışarı taşınır ve hiçbir şeyin tahsisi kaldırılmaz.

Referans Kuralları

Referanslar hakkında tartıştıklarımızı özetleyelim:

  • Herhangi bir zamanda, ya bir tane değiştirilebilir referansa ya da istediğiniz sayıda değiştirilemez referansa sahip olabilirsiniz.
  • Referanslar her zaman geçerli olmalıdır.

Sırada, farklı bir referans türüne bakacağız: dilimler.