---
title: 'Ödünç Denetleyicisi'
source: 'https://academia.sh/tr/kurslar/sahiplik/odunc-denetleyicisi'
course: 'Sahiplik ve Bellek Modeli'
language: tr
updated: '2026-09-13T05:09:25+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Ödünç denetleyicisinin derleme zamanında sınadığı şeyi üç redd örneğiyle (taşıma sırasında ödünç, okurken değiştirme, yerel referans döndürme) gösterir'
    - 'Aynı işi yapan iki farklı çözümün (kapsam daraltma, kopya alma) aynı defter kaydını ürettiğini koşturarak kanıtlar'
    - 'Ödünç kurallarına tam uyan bir kodun yine de yanlış bir sonuç verebileceğini örnekle kanıtlar'
---

# Ödünç Denetleyicisi

Ödünç denetleyicisi, derleme zamanında bir değerin sahiplik ve ödünç kurallarını sınar; üç redd örneği (taşıma sırasında canlı bir ödünç, okurken değiştirme, yerel bir sahibe referans döndürme) hep düzyazıda tarif edilir, yanlarına kabul edilen çözüm koşturulur. Aynı işi yapan iki farklı çözüm aynı defter kaydını üretebilir; ödünç kurallarına uyan bir kod ise yine de yanlış bir sonuç verebilir — denetleyici doğruluğu değil, yalnız sahiplik disiplinini güvence altına alır.

Önceki dört ders ödünç almanın kurallarını tek tek kurdu: sahiplik vermeden erişim, kaynağın
bitmemesi, ömrün kaynağı aşmaması, paylaşımlı/değiştirilebilir ayrımı. Bu ders bu kuralları
uygulayan mekanizmanın kendisine bakıyor: ödünç denetleyicisi derleme zamanında tam olarak neyi
sınıyor? Denetleyicinin izlediği şey bir değerin ömrüdür — Eşzamanlılık kursunda (M09/K03)
sorulan "koşan bir birim nasıl duracak" sorusu değil.

Dört dersin de ortak bir varsayımı vardı: gösterilen her kod zaten kurala uyuyordu, kabul
edilen tarafta duruyordu. Bu ders o çizginin kendisini görünür kılıyor — kuralın neyi
ayırdığını, hem reddedilen tarafı düzyazıyla tarif ederek hem kabul edilen tarafı
koşturarak gösteriyor. Aynı işi yapan birden çok kabul edilen yazımın var olabilmesi de bu
dersin ayrı bir bulgusu: kural tek bir doğru biçim dayatmıyor, yalnız bir sınır çiziyor.

Derleyicinin bir yazımı reddetmesi bu müfredatta ilk kez Go'nun Tasarım Amaçları dersinde
(M09/K01) ana ölçü ekseniydi — orada kaynağın derlenip derlenmediğinin kendisi ölçülüyordu.
Bu kursta aynı soru kursun ana ölçümü **olamaz**; yalnız bu dersin sınırlı payı. Kalan dört
dersin hiçbirinde olmadığı kadar çok redd bu derste görülüyor, ama her biri aynı kurala
bağlı kalıyor: **redd düzyazıda tarif edilir, kabul edilen çözüm koşturulur.**

Önceki üç dersteki reddler tek tek, birer kuralın somut örneğiydi — bu derste üç redd
yan yana durup denetleyicinin genel çalışma biçimini gösteriyor. Üçünün de ortak yanı şu:
denetleyici bir kaynağı satır satır okumuyor, kaynaktaki her değerin ne kadar süre
geçerli olduğunu ve o süre boyunca kimin hangi yetkiyle eriştiğini bir bütün olarak
izliyor. Bir satırın kendi başına geçerli görünmesi yetmiyor; o satırın önceki ve sonraki
satırlarla birlikte kurduğu ilişki de kurala uymak zorunda.

## Redd 1: Taşıma Sırasında Canlı Bir Ödünç

Bir değer ödünç verilmişken taşınamaz. `deger`e bir referans (`bakis`) alınmışken aynı
`deger`i bir işleve taşımaya çalışan bir çağrı derlenmez — çünkü taşıma eski adı geçersiz
kılıyor, ama `bakis` hâlâ o adın işaret ettiği yere bakıyor olurdu. Bu ihlali gösteren bir
kaynak hiç yazılmıyor; onun yerine aynı işi yapan iki ayrı **kabul edilen** çözüm
koşturuluyor ve ikisinin de aynı defter kaydını ürettiği ölçülüyor.

Bu redd, Sahiplik ve Taşıma dersindeki tek-sahip kuralı ile Ödünç Alma dersindeki
kapsam-içi-kalma kuralının kesiştiği yer. Taşıma tek başına serbest, ödünç alma tek
başına serbest; ikisi aynı değer üzerinde, aynı anda çakıştığında denetleyici devreye
giriyor. Taşıma eski adı geçersiz kılarken elde kalan bir referansın ne olacağı
belirsizleşiyor — denetleyici bu belirsizliği, taşımaya izin vermeden önce üzerindeki
bütün ödünçlerin kapanmış olmasını isteyerek gideriyor.

**Çözüm A — kapsam daraltma:** referans kendi dar kapsamında bırakılıp taşımadan önce
kapatılıyor.

```rust
// iz.rs — bir degerin bitisini kaydeden olcum yardimcisi
use std::cell::RefCell;
use std::rc::Rc;

pub type Defter = Rc<RefCell<Vec<String>>>;

pub struct Iz {
    pub ad: String,
    defter: Defter,
}

impl Iz {
    pub fn yeni(ad: &str, defter: &Defter) -> Iz {
        Iz { ad: ad.to_string(), defter: Rc::clone(defter) }
    }
}

impl Drop for Iz {
    fn drop(&mut self) {
        self.defter.borrow_mut().push(self.ad.clone());
    }
}

pub fn yeni_defter() -> Defter {
    Rc::new(RefCell::new(Vec::new()))
}

pub fn okunan(defter: &Defter) -> Vec<String> {
    defter.borrow().clone()
}
```

```rust
// kapsam_daraltma.rs — odunc, tasimadan once dar bir kapsamda bitiriliyor
mod iz;
use iz::{Iz, yeni_defter, okunan};

fn gorevi_isle(deger: Iz) {
    println!("islev icinde tasinan ad: {}", deger.ad);
}

fn main() {
    let defter = yeni_defter();
    let deger = Iz::yeni("gorev", &defter);

    {
        let bakis = &deger;
        println!("mevcut ad: {}", bakis.ad);
    }
    gorevi_isle(deger);
    println!("kapsam daraltma sonrasi defter: [{}]", okunan(&defter).join(", "));
}
```

```
mevcut ad: gorev
islev icinde tasinan ad: gorev
kapsam daraltma sonrasi defter: [gorev]
```

**Çözüm B — kopya alma:** okuma için gereken veri baştan kopyalanıyor, `deger`e hiç
referans alınmıyor.

```rust
// kopya_alma.rs — okunacak veri onceden kopyalanip odunc hic alinmiyor
mod iz;
use iz::{Iz, yeni_defter, okunan};

fn gorevi_isle(deger: Iz) {
    println!("islev icinde tasinan ad: {}", deger.ad);
}

fn main() {
    let defter = yeni_defter();
    let deger = Iz::yeni("gorev", &defter);

    let mevcut_ad = deger.ad.clone();
    println!("mevcut ad: {}", mevcut_ad);
    gorevi_isle(deger);
    println!("kopya alma sonrasi defter: [{}]", okunan(&defter).join(", "));
}
```

```
mevcut ad: gorev
islev icinde tasinan ad: gorev
kopya alma sonrasi defter: [gorev]
```

**SK13.** İki çözüm de aynı defter kaydını (`[gorev]`) üretiyor. Denetleyici tek bir doğru
yazım dayatmıyor; yalnız bir kuralı (taşıma sırasında canlı bir ödünç olamaz) uyguluyor ve
bu kuralı sağlayan her yazım kabul ediliyor. Kapsam daraltma referansın ömrünü kısaltıyor,
kopya alma referansı hiç kurmuyor — ikisi de farklı bir yoldan aynı kurala uyuyor ve aynı
gözlemlenebilir sonuca varıyor.

İki çözüm arasındaki fark maliyette. Kapsam daraltma hiçbir yeni veri kurmuyor, yalnız
referansın kapsamını daraltıyor; kopya alma ise `deger.ad`in bir kopyasını çıkarıyor —
`Iz`in kendisi hâlâ tek, ama `ad` alanının içeriği bir kez daha kuruluyor. İki yazım da
aynı kurala uyuyor, ama hangisinin seçileceği kaynağın geri kalanına bağlı: referansın
kapsamı dar bir bloğa sığdırılabiliyorsa kapsam daraltma tercih edilir; okunacak veri
işlevin farklı noktalarında tekrar tekrar gerekiyorsa kopya almak daha sade kalabilir.

## Redd 2: Okurken Değiştirme

Bir listenin üzerinde paylaşımlı bir ödünçle (örneğin bir döngüyle) gezinirken aynı listeyi
değiştirmeye çalışmak da reddediliyor — döngünün kendisi listeye bir paylaşımlı referans
tutarken, listeye eleman eklemek değiştirilebilir bir referans istiyor ve ikisi çakışıyor.
Çözüm, okuma ile yazmayı iki ayrı adıma bölmek. Bu kalıp, bir koleksiyonun her elemanından
türeyen yeni elemanları aynı koleksiyona eklemek istendiğinde sıkça karşılaşılan bir
durum — koleksiyonun kendi üzerinde gezinirken kendisini büyütmeye çalışmak.

```rust
// okuma_sonra_yazma.rs — once paylasimli odunc ile okunur, sonra ayri bir adimda degistirilir
mod iz;
use iz::{Iz, yeni_defter, okunan};

fn main() {
    let defter = yeni_defter();
    let mut liste = vec![Iz::yeni("p", &defter), Iz::yeni("q", &defter)];

    let mut kopya_adlari = Vec::new();
    for iz in &liste {
        kopya_adlari.push(format!("{}-kopya", iz.ad));
    }

    for ad in &kopya_adlari {
        liste.push(Iz::yeni(ad, &defter));
    }

    println!("liste uzunlugu: {}", liste.len());
    println!("henuz biten yok, defter: [{}]", okunan(&defter).join(", "));
}
```

```
liste uzunlugu: 4
henuz biten yok, defter: []
```

İlk döngü `&liste` üzerinde gezinip her adın bir kopyasını ayrı bir `Vec<String>`e
topluyor; bu döngü bittiğinde paylaşımlı ödünç de kapanıyor. İkinci döngü `liste`yi
serbestçe değiştiriyor, çünkü artık üzerinde hiçbir ödünç yok. Aynı işi tek bir döngüde,
gezinme sürerken listeye eleman ekleyerek yapmaya çalışan bir yazım derlenmezdi.

Bu redd Redd 1'le aynı aileden, ama kaynağı farklı. Redd 1'de çakışan iki şey bir taşıma
ile bir ödünçtü; burada çakışan iki şey bir paylaşımlı ödünç (döngünün kendisi) ile bir
değiştirilebilir ödünç (`push` çağrısı). İkisinin ortak noktası, denetleyicinin çakışmayı
satırların **sırasına** değil, ödünçlerin **kapsamlarının örtüşüp örtüşmediğine** bakarak
bulması. Döngü `liste`nin her elemanını teker teker ziyaret ederken bile, döngünün kendisi
`liste` üzerinde tek, sürekli bir paylaşımlı ödünç tutuyor — döngü bitene kadar kapanmıyor.

## Redd 3: Yerel Bir Sahibe Referans Döndürmek

Bir işlev kendi içinde kurduğu bir değere referans döndüremez — işlev bitince o değerin
sahibi (işlevin kendi kapsamı) da bitiyor, döndürülen referans artık var olmayan bir yere
işaret ederdi. Çözüm, Sahiplik ve Taşıma dersinde kurulan düzenin aynısı: referans değil,
değerin **kendisini** döndürmek.

İlk iki redd, ödünç ile taşımanın ya da ödünç ile başka bir ödüncün aynı kapsamda
çakışmasıydı — her ikisinde de sorunlu iki erişim aynı satırlar arasında, aynı kaynak
içinde duruyordu. Üçüncü redd farklı: sorun tek bir işlevin sınırını aşıyor, çağıran
tarafla çağrılan taraf arasında.

```rust
// sahiplik_dondur.rs — islev yerel degeri referans degil, sahiplikle donduruyor
mod iz;
use iz::{Iz, yeni_defter, okunan, Defter};

fn olustur(ad: &str, defter: &Defter) -> Iz {
    let yerel = Iz::yeni(ad, defter);
    yerel
}

fn main() {
    let defter = yeni_defter();
    let deger = olustur("uretilen", &defter);
    println!("uretilen ad: {}", deger.ad);
    println!("deger hala sahibinde, defter: [{}]", okunan(&defter).join(", "));
}
```

```
uretilen ad: uretilen
deger hala sahibinde, defter: []
```

`olustur` işlevi `yerel`i kendi gövdesinde kuruyor ve onu **taşıyarak** döndürüyor —
`&yerel` değil, `yerel`in kendisi. Sahiplik çağırana geçtiği için `deger` `main`in sonuna
kadar geçerli kalıyor; defter hâlâ boş. `olustur`un imzası `&Iz` döndürseydi, derleyici bu
imzayı bile kabul etmezdi, çünkü döndürülecek referansın hangi kaynağa bağlı olduğu
belirsiz kalırdı — işlev içinde kurulan `yerel`in kendisi işlev bitince bitiyor.

Bu redd önceki ikisinden farklı bir yerde duruyor: Redd 1 ve Redd 2 belirli bir çağrı
sırasını yasaklıyordu, kaynağın kendisi baştan itibaren geçersizdi. Burada asıl reddedilen
şey, işlevin **imzası** — `fn olustur(...) -> &Iz` yazımı, gövdesine hiç bakılmadan
sorunlu. Bir işlevin döndürdüğü bir referansın nereye bağlı olduğu, işlevin çağrıldığı her
yerde bilinmesi gereken bir bilgi; işlev kendi içinde kurduğu bir değere referans
döndürdüğünde bu bilgi kaynakta hiçbir yerde yazılı değil. Sahiplik ve Taşıma dersinde
görülen "işlevin sahip olduğu bir değeri geri döndürmesi bir taşımadır" kuralı burada tek
çözüm olarak geri dönüyor.

**SK14.** Bu redd, bir referansın geçerli olduğu aralığın kaynağının kapsamını hiçbir
zaman aşamaması kuralının (Ödünç Alma dersi) doğrudan bir sonucu. Burada henüz adı
konmamış bir kavram devreye giriyor: bir referansın "ne kadar süre" geçerli kalabileceğini
belirleyen bu aralığın kendi bir adı var, ama bu ders o adı vermiyor.

Bu redd, ilk iki reddin aksine, işlevin çağrıldığı hiçbir yerdeki koda bakmadan, yalnız
`olustur`un kendi gövdesi okunarak tespit edilebiliyor. Denetleyici bunu, işlevin imzasına
bakıp döndürülen referansın hangi parametreye ya da hangi yerel değere bağlı olduğunu
izleyerek yapıyor; `yerel`in kaynağı işlevin dışından gelmediği için, döndürülecek bir
referansın dışarıda geçerli kalabileceği hiçbir yol yok.

## Sınırlayıcı Ölçüm: Denetleyici Doğruluğu Güvence Altına Almaz

Buraya kadarki bütün örnekler denetleyicinin **neyi** reddettiğini gösterdi. Şimdi tersi:
denetleyicinin kabul ettiği, hiçbir kuralı ihlal etmeyen bir kod, yine de yanlış bir sonuç
verebilir.

```rust
// dogruluk_garantisi_yok.rs — odunc kurallarina uyan kod yine de yanlis sonuc verebilir
fn ekle_ve_say(liste: &mut Vec<i32>, deger: i32) -> usize {
    let uzunluk = liste.len();
    liste.push(deger);
    uzunluk
}

fn main() {
    let mut liste = Vec::new();
    let sayim1 = ekle_ve_say(&mut liste, 10);
    let sayim2 = ekle_ve_say(&mut liste, 20);
    println!("islevin dondurdugu sayimlar: {}, {}", sayim1, sayim2);
    println!("listenin gercek uzunlugu: {}", liste.len());
}
```

```
islevin dondurdugu sayimlar: 0, 1
listenin gercek uzunlugu: 2
```

`ekle_ve_say` hiçbir sahiplik ya da ödünç kuralını ihlal etmiyor — `liste`yi bir
değiştirilebilir referansla alıyor, elemanı ekliyor, geçerli bir sayı döndürüyor. Ama
`uzunluk` eklemeden **önceki** uzunluğu tutuyor, döndürülen değer ise "eklemeden sonraki
sayım" olarak okunmak isteniyorsa yanlış: iki çağrı `0` ve `1` döndürüyor, oysa listenin
gerçek uzunluğu sırasıyla `1` ve `2` olmalıydı.

**SK15.** Ödünç denetleyicisi yalnız **sahiplik ve erişim disiplinini** sınıyor — bir
değere aynı anda kimin, ne yetkiyle eriştiğini. Bir hesabın hangi noktada okunduğu,
bir değişkenin doğru satırda güncellenip güncellenmediği bu denetimin dışında; kaynak
tamamen geçerli bir sahiplik/ödünç düzeni taşırken de mantık hatası barındırabilir. Bu
dersin ölçtüğü tek şey şu: bir kodun derlenmesi, onun **doğru** olduğu anlamına gelmiyor.

Hatanın düzeltilmesi de aynı gözlemi tamamlıyor: sorun sahiplikte değil, `uzunluk`un hangi
satırda okunduğunda.

```rust
// duzeltilmis.rs — ayni islev, uzunluk eklemeden sonra okunuyor
fn ekle_ve_say(liste: &mut Vec<i32>, deger: i32) -> usize {
    liste.push(deger);
    liste.len()
}

fn main() {
    let mut liste = Vec::new();
    let sayim1 = ekle_ve_say(&mut liste, 10);
    let sayim2 = ekle_ve_say(&mut liste, 20);
    println!("islevin dondurdugu sayimlar: {}, {}", sayim1, sayim2);
    println!("listenin gercek uzunlugu: {}", liste.len());
}
```

```
islevin dondurdugu sayimlar: 1, 2
listenin gercek uzunlugu: 2
```

`liste.push(deger)` ile `liste.len()`in yeri değiştirildi, başka hiçbir şey değişmedi —
parametre tipi, ödünç, imza aynı kaldı. Düzeltilmiş sürüm de tıpkı hatalı sürüm gibi
ödünç denetleyicisinden geçiyor, çünkü denetleyici ikisi arasındaki farkı zaten hiç
görmüyor: her iki sürümde de `liste`ye bir kez değiştirilebilir referans alınıyor, bir kez
kullanılıyor, kural ihlal edilmiyor. Denetleyicinin sessiz kalması, kodun doğru olduğunun
kanıtı değil, yalnız erişim kurallarının ihlal edilmediğinin kanıtı.

Üç redd ve bir doğruluk sınırı, birlikte ödünç denetleyicisinin sınırlarını çiziyor.
Denetleyici bir kaynağı reddettiğinde, sebep her zaman aynı ailede: bir değere aynı anda
birden fazla, birbiriyle çelişen erişimin var olması (Redd 1, Redd 2) ya da bir referansın
kaynağından daha uzun yaşayacak olması (Redd 3). Kabul ettiğinde ise denetleyicinin
söylediği şey dardır — yalnız bu erişim düzeninin güvenli olduğu, kodun yaptığı hesabın
doğru olduğu değil. Dört ders boyunca kurulan sahiplik, taşıma, ödünç ve dilim kuralları
bu dersle birlikte tek bir mekanizmaya, tek bir denetime bağlanıyor; ama o mekanizmanın
sınadığı şeyin sınırları da aynı netlikte görülüyor.

## Özet

- Ödünç denetleyicisi, derleme zamanında sahiplik ve ödünç kurallarının ihlal edilip
  edilmediğini sınıyor; ihlal eden kaynak hiç derlenmiyor.
- Taşıma sırasında canlı bir ödünç olamaz; kapsam daraltma ve kopya alma bu kısıtı iki
  farklı yoldan sağlayabiliyor ve ikisi de aynı defter kaydını üretiyor: değer iki yazımda
  da **kapsam sonunda** bitiyor.
- Bir kaynak üzerinde paylaşımlı bir ödünçle gezinirken aynı kaynağı değiştirmek
  reddediliyor; okuma ve yazma ayrı adımlara bölünerek çözülüyor.
- Bir işlev kendi içinde kurduğu bir değere referans döndüremez; çözüm, referans değil
  değerin kendisini (sahiplikle) döndürmek.
- Ödünç kurallarına tam uyan bir kod yine de yanlış bir sonuç verebilir — denetleyici
  doğruluğu değil, yalnız erişim disiplinini güvence altına alıyor.
- Aynı kurala uyan birden çok yazım (kapsam daraltma, kopya alma) kabul edilebilir;
  denetleyici tek bir doğru biçim dayatmıyor, yalnız bir sınır çiziyor.

## Sonraki Adım

Bu derste denetleyicinin sınadığı şeyin bir **aralık** olduğu defalarca görüldü — bir
referansın ne kadar süre geçerli kalabileceğini belirleyen bir sınır. Redd 1'de bu aralık
bir taşımayla çakışmamak zorundaydı, Redd 2'de bir değişiklikle çakışmamak zorundaydı,
Redd 3'te ise kaynağının kendi kapsamını hiçbir zaman aşamıyordu. Üçü de aynı şeyden söz
ediyor, ama bu aralığın kendi adı bu beş ders boyunca hiç konmadı. Sıradaki bölüm tam
olarak bu adı koyuyor: bir referansın geçerlilik aralığının kendi kavramı, bu aralığın ne
zaman kaynaktan okunabildiği ve ne zaman açıkça yazılması gerektiği.
