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:
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ünyaBu 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:
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 timeMesajda 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:
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 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:
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:
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: 5Vektö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:
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 immutableRust 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:
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.
&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:
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.
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ı: 10E0506: ö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ı:
fn main() {
let mut puan = 10;
let r = &puan;
puan = 20;
println!("{r} {puan}");
}
// error[E0506]: cannot assign to `puan` because it is borrowedMesaj `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:
fn main() {
let mut puan = 10;
{
let r = &puan;
println!("eski: {r}");
}
puan = 20;
println!("yeni: {puan}");
}
// çıktı: eski: 10
// çıktı: yeni: 20Metotlar 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:
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 timeNedeni, 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:
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 5Alanlar 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:
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 immutableBuradaki 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:
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: 3Bu 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 &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:
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 timeRust 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:
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:
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:
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:
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 borrowedRefCell 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

| Hata | Mesajın özü | Tipik neden | İlk denenecek çözüm |
|---|---|---|---|
| E0499 | cannot borrow as mutable more than once at a time | İki &mut kullanımı iç içe | Kullanımları sıraya koymak, split_at_mut, alanlara doğrudan erişim |
| E0502 | cannot borrow as mutable because it is also borrowed as immutable | Referans tutarken koleksiyonu değiştirmek | Okumayı önce bitirmek, değeri kopyalamak, değişiklikleri ayrı toplamak |
| E0506 | cannot assign to ... because it is borrowed | Referans yaşarken yeni değer atamak | Referansın kapsamını daraltmak |
| E0596 | cannot borrow ... as mutable, as it is not declared as mutable | mut unutulmuş | let mut yazmak |
| E0505 | cannot move out of ... because it is borrowed | Ödünç alınmış değeri taşımak | Taşı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 selfalan 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]vev[j]için farklılık kanıtlamaz;split_at_mutveyaswapkullanı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ı