İçeriğe geç
Educora
Orta22 dk4 / 10

Sahiplik, ödünç alma ve dilimler

Rust'ın en önemli fikrini öğren: her değerin tek bir sahibi vardır. Taşımayı (move), `&` ve `&mut` referanslarını, ödünç alma kurallarını, dilimleri ve `String` ile `&str` arasındaki farkı kavra.

Kendini test et
Bu derste öğreneceklerin
  • Üç sahiplik kuralını söylemek ve taşıma ile kopyalama arasındaki farkı açıklamak
  • Değerleri & ve &mut referanslarıyla sahipliği almadan fonksiyonlara vermek
  • E0382, E0499 ve E0502 hatalarını okumak ve düzeltmek
  • Dilimlerle çalışmak ve String ile &str arasında doğru seçim yapmak

Bir kitabın var. Onu bir arkadaşına hediye edebilirsin; o zaman artık senin değildir. Aynı anda birkaç arkadaşına okumaları için ödünç verebilirsin. Ya da yalnızca bir arkadaşına not yazması için verebilirsin; o yazarken yarım kalmış notları görmesin diye başka kimse kitabı okumamalıdır. Rust belleği tam da bu mantıkla yönetir: sahiplik, okumak için ödünç ve değiştirmek için ödünç.

Yığın, öbek ve sahiplik kuralları

Boyutu önceden bilinen değerler (i32, bool, char) hızlı yığında (stack) tutulur. Boyutu değişebilen veriler, örneğin büyüyen bir String, öbek (heap) denen dinamik bellekte bulunur: yığında yalnızca bir işaretçi, uzunluk ve kapasite kalır. Öbekteki belleği birinin serbest bırakması gerekir; Rust'ta bu, sahibin işidir.

  1. Rust'ta her değerin bir sahibi vardır: onu tutan değişken.
  2. Aynı anda yalnızca bir sahip olabilir.
  3. Sahip kapsam dışına çıktığında ({ } bloğu bittiğinde) değer otomatik olarak düşürülür (drop) ve belleği serbest bırakılır.

Taşıma, klonlama ve Copy

let s2 = s1; yazdığında String'in öbekteki içeriği kopyalanmaz; sahiplik s1'den s2'ye taşınır (move). İkisi de sahip olsaydı, blok bittiğinde aynı belleği iki kez serbest bırakırlardı. Bu yüzden Rust s1'i geçersiz sayar ve kullanılmasını yasaklar. Gerçekten ikinci bir kopya gerekiyorsa .clone() çağır.

E0382 hatası
fn main() {
    let s1 = String::from("Baku");
    let s2 = s1;
    println!("{s1} {s2}");
}
`clone` ile doğru
fn main() {
    let s1 = String::from("Baku");
    let s2 = s1.clone();
    println!("{s1} {s2}");
}
Sağdaki program Baku Baku yazdırır. clone, öbekteki verinin tam bir kopyasını oluşturur; bu yüzden büyük verilerde pahalıdır.
Text
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:4:15
  |
2 |     let s1 = String::from("Baku");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |     println!("{s1} {s2}");
  |               ^^^^ value borrowed here after move

Tam sayılar, ondalıklı sayılar, bool, char ve yalnızca bunlardan oluşan demetler Copy türleridir: tamamen yığında yaşarlar ve kopyalanmaları ucuzdur. Bu tür değerler taşınmaz, kopyalanır; let b = a; sonrasında iki değişken de kullanılabilir. Bir fonksiyona argüman vermek de aynı kurala uyar:

Rust
fn take(text: String) {
    println!("took: {text}");
}

fn show(n: i32) {
    println!("copied: {n}");
}

fn main() {
    let city = String::from("Ganja");
    take(city);
    // println!("{city}"); // error E0382: city was moved

    let year = 2024;
    show(year);
    println!("still have: {year}");
}
Beklenen çıktı
took: Ganja
copied: 2024
still have: 2024
take, String'in sahibi oldu ve sonunda onu düşürdü. year ise i32 olduğu için yalnızca kopyası verildi.

Referanslar ve ödünç alma

Sahipliği her seferinde verip geri almak yorucudur. Bunun yerine değeri ödünç verebilirsin: &name, sahipliği almayan bir referans oluşturur. Fonksiyon değeri referans üzerinden okur, sonrasında sahip değişmez. Değeri değiştirmek için değiştirilebilir referans &mut gerekir; bunun için değişkenin kendisi de mut olmalıdır.

Rust
fn length(text: &String) -> usize {
    text.len()
}

fn add_suffix(text: &mut String) {
    text.push_str(" city");
}

fn main() {
    let mut name = String::from("Baku");
    let len = length(&name);
    println!("{name} has {len} letters");

    add_suffix(&mut name);
    println!("{name}");
}
Beklenen çıktı
Baku has 4 letters
Baku city
Tanım
Ödünç alma kuralları

Herhangi bir anda bir değerin ya istediği kadar & referansı ya da yalnızca bir &mut referansı olabilir; ikisi birlikte asla. Ayrıca referanslar her zaman geçerli olmalıdır: bir referans sahibinden uzun yaşayamaz. Bir referans, son kullanıldığı yerde “biter”.

E0499 hatası
fn main() {
    let mut s = String::from("hi");
    let r1 = &mut s;
    let r2 = &mut s;
    r1.push('!');
    r2.push('?');
    println!("{s}");
}
Doğru: `hi!?` yazdırır
fn main() {
    let mut s = String::from("hi");
    let r1 = &mut s;
    r1.push('!');
    let r2 = &mut s;
    r2.push('?');
    println!("{s}");
}
Solda iki &mut referansı aynı anda “canlı” (cannot borrow s as mutable more than once at a time). Sağda r1 son kez push içinde kullanılır ve r2 oluşturulmadan önce biter.
E0502 hatası
fn main() {
    let mut scores = vec![90, 85];
    let first = &scores[0];
    scores.push(70);
    println!("{first}");
}
Doğru: `90` yazdırır
fn main() {
    let mut scores = vec![90, 85];
    let first = scores[0];
    scores.push(70);
    println!("{first}");
}
vec![...] büyüyebilen bir liste (Vec) oluşturur. Sağda first bir referans değil, i32 değerinin kopyasıdır; bu yüzden liste değiştirilebilir.
Text
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.push(70);
  |     ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("{first}");
  |               ------- immutable borrow later used here

Dilimler: String ve &str

Dilim (slice), bir koleksiyonun bir kısmına referanstır: &numbers[1..4], 1, 2 ve 3 indeksli elemanları gösterir. Metin diliminin türü &str'dir. Programdaki her metin değişmezi ("hello") de bir &str'dir: program dosyasının içinde saklanan metne bakan bir referans. String ise kendi verisinin sahibidir, öbekte yaşar ve büyüyebilir.

Rust
fn first_word(text: &str) -> &str {
    for (i, ch) in text.char_indices() {
        if ch == ' ' {
            return &text[..i];
        }
    }
    text
}

fn main() {
    let sentence = String::from("hello brave world");
    let word = first_word(&sentence);
    println!("{word}");
    println!("{}", first_word("Rust"));

    let numbers = [10, 20, 30, 40, 50];
    let middle = &numbers[1..4];
    println!("{:?} len={}", middle, middle.len());
}
Beklenen çıktı
hello
Rust
[20, 30, 40] len=3
first_word hem &String hem de değişmez kabul eder: Rust, &String'i otomatik olarak &str'ye çevirir. &text[..i], baştan i baytına kadar olan kısımdır.
ÖzellikString&str
Sahiplikverisinin sahibidirödünç alır
Değiştirmemut ile büyüyebilir: push_stryalnızca okuma
OluşturmaString::from("hi"), "hi".to_string()"hi", &s, &s[0..2]
Tipik yeriyapı alanları, metin oluşturmafonksiyon parametreleri

Önemli noktalar

  • Her değerin tek bir sahibi vardır; sahip kapsam dışına çıkınca değer düşürülür.
  • String gibi türler atamada ve fonksiyona verilirken taşınır; Copy türleri ise kopyalanır.
  • & okumak için, &mut değiştirmek için ödünç alır; sahiplik değişmez.
  • Aynı anda ya çok sayıda & ya da tek bir &mut olabilir; referans sahibinden uzun yaşamaz.
  • &str bir metin dilimidir, String ise kendi verisine sahip, büyüyebilen metindir; parametrelerde &str kullan.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
let a = String::from("x"); let b = a; kodundan sonra hangi değişken kullanılabilir?