Rust'ta ownership (sahiplik), belleği çöp toplayıcı olmadan ve elle free çağırmadan yöneten derleme zamanı kurallarıdır: her değerin tek bir sahibi vardır, sahip kapsamdan çıkınca değer düşürülür (drop) ve bir değeri başka bir değişkene ya da fonksiyona verdiğinizde sahiplik taşınır (move). Kurallar derleyici tarafından denetlenir; ihlal eden program derlenmez ve çalışma anında ek bir maliyet oluşmaz.
Bu yazı, Rust nedir? yazısındaki kısa özetin ötesine geçer: move ile kopyanın farkını, drop sırasını, kısmi taşımayı, Copy ile Drop çatışmasını ve std::mem::take gibi araçları çalışan örneklerle anlatır. Referanslarla ödünç alma konusu ayrı bir yazıda: Rust referans ve borrowing.
Stack, heap ve sahipliğin asıl işi
Sahiplik kurallarının neden böyle olduğunu anlamak için verinin nerede durduğunu bilmek gerekir. Stack, son giren ilk çıkar mantığıyla çalışır; buraya konan her verinin boyutu derleme zamanında bilinmeli ve sabit olmalıdır. Boyutu çalışma anında belli olan ya da büyüyüp küçülebilen veri heap üzerinde tutulur: ayırıcı (allocator) yeterince büyük boş bir alan bulur, işaretler ve size bir işaretçi döndürür. İşaretçinin kendisi sabit boyutlu olduğu için stack'te durabilir.
Stack'e veri koymak heap'te yer ayırmaktan hızlıdır, çünkü ayırıcının yer araması gerekmez. Heap'teki veriye erişmek de genellikle daha yavaştır, çünkü işaretçiyi takip etmek gerekir. The Rust Book, sahipliğin temel amacının heap verisini yönetmek olduğunu vurgular: hangi kodun hangi heap verisini kullandığını izlemek, gereksiz kopyaları azaltmak ve artık kullanılmayan veriyi temizlemek. i32 gibi küçük, sabit boyutlu tiplerde neden farklı davranış gördüğünüz de buradan gelir.
Üç sahiplik kuralı

- Rust'taki her değerin bir sahibi (owner) vardır.
- Aynı anda yalnızca bir sahip olabilir.
- Sahip kapsamdan çıktığında değer düşürülür (drop edilir).
Kapsam, bir öğenin geçerli olduğu program aralığıdır. Değişken tanımlandığı satırdan bulunduğu bloğun kapanış süslü parantezine kadar geçerlidir. String gibi heap kullanan bir tipte, kapanış parantezinde Rust otomatik olarak drop fonksiyonunu çağırır ve bellek ayırıcıya geri verilir. C++ dünyasında bu desene RAII denir. Kendi tipinize Drop trait'ini uygularak bu anı gözle görebilirsiniz:
struct Iz(&'static str);
impl Drop for Iz {
fn drop(&mut self) {
println!("drop: {}", self.0);
}
}
fn main() {
let _a = Iz("a");
let mut b = Iz("b1");
println!("b önce {}", b.0);
b = Iz("b2"); // eski değer (b1) atama anında düşürülür
println!("b şimdi {}", b.0);
{
let _c = Iz("c");
println!("iç kapsam bitiyor");
}
println!("main bitiyor");
}
// çıktı: b önce b1
// çıktı: drop: b1
// çıktı: b şimdi b2
// çıktı: iç kapsam bitiyor
// çıktı: drop: c
// çıktı: main bitiyor
// çıktı: drop: b2
// çıktı: drop: aÇıktıda üç ayrıntı var. Birincisi, var olan bir değişkene yeni değer atadığınızda eski değer hemen düşürülür; Rust Reference bunu atamanın sol taraftaki başlatılmış değerin yıkıcısını çalıştırması olarak tanımlar. İkincisi, iç blok bitince _c o anda temizlenir. Üçüncüsü, main sonunda değişkenler tanımlanma sırasının tersiyle düşürülür: önce b, sonra _a. Bir struct'ın alanları ise tanımlandıkları sırayla düşürülür.
String literal ile String arasındaki fark
Sahipliği göstermek için The Book String tipini seçer, çünkü string literal'lerden farklı davranır. "merhaba" gibi bir literal'in içeriği derleme zamanında bilinir ve doğrudan çalıştırılabilir dosyanın içine gömülür; bu yüzden hızlıdır ama değiştirilemez. Kullanıcıdan okunan bir metin gibi boyutu önceden bilinmeyen veri için ise String kullanılır. String::from çağrıldığında gereken bellek çalışma anında heap'ten istenir ve metin büyüyebilir, örneğin push_str ile sonuna ekleme yapılabilir.
Belleği istemek her dilde benzerdir; asıl fark geri verme kısmındadır. Çöp toplayıcılı dillerde bunu çalışma zamanı üstlenir. Çöp toplayıcı olmayan dillerde programcı her ayırma için tam bir serbest bırakma yazmak zorundadır: unutursa bellek boşa gider, erken yaparsa geçersiz bir değişken kalır, iki kez yaparsa yine hata olur. Rust üçüncü yolu seçer: bellek, sahibi olan değişken kapsamdan çıktığı anda otomatik olarak geri verilir.
Move: atama kopyalamak değildir
Bir String stack'te üç parçadan oluşur: heap'teki içeriği gösteren işaretçi, uzunluk ve kapasite. let s2 = s1; yazdığınızda bu üç değer kopyalanır, heap'teki metin kopyalanmaz. Eğer Rust iki değişkeni de geçerli sayısaydı, kapsam sonunda ikisi de aynı belleği serbest bırakmaya çalışırdı. Buna double free denir ve bellek bozulmasına yol açabilir. Rust bunu önlemek için atamadan sonra s1'i geçersiz sayar. Bu işleme move (taşıma) denir; sığ kopyaya benzer ama ilk değişken artık kullanılamaz.
fn main() {
let s1 = String::from("merhaba");
let s2 = s1;
println!("{s1}, dünya");
}
// error[E0382]: borrow of moved value: `s1`Derleyici hatanın yanında üç ipucu verir: s1'in tipi Copy trait'ini uygulamadığı için move olduğunu, değerin hangi satırda taşındığını ve taşındıktan sonra nerede kullanılmaya çalışıldığını gösterir. Ayrıca maliyeti kabul edilebilirse clone önerir. Bu hatanın tüm çeşitleri ve çözüm yolları için E0382 borrow of moved value rehberi yazısına bakabilirsiniz.
Bu tasarımın önemli bir sonucu var: Rust verinizin derin kopyasını hiçbir zaman kendiliğinden oluşturmaz. Kodda otomatik gerçekleşen her kopyanın ucuz olduğunu varsayabilirsiniz. Pahalı olabilecek bir kopya her zaman clone() çağrısıyla görünür hâle gelir.
Clone ve Copy farkı

Heap içeriğiyle birlikte gerçek bir kopya istiyorsanız clone çağırırsınız. Tamsayılar ise farklı davranır: let y = x; sonrasında x geçerliliğini korur, çünkü i32 tamamen stack'te duran, boyutu bilinen bir tiptir ve Copy trait'ini uygular. Böyle tiplerde sığ ve derin kopya arasında fark yoktur.
fn main() {
let s1 = String::from("merhaba");
let s2 = s1.clone(); // heap verisi de kopyalanır
println!("s1 = {s1}, s2 = {s2}");
let x = 5;
let y = x; // i32 Copy olduğu için x geçerli kalır
println!("x = {x}, y = {y}");
let t = (1, 'a');
let u = t; // içindeki her tip Copy olduğundan demet de Copy
println!("{t:?} {u:?}");
}
// çıktı: s1 = merhaba, s2 = merhaba
// çıktı: x = 5, y = 5
// çıktı: (1, 'a') (1, 'a')| Tip | Atamada ne olur | Not |
|---|---|---|
| u32, i64 gibi tüm tamsayılar | Copy | Değer kopyalanır, kaynak geçerli kalır |
| f64 gibi kayan noktalı sayılar | Copy | Aynı şekilde |
| bool ve char | Copy | Sabit boyutlu skaler tipler |
| (i32, i32) | Copy | Tüm elemanlar Copy ise demet de Copy |
| (i32, String) | Move | Bir eleman Copy değilse demet de değil |
| String, Vec<T>, Box<T> | Move | Heap kaynağı tutar, kopya için clone gerekir |
Bir tip veya parçalarından biri Drop uyguluyorsa ona Copy eklenemez. Kapsam sonunda özel bir iş yapması gereken bir değerin sessizce çoğaltılması anlamsız olurdu; derleyici bunu E0184 koduyla reddeder:
#[derive(Clone, Copy)]
struct Kaynak;
impl Drop for Kaynak {
fn drop(&mut self) {}
}
fn main() {}
// error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructorKendi struct'larınıza Copy ve Clone eklemenin ayrıntıları için Rust derive rehberi yazısına göz atın.
Fonksiyonlara değer verme ve geri alma
Fonksiyona argüman geçirmek, atamayla aynı kurallara uyar: Copy olmayan bir değer fonksiyona taşınır ve çağıran taraf artık onu kullanamaz. Fonksiyonun dönüş değeri de sahipliği çağırana taşır. Sahipliği geri almak için değeri demet içinde döndürmek mümkündür ama hantaldır; bu yüzden pratikte referans kullanılır.
fn uzunluk_al(s: String) -> (String, usize) {
let n = s.len();
(s, n) // sahiplik çağırana geri döner
}
fn tuket(s: String) {
println!("tüketildi: {s}");
} // s burada düşürülür
fn main() {
let ad = String::from("Ayşe");
let (ad, n) = uzunluk_al(ad);
println!("{ad} {n} bayt");
tuket(ad);
// buradan sonra ad kullanılamaz
}
// çıktı: Ayşe 5 bayt
// çıktı: tüketildi: AyşeDikkat: len() karakter değil bayt sayısını döndürür. "Ayşe" dört karakterdir ama ş harfi UTF-8'de iki bayt tuttuğu için sonuç 5'tir. Metin ayrıntıları için String ve &str farkı yazısına bakın.
Taşındıktan sonra değişebilirlik
Değişebilirlik (mutability) değerin değil bağlamanın (binding) özelliğidir. Bu yüzden sahiplik el değiştirirken değişebilirlik de değişebilir: mut olmadan tanımlanmış bir Box'ı let mut ile yeni bir değişkene taşırsanız içeriğini değiştirebilirsiniz.
fn main() {
let sabit = Box::new(5u32);
// *sabit = 4; // derlenmez: sabit mut değil
let mut degisken = sabit; // move, artık sahibi degisken
*degisken = 4;
println!("{degisken}");
}
// çıktı: 4Kısmi move (partial move)
Bir struct'ı desenle parçalarken bazı alanları taşıyıp bazılarını referansla bağlayabilirsiniz. Buna kısmi move denir. Taşınan alanlar artık eski değişkenden kullanılamaz ve struct bir bütün olarak da kullanılamaz; ama taşınmayan alanlara erişmeye devam edebilirsiniz. Desendeki ref anahtar kelimesi alanı taşımak yerine ona referans alır.
#[derive(Debug)]
struct Kisi {
ad: String,
yas: Box<u8>,
}
fn main() {
let kisi = Kisi { ad: String::from("Ali"), yas: Box::new(30) };
let Kisi { ad, ref yas } = kisi; // ad taşınır, yas ödünç alınır
println!("ad: {ad}, yaş: {yas}");
println!("kisi.yas hâlâ kullanılabilir: {}", kisi.yas);
}
// çıktı: ad: Ali, yaş: 30
// çıktı: kisi.yas hâlâ kullanılabilir: 30Aynı programın sonuna println!("{kisi:?}"); eklerseniz derleyici bütünü kullanmanıza izin vermez:
#[derive(Debug)]
struct Kisi {
ad: String,
yas: Box<u8>,
}
fn main() {
let kisi = Kisi { ad: String::from("Ali"), yas: Box::new(30) };
let Kisi { ad, ref yas } = kisi;
println!("{ad} {yas}");
println!("{kisi:?}");
}
// error[E0382]: borrow of partially moved value: `kisi`Rust by Example bir kısıtı daha hatırlatır: Drop uygulayan tiplerden kısmi move yapılamaz, çünkü drop metodu değeri daha sonra bütün olarak kullanacaktır.
Ödünç alınmış değerden taşımak: mem::take ve mem::replace
Sık karşılaşılan bir durum: elinizde &mut referans var ve içindeki bir alanı dışarı almak istiyorsunuz. Referans sahiplik vermediği için alanı doğrudan taşıyamazsınız; derleyici E0507 hatası verir.
struct Kutu {
icerik: String,
}
fn bosalt(k: &mut Kutu) -> String {
k.icerik
}
fn main() {}
// error[E0507]: cannot move out of `k.icerik` which is behind a mutable referenceÇözüm, alanın yerine geçerli bir değer bırakarak eskisini almaktır. std::mem::take hedefi tipin Default değeriyle değiştirip eski değeri döndürür; std::mem::replace ise yerine sizin verdiğiniz değeri koyar. Belgelere göre replace iki değerden hiçbirini düşürmez. Değeri kapsam bitmeden erken temizlemek istediğinizde drop fonksiyonunu çağırırsınız; bu fonksiyon gerçekte boş gövdeli pub fn drop<T>(_x: T) {} tanımından ibarettir, değeri içine taşıdığı için dönmeden önce düşürür.
use std::mem;
struct Kutu {
icerik: String,
}
fn bosalt(k: &mut Kutu) -> String {
mem::take(&mut k.icerik) // yerine String::new() bırakılır
}
fn main() {
let mut k = Kutu { icerik: String::from("kitap") };
let alinan = bosalt(&mut k);
println!("alınan: {alinan}, kalan: {:?}", k.icerik);
let mut durum = String::from("eski");
let onceki = mem::replace(&mut durum, String::from("yeni"));
println!("{onceki} yerine {durum}");
let v = vec![1, 2, 3];
drop(v); // v burada düşürülür, sonra kullanılamaz
println!("bitti");
}
// çıktı: alınan: kitap, kalan: ""
// çıktı: eski yerine yeni
// çıktı: bittiFonksiyon imzasından sahipliği okumak

Sahiplik kurallarını içselleştirmenin pratik yolu, her fonksiyon imzasında parametrenin hangi biçimde alındığına bakmaktır. Deyimsel Rust'ta fonksiyonlar gerçekten ihtiyaç duymadıkça argümanlarının sahipliğini almaz. Aşağıdaki tablo üç temel biçimi karşılaştırır:
| Parametre | Çağıran açısından | Ne zaman seçilir |
|---|---|---|
| s: String | Değer taşınır, sonra kullanılamaz | Fonksiyon değeri saklayacak ya da tüketecekse |
| s: &String veya s: &str | Değer ödünç verilir, sonra kullanılabilir | Fonksiyon yalnızca okuyacaksa |
| s: &mut String | Değiştirilebilir ödünç, çağrı boyunca tek erişim | Fonksiyon değeri yerinde değiştirecekse |
Dönüş tarafında da benzer bir okuma yapılır: String döndüren bir fonksiyon sahipliği size verir ve değeri temizlemek artık sizin kapsamınızın işidir. Referans döndüren fonksiyonlarda ise dönen referansın hangi girdiye bağlı olduğu lifetime kurallarıyla belirlenir; bu konu Rust lifetime rehberi yazısında anlatılıyor.
Sık yapılan hatalar
- Döngü içinde taşımak:
fordöngüsündeStringalan bir fonksiyona aynı değişkeni vermek ilk turda değeri taşır, ikinci turda derleyici "value moved here, in previous iteration of loop" notuyla E0382 verir. Referans geçirin ya da döngü içinde yeni değer üretin. - Her yerde clone kullanmak: Hata mesajı clone önerse de bu her zaman doğru çözüm değildir. Fonksiyon yalnızca okuyorsa parametreyi
&Stringyerine&strreferansı olarak almak hem kopyayı hem taşımayı ortadan kaldırır. - Tamsayı ile String davranışını karıştırmak:
let y = x;sonrasıx'in çalışmaya devam etmesi tipinCopyolmasından kaynaklanır; aynı koduStringile yazınca taşınma olur. - Atamanın eski değeri düşürdüğünü unutmak:
Dropuygulayan bir değişkene yeni değer atamak, eski değerin yıkıcısını o satırda çalıştırır. Dosya veya kilit gibi kaynaklarda bu zamanlama önemlidir. _ile_adfarkı:let _ = deger;hiçbir şeyi bağlamaz ve taşımaz,let _ad = deger;ise değeri taşır. Ayrıntı: Rust pattern sözdizimi.
Sık Sorulan Sorular
Ownership çalışma anında yavaşlığa yol açar mı?
Hayır. The Book'a göre sahiplik kurallarının hiçbiri programın çalışmasını yavaşlatmaz; tüm denetim derleme sırasında yapılır. drop çağrıları da derleyicinin kapsam sonuna yerleştirdiği sıradan fonksiyon çağrılarıdır.
Move olunca veri bellekte kopyalanır mı?
String için yalnızca stack'teki işaretçi, uzunluk ve kapasite kopyalanır; heap'teki içerik yerinde kalır ve yeni sahibe geçer. Heap verisinin kopyalanması yalnızca clone çağırdığınızda olur.
Değişkenlerin düşürülme sırası nedir?
Bir kapsamdan çıkılırken o kapsamdaki değişkenler tanımlanma sırasının tersiyle düşürülür. Struct alanları ve demet elemanları ise tanım sırasıyla düşürülür. Ayrıntılar Rust Reference'ın Destructors sayfasındadır.
Bir değeri kapsam bitmeden nasıl serbest bırakırım?
drop(deger) çağırın. Değer fonksiyona taşındığı için dönüşten önce temizlenir ve sonrasında kullanılamaz. Copy tiplerde bu çağrının etkisi yoktur, çünkü fonksiyona kopya gider.
Sahiplik kurallarını aşmanın yolu var mı?
Kuralları aşmak yerine onlara uygun araçlar kullanılır: okumak için referans, birden fazla sahip için Rc ve RefCell, iş parçacıkları arasında paylaşım için Arc ve Mutex.
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 · Slice ve &str · Lifetime · E0382 hatası · Box akıllı işaretçi · Rust nedir?