Rust'ta E0499 (cannot borrow as mutable more than once at a time), E0502 (cannot borrow as mutable because it is also borrowed as immutable) ve E0506 (cannot assign to ... because it is borrowed) hatalarının ortak nedeni tek bir kuraldır: bir değere aynı anda ya istediğiniz kadar paylaşılan referans (&T) ya da tek bir değiştirilebilir referans (&mut T) olabilir. Bu hataları çözmenin yolu, çakışan iki ödünç almanın zamanda üst üste binmesini engellemektir: önceki referansın son kullanımını yeni ödünç almadan önceye almak, referans yerine değeri kopyalamak, kapsamı daraltmak ya da derleyiciye iki ayrı parçayla çalıştığınızı gösteren split_at_mut gibi araçlar kullanmak.

Bu yazıda üç hatayı ve en sık görüldükleri durumları Rust 1.99.1'in gerçek mesajlarıyla inceliyoruz. Referansların temel kullanımı referans ve borrowing yazısında anlatılıyor. Değeri taşıdıktan sonra kullanmaktan kaynaklanan hatalar için E0382 borrow of moved value, referansın sahibinden uzun yaşamasıyla ilgili hatalar için E0597 ve lifetime hataları yazısına bakabilirsiniz.

Kural: ödünç alma ne zaman başlar, ne zaman biter?

The Rust Programming Language kitabı bu kısıtın amacını veri yarışlarını derleme anında önlemek olarak açıklar. Kitaba göre veri yarışı üç koşul bir araya geldiğinde oluşur: iki veya daha fazla işaretçi aynı veriye aynı anda erişir, en az biri yazar ve erişimler senkronize edilmez. Rust bu durumu çalışma anında aramak yerine böyle kodu hiç derlemeyerek engeller. Tek iş parçacıklı kodda bile kural geçerlidir, çünkü örneğin bir vektöre eleman eklemek elemanların bellekte yer değiştirmesine yol açabilir.

Kuralın pratikteki en önemli ayrıntısı, bir referansın ne zaman bittiğidir. Kitap bunu açıkça söyler: referansın kapsamı tanıtıldığı yerde başlar ve o referansın son kullanıldığı yere kadar sürer, süslü parantezin kapandığı yere kadar değil. Bu yüzden aşağıdaki kod derlenir; r1 ve r2 son kez println! içinde kullanılır ve r3 ondan sonra oluşturulur:

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

    let r1 = &s;
    let r2 = &s;
    println!("{r1} ve {r2}");
    // r1 ve r2 burada son kez kullanıldı; ödünç alma bitti

    let r3 = &mut s;
    r3.push_str(" dünya");
    println!("{r3}");
}
// çıktı: merhaba ve merhaba
// çıktı: merhaba dünya

Bu yazıdaki hataların neredeyse hepsinde derleyici üç yer işaretler: ilk ödünç almanın başladığı yer, çakışan ikinci ödünç almanın yapıldığı yer ve ilk referansın daha sonra kullanıldığı yer (later used here). Çözüm genellikle bu üçüncü satırdadır: o kullanım ortadan kalkarsa veya yeri değişirse çakışma da biter.

E0499: aynı değere iki &mut

Aynı değere aynı anda iki değiştirilebilir referans almak E0499 verir:

rust
fn main() {
    let mut s = String::from("merhaba");
    let r1 = &mut s;
    let r2 = &mut s;
    r1.push('!');
    r2.push('?');
    println!("{s}");
}
// error[E0499]: cannot borrow `s` as mutable more than once at a time

Mesajda first mutable borrow occurs here etiketi r1 satırını, second mutable borrow occurs here etiketi r2 satırını, first borrow later used here etiketi ise r1.push('!') satırını gösterir. İki değiştirilebilir referansın kullanımı iç içe geçtiği için hata çıkar. Kullanımları sıraya koymak yeterlidir; burada ilk referansı ayrı bir blokta bitirdik:

rust
fn main() {
    let mut s = String::from("merhaba");
    {
        let r1 = &mut s;
        r1.push('!');
    }
    let r2 = &mut s;
    r2.push('?');
    println!("{s}");
}
// çıktı: merhaba!?

Blok burada okunabilirlik içindir; r1 bloktan önce son kez kullanıldığı için bloksuz sürüm de, yani r1.push('!') satırını r2 tanımından önceye almak da derlenir.

E0502: paylaşılan ve değiştirilebilir referansı karıştırmak

E0502: paylaşılan ve değiştirilebilir referansı karıştırmak: Döngü içinde koleksiyonu değiştirmek, &mut parametre ile çalışan fonksiyonlarda

E0502 en sık bir koleksiyonun elemanına referans tutarken koleksiyonu değiştirmeye çalıştığınızda görülür. Kitabın vektörler bölümündeki klasik örnek şudur:

rust
fn main() {
    let mut v = vec![1, 2, 3];
    let ilk = &v[0];
    v.push(4);
    println!("ilk eleman: {ilk}");
}
// error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable

İlk bakışta ilk elemana referans ile sona eleman eklemek ilgisiz görünür. Kitap nedeni şöyle açıklar: vektör elemanları bellekte yan yana durur; yeni eleman için yer yoksa vektör yeni bir bellek alanı ayırıp eski elemanları oraya kopyalayabilir. Bu durumda ilk referansı serbest bırakılmış belleği gösterirdi. Ödünç alma kuralları tam da bu durumu engeller. İki basit çözüm var: okumayı değişiklikten önce bitirmek ya da i32 gibi Copy tiplerde referans yerine değerin kendisini almak:

rust
fn main() {
    let mut v = vec![1, 2, 3];

    // Çözüm 1: okumayı değişiklikten önce bitirin
    let ilk = &v[0];
    println!("ilk eleman: {ilk}");
    v.push(4);

    // Çözüm 2: referans yerine değeri kopyalayın (i32 Copy'dir)
    let ilk_deger = v[0];
    v.push(5);
    println!("ilk değer: {ilk_deger}, uzunluk: {}", v.len());
}
// çıktı: ilk eleman: 1
// çıktı: ilk değer: 1, uzunluk: 5

Vektör kullanımının geri kalanı için Vec kullanımı yazısına bakabilirsiniz.

Döngü içinde koleksiyonu değiştirmek

Bir koleksiyonu for x in &v ile dolaşırken aynı koleksiyona eleman eklemek de E0502 verir, çünkü döngü boyunca v paylaşılan olarak ödünç alınmıştır:

rust
fn main() {
    let mut v = vec![1, 2, 3];
    for x in &v {
        if *x % 2 == 1 {
            v.push(*x * 10);
        }
    }
    println!("{v:?}");
}
// error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable

Rust 1.99.1 burada iki yol önerir: indeks tabanlı bir döngü kullanmak veya değişiklikleri ayrı bir koleksiyonda toplamak. İkisini de denedik. İndeksli döngüde 0..w.len() aralığı döngü başında bir kez hesaplanır, bu yüzden yeni eklenen elemanlar dolaşılmaz:

rust
fn main() {
    let mut v = vec![1, 2, 3];
    let eklenecek: Vec<i32> = v.iter().filter(|x| *x % 2 == 1).map(|x| x * 10).collect();
    v.extend(eklenecek);
    println!("{v:?}");

    // Alternatif: indeksle dolaşmak (döngü sınırı başta sabitlenir)
    let mut w = vec![1, 2, 3];
    for i in 0..w.len() {
        if w[i] % 2 == 1 {
            let yeni = w[i] * 10;
            w.push(yeni);
        }
    }
    println!("{w:?}");
}
// çıktı: [1, 2, 3, 10, 30]
// çıktı: [1, 2, 3, 10, 30]

Okunabilirlik açısından birinci yol genellikle daha açıktır: önce neyin ekleneceğini hesaplarsınız, sonra tek seferde eklersiniz. Iterator zincirleri için iterator, map, filter ve collect yazısına bakabilirsiniz.

&amp;mut parametre ile çalışan fonksiyonlarda

Hata kodu dizinindeki örnek, değiştirilebilir bir referans parametresinin önce paylaşılan olarak ödünç alınması, sonra başka bir fonksiyona değiştirilebilir olarak verilmesidir. Mesajdaki *a ifadesine dikkat edin: ödünç alınan şey referansın gösterdiği değerdir:

rust
fn ikiye_katla(x: &mut i32) {
    *x *= 2;
}

fn islem(a: &mut i32) {
    let y = &a;
    ikiye_katla(a);
    println!("{}", y);
}

fn main() {
    let mut n = 5;
    islem(&mut n);
}
// error[E0502]: cannot borrow `*a` as mutable because it is also borrowed as immutable

Çözüm yine sıralamadır: değiştirme işlemini önce yapın, paylaşılan referansı sonra alın.

rust
fn ikiye_katla(x: &mut i32) {
    *x *= 2;
}

fn islem(a: &mut i32) {
    ikiye_katla(a);
    let y = &a;
    println!("{}", y);
}

fn main() {
    let mut n = 5;
    islem(&mut n);
    println!("{n}");
}
// çıktı: 10
// çıktı: 10

E0506: ödünç alınmış değere atama yapmak

E0506: ödünç alınmış değere atama yapmak

E0506, bir değere referans hâlâ kullanılacakken o değişkene yeni bir değer atadığınızda çıkar. Atama eski değeri bıraktığı için referans geçersiz kalırdı:

rust
fn main() {
    let mut puan = 10;
    let r = &puan;
    puan = 20;
    println!("{r} {puan}");
}
// error[E0506]: cannot assign to `puan` because it is borrowed

Mesaj `puan` is borrowed here, `puan` is assigned to here but it was already borrowed ve borrow later used here etiketlerini taşır. Hata kodu dizini üç çözüm gösterir: referansı bir blok içine alarak ömrünü kısaltmak, referansı bir fonksiyona vererek ödünç almayı fonksiyon çağrısıyla sınırlamak veya eski değeri başka bir değişkene taşıyıp sonra yeni değeri atamak. Blok yöntemiyle:

rust
fn main() {
    let mut puan = 10;
    {
        let r = &puan;
        println!("eski: {r}");
    }
    puan = 20;
    println!("yeni: {puan}");
}
// çıktı: eski: 10
// çıktı: yeni: 20

Metotlar neden alanlardan daha katı?

Bir struct'ın iki farklı alanına aynı anda değiştirilebilir referans almak mümkündür, çünkü derleyici iki ayrı bellek yerini ödünç aldığınızı görür. Ama aynı işi &mut self alan metotlarla yapmaya çalışırsanız E0499 alırsınız:

rust
struct Oyuncu {
    can: u32,
    puan: u32,
}

impl Oyuncu {
    fn can_mut(&mut self) -> &mut u32 {
        &mut self.can
    }
    fn puan_mut(&mut self) -> &mut u32 {
        &mut self.puan
    }
}

fn main() {
    let mut o = Oyuncu { can: 100, puan: 0 };
    let c = o.can_mut();
    let p = o.puan_mut();
    *c -= 10;
    *p += 5;
    println!("{} {}", o.can, o.puan);
}
// error[E0499]: cannot borrow `o` as mutable more than once at a time

Nedeni, derleyicinin bir metot çağrısını değerlendirirken metodun gövdesine değil imzasına bakmasıdır. fn can_mut(&mut self) -> &mut u32 imzası, dönen referans yaşadıkça bütün self değerinin değiştirilebilir olarak ödünç alındığını söyler. Metodun yalnızca tek bir alana dokunduğu imzadan anlaşılmaz. Aynı modül içindeyseniz alanlara doğrudan erişmek çözümdür:

rust
struct Oyuncu {
    can: u32,
    puan: u32,
}

fn main() {
    let mut o = Oyuncu { can: 100, puan: 0 };
    // Alanlara doğrudan erişince derleyici iki ayrı yeri ödünç aldığınızı görür
    let c = &mut o.can;
    let p = &mut o.puan;
    *c -= 10;
    *p += 5;
    println!("{} {}", o.can, o.puan);
}
// çıktı: 90 5

Alanlar dışarıdan gizliyse, iki alana birden değiştirilebilir referans döndüren tek bir metot yazmak (örneğin bir tuple döndürmek) ya da işlemi struct'ın içine taşımak yaygın çözümlerdir. Struct ve metot tanımlama için struct ve metotlar yazısına bakabilirsiniz.

Mini örnek: bulunan elemanı tutarken listeyi büyütmek

Gerçek kodda E0502 çoğu zaman bir arama sonucuyla birlikte gelir. Aşağıda bir görev listesinde find ile bir görev aranıyor; bulunursa listeye yeni bir görev ekleniyor ve ardından bulunan görevin adı yazdırılıyor. find listedeki elemana bir referans döndürdüğü için g yaşadıkça liste paylaşılan olarak ödünç alınmış sayılır:

rust
struct Gorev {
    ad: String,
}

fn main() {
    let mut liste = vec![Gorev { ad: String::from("yedek al") }, Gorev { ad: String::from("güncelle") }];
    let hedef = liste.iter().find(|g| g.ad == "yedek al");
    if let Some(g) = hedef {
        liste.push(Gorev { ad: String::from("kontrol et") });
        println!("bulunan: {}", g.ad);
    }
}
// error[E0502]: cannot borrow `liste` as mutable because it is also borrowed as immutable

Buradaki tehlike gerçektir: push vektörün belleğini yeniden ayırırsa g eski konumu gösterirdi. Çözümün fikri, elemanın kendisi yerine konumunu hatırlamaktır. position metodu bir referans değil usize türünde bir indeks döndürür; arama bittiği anda ödünç alma da biter. Eleman, ekleme yapıldıktan sonra indeksle yeniden okunur:

rust
struct Gorev {
    ad: String,
}

fn main() {
    let mut liste = vec![Gorev { ad: String::from("yedek al") }, Gorev { ad: String::from("güncelle") }];
    // position referans değil usize döndürür; ödünç alma hemen biter
    if let Some(i) = liste.iter().position(|g| g.ad == "yedek al") {
        liste.push(Gorev { ad: String::from("kontrol et") });
        println!("bulunan: {}", liste[i].ad);
    }
    println!("toplam: {}", liste.len());
}
// çıktı: bulunan: yedek al
// çıktı: toplam: 3

Bu desen yalnızca araya giren işlem indeksleri kaydırmıyorsa güvenlidir. Sona eleman eklemek mevcut indeksleri değiştirmez, ama ortadan eleman silmek veya başa eklemek değiştirir; öyle durumlarda gerekli değeri (örneğin adın bir kopyasını) değişiklikten önce almak daha sağlam bir yoldur.

Aynı vektörden iki &amp;mut: split_at_mut

Bir dizinin iki farklı elemanına aynı anda değiştirilebilir referans almak da E0499 verir. Derleyici indekslerin farklı olduğunu kanıtlayamaz; v[0] ve v[3] onun için aynı vektörün iki ödünç alınmasıdır:

rust
fn main() {
    let mut v = vec![1, 2, 3, 4];
    let a = &mut v[0];
    let b = &mut v[3];
    std::mem::swap(a, b);
    println!("{v:?}");
}
// error[E0499]: cannot borrow `v` as mutable more than once at a time

Rust 1.99.1 bu durumda doğrudan bir yardım satırı ekler: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices. split_at_mut, dilimi verilen konumdan çakışmayan iki değiştirilebilir parçaya ayırır; derleyici artık iki ayrı dilimle çalıştığınızı bilir. Yalnızca iki elemanın yerini değiştirmek istiyorsanız dilimlerin swap metodu daha da basittir:

rust
fn main() {
    let mut v = vec![1, 2, 3, 4];
    let (sol, sag) = v.split_at_mut(2);
    std::mem::swap(&mut sol[0], &mut sag[1]);
    println!("{v:?}");

    // Yalnızca iki elemanı değiştirmek için hazır metot da var
    v.swap(1, 2);
    println!("{v:?}");
}
// çıktı: [4, 2, 3, 1]
// çıktı: [4, 3, 2, 1]

Elemanların hepsini sırayla değiştirmek istiyorsanız iki değiştirilebilir referansa hiç ihtiyacınız yoktur. iter_mut her adımda tek bir elemana &mut verir ve bir sonraki adıma geçmeden önce o referans biter. Komşu elemanlar üzerinde çalışmak gerekiyorsa dilimlerin chunks_mut metodu, birbiriyle çakışmayan değiştirilebilir parçalar üretir; böylece her parçanın içinde istediğiniz kadar değişiklik yapabilirsiniz. İki yöntem de indeksle tek tek erişmekten hem daha kısa hem de derleyici açısından daha açık bir yazımdır:

rust
fn main() {
    let mut fiyatlar = vec![100, 250, 40];
    for f in fiyatlar.iter_mut() {
        *f += *f / 10;
    }
    println!("{fiyatlar:?}");

    // Komşu elemanlarla çalışmak için chunks_mut çakışmayan parçalar verir
    let mut v = [1, 2, 3, 4, 5, 6];
    for parca in v.chunks_mut(2) {
        parca.swap(0, 1);
    }
    println!("{v:?}");
}
// çıktı: [110, 275, 44]
// çıktı: [2, 1, 4, 3, 6, 5]

HashMap ve tek adımda güncelleme

HashMap ile çalışırken önce değeri okuyup sonra aynı haritaya yazan kodlar kolayca ödünç alma çakışmalarına yol açar. Standart kütüphanenin entry API'si okuma ve yazmayı tek bir değiştirilebilir ödünç almada birleştirir; anahtar yoksa varsayılan değeri ekler ve değere değiştirilebilir referans döndürür. Aşağıdaki sayaç bu yüzden hiçbir çakışma olmadan çalışır. Ayrıntılar HashMap kullanımı yazısında:

rust
use std::collections::HashMap;

fn main() {
    let mut stok: HashMap<&str, u32> = HashMap::new();
    for urun in ["elma", "armut", "elma"] {
        *stok.entry(urun).or_insert(0) += 1;
    }
    let mut anahtarlar: Vec<_> = stok.iter().collect();
    anahtarlar.sort();
    println!("{anahtarlar:?}");
}
// çıktı: [("armut", 1), ("elma", 2)]

Yakın akraba: E0596 ve çalışma anındaki RefCell paniği

Değişkeni mut olarak tanımlamayı unutup &mut almaya çalışmak farklı bir hatadır: error[E0596]: cannot borrow `liste` as mutable, as it is not declared as mutable. Çözümü basittir: let mut liste yazmak. Bu hata çakışma değil, izin eksikliği bildirir.

Kurallardan kaçmak için RefCell kullanmak ise denetimi ortadan kaldırmaz, çalışma anına taşır. Aynı kural ihlali derleme hatası yerine paniğe dönüşür:

rust
use std::cell::RefCell;

fn main() {
    let liste = RefCell::new(vec![1, 2]);
    let okuyucu = liste.borrow();
    println!("uzunluk: {}", okuyucu.len());
    liste.borrow_mut().push(3);
    println!("{:?}", okuyucu);
}
// çıktı: uzunluk: 2
// panic: RefCell already borrowed

RefCell paylaşılan sahiplik gerektiren yapılarda doğru araçtır, ama ödünç alma hatasını susturmanın kısa yolu olarak kullanılırsa hatayı yalnızca daha geç ve daha pahalı bir ana erteler. Ne zaman uygun olduğunu Rc ve RefCell yazısında bulabilirsiniz.

Hızlı karar tablosu

Hızlı karar tablosu: E0499, E0502, E0506, E0596
HataMesajın özüTipik nedenİlk denenecek çözüm
E0499cannot borrow as mutable more than once at a timeİki &mut kullanımı iç içeKullanımları sıraya koymak, split_at_mut, alanlara doğrudan erişim
E0502cannot borrow as mutable because it is also borrowed as immutableReferans tutarken koleksiyonu değiştirmekOkumayı önce bitirmek, değeri kopyalamak, değişiklikleri ayrı toplamak
E0506cannot assign to ... because it is borrowedReferans yaşarken yeni değer atamakReferansın kapsamını daraltmak
E0596cannot borrow ... as mutable, as it is not declared as mutablemut unutulmuşlet mut yazmak
E0505cannot move out of ... because it is borrowedÖdünç alınmış değeri taşımakTaşımayı son kullanımdan sonraya almak

Sık yapılan hatalar

  • Referansı gereğinden uzun tutmak: Bir elemana referansı fonksiyonun başında alıp en sonda kullanmak, aradaki tüm değişiklikleri engeller. Referansı ihtiyaç duyduğunuz yere yakın alın.
  • Her şeyi clone ile çözmek: Okuma ile yazma arasındaki çakışmayı kopya alarak çözebilirsiniz, ama çoğu zaman sıralamayı değiştirmek yeterli ve daha ucuzdur.
  • Getter metotlarıyla alan ödünç almak: &mut self alan iki getter'ı aynı anda kullanamazsınız; alanlara doğrudan erişin veya ikisini birden döndüren tek bir metot yazın.
  • İndekslerin farklı olduğunu derleyicinin bileceğini sanmak: Derleyici v[i] ve v[j] için farklılık kanıtlamaz; split_at_mut veya swap kullanın.
  • RefCell ile hatayı ertelemek: Derleme hatasını paniğe çevirmek gerçek bir çözüm değildir.

Sık Sorulan Sorular

Tek iş parçacıklı kodda bu kurala neden ihtiyaç var?

Çünkü sorun yalnızca iş parçacıkları değildir. Vektör örneğinde olduğu gibi bir değişiklik, mevcut referansların gösterdiği belleği geçersiz kılabilir. Kural bu tür hataları tek iş parçacığında da önler.

Değişkeni süslü parantez sonuna kadar kullanmıyorum, yine de hata alıyorum. Neden?

Derleyici referansın son kullanımına bakar. Mesajdaki later used here etiketi o son kullanımı gösterir. Bazen bu kullanım gözden kaçan bir println! veya döngünün kendisidir; örneğin for x in &v döngüsü boyunca v ödünç alınmış sayılır.

E0499 ile E0502 arasındaki fark nedir?

E0499 iki değiştirilebilir referansın çakışmasıdır. E0502 ise biri paylaşılan, diğeri değiştirilebilir iki referansın çakışmasıdır. İkisinin de çözümü çakışan kullanımları zamanda ayırmaktır.

Hata açıklamalarını nereden okuyabilirim?

Derleyicinin sonundaki For more information about this error, try `rustc --explain E0502`. satırında yazan komutu çalıştırabilir veya Rust Compiler Error Index sayfasını kullanabilirsiniz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Rust öğrenme rehberi · Referans ve borrowing · E0382 move hataları · Lifetime hataları · Rc ve RefCell · Vec kullanımı