Rust'ta error[E0382]: borrow of moved value hatası, sahipliği başka bir değişkene, fonksiyona veya closure'a taşınmış (move edilmiş) bir değeri sonradan kullanmaya çalıştığınızı söyler. Çözüm, değerin gerçekten taşınması gerekip gerekmediğine bağlıdır: yalnızca okumak yetiyorsa & ile ödünç verirsiniz, bağımsız bir kopya gerekiyorsa .clone() çağırırsınız, tip küçük ve basitse Copy türetirsiniz, değeri birden çok yerin sahiplenmesi gerekiyorsa Rc kullanırsınız ya da kodu taşımanın kullanımdan sonra gerçekleşeceği şekilde yeniden sıralarsınız.
Bu yazıda E0382'yi ve ona çok yakın iki move hatasını, E0505'i (ödünç alınmışken taşıma) ve E0507'yi (referansın arkasından taşıma) Rust 1.99.1'in gerçek mesajlarıyla inceliyoruz. Sahiplik kurallarının kendisi Rust ownership kuralları yazısının, referanslar ise referans ve borrowing yazısının konusu; burada hatayı görünce ne yapacağınıza odaklanıyoruz. Aynı anda ödünç alma çakışmaları için E0499 ve E0502 hataları yazısına bakabilirsiniz.
Hata mesajı ne söylüyor?

En kısa örnekle başlayalım. Bir String başka bir değişkene atanıyor, ardından ilk değişken yazdırılıyor:
fn main() {
let s1 = String::from("merhaba");
let s2 = s1;
println!("{s1} {s2}");
}
// error[E0382]: borrow of moved value: `s1`Derleyici mesajı üç işaretli satır içerir ve hatayı çözmek için gereken her şeyi söyler. Birinci etiket, s1 değişkeninin String tipinde olduğunu ve bu tipin Copy trait'ini uygulamadığını, dolayısıyla atamada kopyalanmak yerine taşınacağını belirtir. İkinci etiket, taşımanın gerçekleştiği yeri (let s2 = s1;) gösterir. Üçüncüsü ise taşımadan sonra değerin ödünç alındığı yeri (println! içindeki s1) işaret eder. Mesajın sonunda bir de help: consider cloning the value if the performance cost is acceptable önerisi bulunur.
Mesajın başındaki fiil de bir ipucudur. borrow of moved value, taşınmış değeri referans alarak kullandığınızda (örneğin println! veya .len() çağrısı) çıkar. use of moved value ise taşınmış değeri bir kez daha taşımaya çalıştığınızda, örneğin aynı değişkeni ikinci kez sahiplik alan bir fonksiyona verdiğinizde görülür. İkisi de aynı E0382 kodunu taşır ve aynı yollarla çözülür. Herhangi bir hata kodunun açıklamasını yerel olarak okumak için rustc --explain E0382 komutunu kullanabilirsiniz; çıktının ilk satırı "A variable was used after its contents have been moved elsewhere." şeklindedir.
Rust neden değeri taşıyor?
The Rust Programming Language kitabındaki sahiplik kuralları kısaca şunu söyler: her değerin tek bir sahibi vardır ve sahip kapsamdan çıktığında değer bırakılır. Bir String yığında (stack) üç parça tutar: heap'teki veriye işaretçi, uzunluk ve kapasite. let s2 = s1; yalnızca bu üç parçayı kopyalasaydı, iki değişken aynı heap bölgesini gösterirdi ve ikisi kapsamdan çıkarken aynı belleği iki kez serbest bırakmaya çalışırdı. Rust bunu önlemek için atamadan sonra s1 değişkenini geçersiz sayar. Kitap bu işleme sığ kopya yerine move der.
Tamsayılar, bool, char, kayan noktalı sayılar ve yalnızca bu tiplerden oluşan tuple'lar gibi boyutu derleme anında bilinen ve tamamen yığında duran tipler Copy trait'ini uygular. Bu tiplerde atama değeri kopyalar, eski değişken geçerli kalır ve E0382 hiç oluşmaz. Hatayı neredeyse her zaman String, Vec<T>, Box<T> veya bunları içeren kendi struct'larınızda görürsünüz.
E0382'nin en sık görüldüğü beş durum

1. Değeri sahiplik alan bir fonksiyona vermek
Fonksiyon parametresi String gibi sahipli bir tipse, çağrı değeri fonksiyona taşır. Çağrıdan sonra değişkeni kullanamazsınız:
fn uzunluk(s: String) -> usize {
s.len()
}
fn main() {
let ad = String::from("Ayşe");
let n = uzunluk(ad);
println!("{ad}: {n} bayt");
}
// error[E0382]: borrow of moved value: `ad`Fonksiyon değeri yalnızca okuyorsa sahipliğe ihtiyacı yoktur. Parametreyi &str yapıp çağrıda &ad verdiğinizde değer ödünç verilir ve çağrıdan sonra hâlâ sizindir. Çıktıdaki 5 bayt, "ş" harfinin UTF-8'de iki bayt tutmasından gelir; bu konu String ve &str farkı yazısında ayrıntılı.
fn uzunluk(s: &str) -> usize {
s.len()
}
fn main() {
let ad = String::from("Ayşe");
let n = uzunluk(&ad);
println!("{ad}: {n} bayt");
}
// çıktı: Ayşe: 5 bayt2. Döngü içinde taşımak
Döngünün ilk turunda değer taşınır, ikinci turda artık yoktur. Derleyici bunu özel bir ifadeyle belirtir: value moved here, in previous iteration of loop. Mesajın kökü bu kez use of moved value olur, çünkü değer ikinci kez taşınmaya çalışılıyor:
fn kaydet(satirlar: Vec<String>) {
println!("{} satır kaydedildi", satirlar.len());
}
fn main() {
let satirlar = vec![String::from("a"), String::from("b")];
for _ in 0..2 {
kaydet(satirlar);
}
}
// error[E0382]: use of moved value: `satirlar`Rust 1.99.1 burada ayrıca bir not ekler: sahiplik gerekmiyorsa kaydet fonksiyonunun parametre tipini ödünç alacak şekilde değiştirmeyi önerir. Bu öneriyi uyguladığımızda, yani parametreyi &[String] yapıp çağrıda &satirlar verdiğimizde, kod iki kez 2 satır kaydedildi yazdırdı. &Vec<String> yerine dilim almak fonksiyonu daha esnek kılar; dilimler için slice ve str dilimleri yazısına bakabilirsiniz.
3. for döngüsünün koleksiyonu tüketmesi
Bu durum en çok şaşırtanıdır, çünkü kodda açık bir atama yoktur:
fn main() {
let sayilar = vec![1, 2, 3];
for n in sayilar {
println!("{n}");
}
println!("toplam eleman: {}", sayilar.len());
}
// error[E0382]: borrow of moved value: `sayilar`Derleyicinin notu nedeni açıklar: for döngüsü arka planda IntoIterator::into_iter çağrısına dönüşür ve bu metot self değerini sahiplenir; yani vektör döngüye taşınır. Yardım satırı da çözümü verir: vektörün içeriğini dilim üzerinden dolaşmak için for n in &sayilar yazın. Bu durumda n değişkeninin tipi &i32 olur ve vektör döngüden sonra kullanılabilir:
fn main() {
let sayilar = vec![1, 2, 3];
for n in &sayilar {
println!("{n}");
}
println!("toplam eleman: {}", sayilar.len());
}
// çıktı: 1
// çıktı: 2
// çıktı: 3
// çıktı: toplam eleman: 3Elemanları değiştirmeniz gerekiyorsa for n in &mut sayilar, koleksiyonla işiniz bittiyse ve elemanları sahiplenmek istiyorsanız ilk yazım doğrudur. Iterator türleri arasındaki fark iterator, map, filter ve collect yazısında anlatılıyor.
4. Struct alanını dışarı taşımak (kısmi move)
Bir struct'ın sahipli bir alanını dışarı aldığınızda struct kısmen taşınmış olur. Taşınmayan alanlar (burada Copy olan yas) hâlâ kullanılabilir, ama struct'ın tamamı artık kullanılamaz:
#[derive(Debug)]
struct Kisi {
ad: String,
yas: u8,
}
fn main() {
let k = Kisi { ad: String::from("Mert"), yas: 30 };
let ad = k.ad;
println!("{ad} {}", k.yas);
println!("{:?}", k);
}
// error[E0382]: borrow of partially moved value: `k`Alanı yalnızca okumak istiyorsanız &k.ad ile ödünç alın. Struct ile işiniz bittiyse onu desenle tamamen parçalayıp alanları ayrı değişkenler olarak kullanmak da temiz bir yoldur:
#[derive(Debug)]
struct Kisi {
ad: String,
yas: u8,
}
fn main() {
let k = Kisi { ad: String::from("Mert"), yas: 30 };
let ad = &k.ad;
println!("{ad} {}", k.yas);
println!("{:?}", k);
// Yapıyı tamamen parçalamak da bir seçenek: artık k yerine parçaları kullanırsınız
let Kisi { ad, yas } = k;
println!("{ad} / {yas}");
}
// çıktı: Mert 30
// çıktı: Kisi { ad: "Mert", yas: 30 }
// çıktı: Mert / 305. move closure'a vermek
move anahtar kelimesi closure'ın kullandığı değişkenlerin sahipliğini almasını sağlar. Bu, değeri bir iş parçacığına göndermek için gereklidir ama closure'dan sonra değişkeni kullanamazsınız:
fn main() {
let mesaj = String::from("selam");
let gonder = move || println!("gönderildi: {mesaj}");
gonder();
println!("{mesaj}");
}
// error[E0382]: borrow of moved value: `mesaj`Derleyici closure'a taşımadan önce bir kopya oluşturmayı önerir. Closure değeri yalnızca okuyorsa move yazmanız gerekmez; closure değişkeni ödünç alır:
fn main() {
let mesaj = String::from("selam");
let kopya = mesaj.clone();
let gonder = move || println!("gönderildi: {kopya}");
gonder();
println!("{mesaj}");
// Closure yalnızca okuyorsa move gerekmez: ödünç alır
let oku = || println!("okundu: {mesaj}");
oku();
println!("{mesaj}");
}
// çıktı: gönderildi: selam
// çıktı: selam
// çıktı: okundu: selam
// çıktı: selamClosure'ların değer yakalama biçimleri ve Fn, FnMut, FnOnce farkı için Rust closure, iş parçacıklarına veri taşımak için thread::spawn ve join yazılarına bakabilirsiniz.
Çözüm yolları karşılaştırması
| Yöntem | Ne zaman uygun | Bedeli |
|---|---|---|
| & veya &mut ile ödünç vermek | Alıcı yalnızca okuyor veya geçici olarak değiştiriyor | Yok; ödünç alma kuralları geçerli |
| .clone() | İki bağımsız kopya gerçekten gerekiyor | Heap verisi kopyalanır, bellek ve zaman harcar |
| #[derive(Clone, Copy)] | Tipin tüm alanları Copy (ör. yalnızca sayılar) | Yok; String veya Vec içeren tipte mümkün değil |
| Rc<T> | Tek iş parçacığında birden çok sahip | Referans sayacı; değer varsayılan olarak salt okunur |
| Sırayı değiştirmek | Taşıma, son kullanımdan sonra yapılabiliyor | Yok |
| Sahipliği geri döndürmek | Fonksiyon değeri işleyip geri verebiliyor | İmza değişir |
| mem::take veya Option::take | Değeri bir alandan çıkarıp yerine varsayılan bırakmak | Alan boş kalır, bunu kod hesaba katmalı |
Copy ve clone arasındaki farkı tek örnekte görebilirsiniz. Yalnızca iki tamsayıdan oluşan Nokta tipi Copy türettiği için atamadan sonra iki değişken de geçerlidir. String ise Copy olamaz; açık bir clone çağrısı bağımsız bir kopya üretir ve kopya üzerindeki değişiklik aslını etkilemez:
#[derive(Debug, Clone, Copy)]
struct Nokta {
x: i32,
y: i32,
}
fn main() {
let a = Nokta { x: 1, y: 2 };
let b = a;
println!("{a:?} {b:?} {}", a.x + b.y);
let s1 = String::from("çok");
let mut s2 = s1.clone();
s2.push_str(" güzel");
println!("{s1} / {s2}");
}
// çıktı: Nokta { x: 1, y: 2 } Nokta { x: 1, y: 2 } 3
// çıktı: çok / çok güzelBir değerin gerçekten birden çok sahibi olması gerekiyorsa, örneğin aynı yapılandırma nesnesi birkaç modül tarafından tutuluyorsa, Rc kullanabilirsiniz. Rc::clone veriyi kopyalamaz, yalnızca referans sayacını artırır. Örnekte sayaç, asıl değişken ve iki kopya için 3 gösteriyor:
use std::rc::Rc;
struct Ayar {
ad: String,
}
fn main() {
let ayar = Rc::new(Ayar { ad: String::from("üretim") });
let modul_a = Rc::clone(&ayar);
let modul_b = Rc::clone(&ayar);
println!("{} {} {}", modul_a.ad, modul_b.ad, Rc::strong_count(&ayar));
}
// çıktı: üretim üretim 3Tablodaki sahipliği geri döndürme seçeneği kitabın sahiplik bölümünde de gösterilir: fonksiyon değeri alır, işini yapar ve sonucu bir tuple içinde geri verir. Çağıran, aynı adı gölgeleyerek (shadowing) yeni değeri kullanmaya devam eder. Bu yöntem işe yarar ama kitabın da belirttiği gibi her fonksiyon için zahmetlidir; değeri yalnızca okuyan fonksiyonlar için referans almak çok daha sade bir imza verir. Yine de fonksiyon değeri dönüştürüyorsa, örneğin büyük harfe çeviriyorsa, değeri alıp yenisini döndürmek doğal bir tasarımdır:
fn buyuk_harf(s: String) -> (String, usize) {
let n = s.chars().count();
(s.to_uppercase(), n)
}
fn main() {
let ad = String::from("ankara");
let (ad, uzunluk) = buyuk_harf(ad);
println!("{ad} {uzunluk}");
}
// çıktı: ANKARA 6Hangi yolu seçeceğinize karar verirken şu sırayı izlemek işinizi kolaylaştırır. Önce alıcı tarafın değere sahip olması gerekip gerekmediğini sorun; gerekmiyorsa parametreyi referans yapın. Gerekiyorsa ve sizin de değeri sonradan kullanmanız gerekiyorsa, kullanımları taşımadan önceye almayı deneyin. Bu da mümkün değilse kopyalamanın maliyetini düşünün: küçük bir değerde clone sorun değildir, büyük bir koleksiyonda ise paylaşımlı sahiplik daha uygun olabilir.
Rc yalnızca tek iş parçacığında kullanılabilir ve içindeki değeri değiştirmek için RefCell gerekir. Bu ikiliyi Rc ve RefCell, iş parçacıkları arasında paylaşımı Arc ve Mutex yazısında bulabilirsiniz.
E0505: ödünç alınmış değeri taşımak
E0505, bir değer hâlâ ödünç alınmışken onu taşımaya çalıştığınızda çıkar. Aşağıda r referansı metin değişkenini gösteriyor; değer tuket fonksiyonuna taşınıyor ve ardından r kullanılıyor. Taşıma gerçekleşseydi r artık var olmayan bir değeri gösterirdi:
fn tuket(s: String) {
println!("tüketildi: {s}");
}
fn main() {
let metin = String::from("rapor");
let r = &metin;
tuket(metin);
println!("{r}");
}
// error[E0505]: cannot move out of `metin` because it is borrowedHata kodu dizini üç çözüm sayar: taşımaktan kaçınmak (fonksiyona referans vermek), taşımadan önce ödünç almayı bitirmek veya tipe Copy uygulamak. Çoğu zaman en basiti ikincisidir. Derleyici referansın son kullanıldığı yeri esas aldığı için, r ile işinizi taşımadan önce bitirmeniz yeterlidir:
fn tuket(s: String) {
println!("tüketildi: {s}");
}
fn main() {
let metin = String::from("rapor");
let r = &metin;
println!("{r}");
// r artık kullanılmıyor, taşımak serbest
tuket(metin);
}
// çıktı: rapor
// çıktı: tüketildi: raporE0507: referansın arkasından taşımak
E0507, yalnızca ödünç aldığınız bir şeyin içinden sahipli bir değeri dışarı almaya çalıştığınızda çıkar. Referans size değeri kullanma hakkı verir, sahipliği değil. Tipik örnek, bir vektörün elemanına referans alıp onun alanını taşımaktır:
struct Kisi {
ad: String,
}
fn main() {
let liste = vec![Kisi { ad: String::from("Ece") }];
let ilk = &liste[0];
let ad = ilk.ad;
println!("{ad}");
}
// error[E0507]: cannot move out of `ilk.ad` which is behind a shared referenceAynı hata &mut self alan bir metotta alanı doğrudan döndürmeye çalıştığınızda da görülür; mesaj bu kez which is behind a mutable reference diye biter. Değiştirilebilir referans da sahiplik vermez: değeri dışarı alıp yerini boş bırakamazsınız.
struct Oturum {
belirtec: Option<String>,
}
impl Oturum {
fn kapat(&mut self) -> Option<String> {
self.belirtec
}
}
fn main() {
let mut o = Oturum { belirtec: Some(String::from("abc123")) };
println!("{:?}", o.kapat());
}
// error[E0507]: cannot move out of `self.belirtec` which is behind a mutable referenceÇözüm, ne istediğinize bağlıdır. Bir kopya yetiyorsa clone, okumak yetiyorsa referans, değeri gerçekten dışarı almak istiyorsanız yerine bir şey bırakan yöntemler kullanılır. Hata kodu dizini bunun için mem::replace fonksiyonunu gösterir; mem::take aynı işi tipin varsayılan değerini bırakarak yapar. Option alanları için take() metodu değeri alır ve yerine None koyar:
struct Oturum {
belirtec: Option<String>,
}
impl Oturum {
fn kapat(&mut self) -> Option<String> {
self.belirtec.take()
}
}
fn main() {
let mut o = Oturum { belirtec: Some(String::from("abc123")) };
println!("{:?}", o.kapat());
println!("{:?}", o.kapat());
}
// çıktı: Some("abc123")
// çıktı: NoneVektör örneğindeki dört farklı çözümü bir arada görmek için:
struct Kisi {
ad: String,
}
fn main() {
let mut liste = vec![Kisi { ad: String::from("Ece") }, Kisi { ad: String::from("Can") }];
// 1) Kopya almak
let kopya = liste[0].ad.clone();
// 2) Yalnızca ödünç almak
let ad_ref: &str = &liste[1].ad;
println!("{kopya} {ad_ref}");
// 3) Yerine boş bir değer bırakıp sahipliği almak
let alinan = std::mem::take(&mut liste[0].ad);
println!("{alinan:?} / kalan: {:?}", liste[0].ad);
// 4) Vec'ten elemanı tamamen çıkarmak
let son = liste.pop().unwrap();
println!("{} çıkarıldı, {} eleman kaldı", son.ad, liste.len());
}
// çıktı: Ece Can
// çıktı: "Ece" / kalan: ""
// çıktı: Can çıkarıldı, 1 eleman kaldıE0507'nin sinsi bir biçimi de &self içindeki bir Option alanında unwrap() çağırmaktır. unwrap değeri sahiplendiği için alanı taşımaya çalışır. Rust 1.99.1 bu durumda .as_ref() çağırmayı önerir; String içeren bir Option için as_deref() doğrudan Option<&str> verir:
struct Profil {
eposta: Option<String>,
}
impl Profil {
fn alan_adi(&self) -> &str {
match self.eposta.as_deref() {
Some(e) => e.split('@').nth(1).unwrap_or(""),
None => "",
}
}
}
fn main() {
let p = Profil { eposta: Some(String::from("ali@ornek.com")) };
println!("{}", p.alan_adi());
let bos = Profil { eposta: None };
println!("{:?}", bos.alan_adi());
}
// self.eposta.unwrap() yazılsaydı:
// error[E0507]: cannot move out of `self.eposta` which is behind a shared reference
// çıktı: ornek.com
// çıktı: ""Hata kodu dizini ayrıca Fn veya FnMut closure'ların gövdesinden değer taşımanın da E0507 verdiğini not eder, çünkü bu closure'lar birden çok kez çağrılabilir ve ilk çağrıda taşınan değer ikinci çağrıda olmaz.
Sık yapılan hatalar

- Her E0382'yi clone ile kapatmak: Derleyicinin önerisi her zaman en iyi çözüm değildir. Önce alıcının sahipliğe gerçekten ihtiyacı olup olmadığını sorun; çoğu zaman bir referans yeterlidir.
- String içeren tipe Copy türetmeye çalışmak:
Copyancak tüm alanlarCopyise mümkündür. Böyle bir tipteClonetüretip açıkçaclone()çağırmanız gerekir. - Döngüde koleksiyonu tüketmek: Koleksiyonu döngüden sonra da kullanacaksanız
&vveyav.iter()ile dolaşın. - Option alanında unwrap çağırmak:
&selfiçindeas_ref()veyaas_deref(),&mut selfiçinde ve değeri almak istiyorsanıztake()kullanın. - Rc'yi iş parçacıkları arasında paylaşmaya çalışmak:
Rctek iş parçacığı içindir; çok iş parçacıklı paylaşım içinArcgerekir.
Sık Sorulan Sorular
borrow of moved value ile use of moved value aynı hata mı?
İkisi de E0382'dir. Fark, taşınmış değerin nasıl kullanıldığındadır: referans alınarak kullanılıyorsa borrow, yeniden taşınıyorsa use ifadesi çıkar. Çözümler aynıdır.
clone kullanmak kötü bir alışkanlık mı?
Hayır, iki bağımsız kopyaya gerçekten ihtiyacınız varsa doğru araç budur. Hata kodu dizini de bunu açıkça önerir. Sorun, yalnızca derleyiciyi susturmak için büyük verileri kopyalamaktır.
Tamsayılarda neden bu hatayı hiç görmüyorum?
Tamsayılar Copy trait'ini uygular. Atama veya fonksiyon çağrısı değeri kopyalar ve eski değişken geçerli kalır. Kendi tipiniz yalnızca Copy alanlardan oluşuyorsa #[derive(Clone, Copy)] ile aynı davranışı elde edersiniz.
E0505 ile E0502 arasındaki fark nedir?
E0505, ödünç alınmış bir değeri taşımaya çalıştığınızda çıkar. E0502 ise değer paylaşılan olarak ödünç alınmışken onu değiştirilebilir olarak ödünç almaya çalıştığınızda görülür. İkincisi için E0499 ve E0502 hataları yazısına bakın.
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 · Ownership kuralları · Referans ve borrowing · E0499 ve E0502 · Lifetime hataları · E0277 ve E0308 tip hataları