Məzmuna keç
Educora
Orta22 dəq4 / 10

Sahiblik, borc götürmə və dilimlər

Rust-ın ən vacib ideyasını öyrən: hər qiymətin bir sahibi var. Sahibliyin ötürülməsini (move), `&` və `&mut` istinadlarını, borc götürmə qaydalarını, dilimləri və `String` ilə `&str` fərqini anla.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Sahibliyin üç qaydasını söyləmək və move ilə surət çıxarmanın fərqini izah etmək
  • & və &mut istinadları ilə qiyməti sahibliyi götürmədən funksiyaya vermək
  • E0382, E0499 və E0502 xətalarını oxumaq və düzəltmək
  • Dilimlərlə işləmək və String ilə &str arasında düzgün seçim etmək

Sənin bir kitabın var. Onu dostuna bağışlaya bilərsən — onda kitab artıq sənin deyil. Onu bir neçə dosta eyni anda oxumağa verə bilərsən. Ya da onu yalnız bir dosta qeyd yazmağa verə bilərsən — o yazarkən başqa heç kim kitabı oxumamalıdır ki, yarımçıq qeydləri görməsin. Rust yaddaşı məhz bu məntiqlə idarə edir: sahiblik, oxumaq üçün borc və dəyişmək üçün borc.

Stek, heap və sahiblik qaydaları

Ölçüsü əvvəlcədən məlum olan qiymətlər (i32, bool, char) sürətli stekdə saxlanılır. Ölçüsü dəyişə bilən məlumat, məsələn, böyüyən mətn String, heap adlanan dinamik yaddaşda yerləşir: stekdə yalnız göstərici, uzunluq və tutum qalır. Heap-dəki yaddaşı kimsə azad etməlidir — Rust-da bu, sahibin işidir.

  1. Rust-da hər qiymətin bir sahibi var — onu saxlayan dəyişən.
  2. Eyni anda yalnız bir sahib ola bilər.
  3. Sahib görünmə sahəsindən ({ } blokundan) çıxanda qiymət avtomatik silinir (drop) və yaddaş azad olunur.

Ötürmə (move), klonlama və Copy

let s2 = s1; yazanda String-in heap-dəki məzmunu kopyalanmır — sahiblik s1-dən s2-yə ötürülür (move). Əgər hər ikisi sahib olsaydı, blok bitəndə eyni yaddaşı iki dəfə azad edərdilər. Buna görə Rust s1-i etibarsız sayır və ondan istifadəni qadağan edir. Həqiqətən ikinci surət lazımdırsa, .clone() çağır.

Xəta E0382
fn main() {
    let s1 = String::from("Baku");
    let s2 = s1;
    println!("{s1} {s2}");
}
`clone` ilə düzgün
fn main() {
    let s1 = String::from("Baku");
    let s2 = s1.clone();
    println!("{s1} {s2}");
}
Sağdakı proqram Baku Baku çap edir. clone heap-dəki məlumatın tam surətini yaradır, ona görə böyük məlumatda baha başa gəlir.
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 ədədlər, kəsr ədədlər, bool, char və yalnız onlardan ibarət kortejlər Copy tiplərdir: onlar bütövlüklə stekdə yaşayır və surət çıxarmaq ucuzdur. Belə qiymətlər ötürülmür, surəti çıxarılır — let b = a;-dan sonra hər iki dəyişən işləkdir. Funksiyaya arqument vermək də eyni qaydaya tabedir:

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}");
}
Gözlənilən nəticə
took: Ganja
copied: 2024
still have: 2024
take String-in sahibi oldu və sonunda onu sildi. year isə i32 olduğu üçün sadəcə surəti ötürüldü.

İstinadlar və borc götürmə

Hər dəfə sahibliyi verib geri almaq yorucudur. Bunun əvəzinə qiyməti borc vermək olar: &name sahibliyi götürməyən istinad yaradır. Funksiya istinadla qiyməti oxuyur, iş bitəndə isə sahib dəyişmir. Qiyməti dəyişmək üçün dəyişən istinad &mut lazımdır — bunun üçün dəyişənin özü də 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}");
}
Gözlənilən nəticə
Baku has 4 letters
Baku city
Tərif
Borc götürmə qaydaları

İstənilən anda qiymətə ya istənilən sayda & istinadı, ya da yalnız bir &mut istinadı ola bilər — ikisi birlikdə yox. İstinadlar isə həmişə etibarlı olmalıdır: istinad sahibindən uzun yaşaya bilməz. İstinad son dəfə istifadə olunduğu yerdə «bitir».

Xəta E0499
fn main() {
    let mut s = String::from("hi");
    let r1 = &mut s;
    let r2 = &mut s;
    r1.push('!');
    r2.push('?');
    println!("{s}");
}
Düzgün: `hi!?` çap edir
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 eyni anda «canlıdır» (cannot borrow s as mutable more than once at a time). Sağda r1 son dəfə push-da istifadə olunur və r2 yaranandan əvvəl bitir.
Xəta E0502
fn main() {
    let mut scores = vec![90, 85];
    let first = &scores[0];
    scores.push(70);
    println!("{first}");
}
Düzgün: `90` çap edir
fn main() {
    let mut scores = vec![90, 85];
    let first = scores[0];
    scores.push(70);
    println!("{first}");
}
vec![...] böyüyə bilən siyahı (Vec) yaradır. Sağda first istinad yox, i32 qiymətinin surətidir, ona görə siyahını dəyişmək olar.
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

Dilimlər: String və &str

Dilim (slice) kolleksiyanın bir hissəsinə istinaddır: &numbers[1..4] 1, 2 və 3 indeksli elementləri göstərir. Mətn dilimi &str tipindədir. Proqramdakı hər mətn literalı ("hello") da &str-dir: o, proqram faylının içindəki mətnə baxan istinaddır. String isə öz məlumatının sahibidir, heap-də yaşayır və böyüyə bilər.

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());
}
Gözlənilən nəticə
hello
Rust
[20, 30, 40] len=3
first_word həm &String, həm də literal qəbul edir: Rust &String-i avtomatik &str-ə çevirir. &text[..i] əvvəldən i baytına qədər olan hissədir.
XüsusiyyətString&str
Sahiblikməlumatın sahibidirborc götürür
Dəyişməkmut ilə böyüyə bilər: push_stryalnız oxumaq
YaratmaqString::from("hi"), "hi".to_string()"hi", &s, &s[0..2]
Tipik yeristruktur sahələri, mətn qurmaqfunksiya parametrləri

Əsas fikirlər

  • Hər qiymətin bir sahibi var; sahib görünmə sahəsindən çıxanda qiymət silinir.
  • String kimi tiplər mənimsədiləndə və funksiyaya veriləndə ötürülür (move); Copy tiplərin isə surəti çıxarılır.
  • & oxumaq üçün, &mut dəyişmək üçün borc götürür; sahiblik dəyişmir.
  • Eyni anda ya çoxlu &, ya da tək &mut ola bilər; istinad sahibindən uzun yaşamır.
  • &str mətn dilimidir, String isə öz məlumatına sahib olan, böyüyə bilən mətndir; parametrlərdə &str yaz.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
let a = String::from("x"); let b = a; kodundan sonra hansı dəyişəndən istifadə etmək olar?