Rust Akıllı İşaretçiler: Box, Rc, Arc ve RefCell (2026)

Rust akıllı işaretçileri Box, Rc, Arc ve RefCell; derlenebilir 2026 örnekleri, bir karar tablosu ve yaygın mülakat sorularıyla açıklanıyor.

Rust akıllı işaretçiler Box, Rc, Arc ve RefCell bellek yönetimi diyagramı

Rust akıllı işaretçileri, ödünç denetleyicisinin tek başına ifade edemediği sahiplik desenlerini açan araçlardır: yığın üzerinde ayırma, paylaşılan sahiplik ve paylaşılan bir referans arkasında değiştirme. Sıradan bir referans (&T) yalnızca bir değeri ödünç alırken, akıllı bir işaretçi verisinin sahibidir ve üzerine ek davranış ekler. Bu rehber, her Rust geliştiricisinin üretimde ve mülakatlarda karşılaştığı dört türü ele alır: Box, Rc, Arc ve RefCell; Rust 2024 sürümünü hedefleyen derlenebilir örneklerle birlikte.

Tek satırlık özet

Tek sahipli yığın ayırma için Box<T>, tek iş parçacığında paylaşılan sahiplik için Rc<T>, iş parçacıkları arasında paylaşılan sahiplik için Arc<T> ve bir değeri paylaşılan bir referans üzerinden değiştirmek için RefCell<T> kullanılır. Rc<RefCell<T>> ve Arc<Mutex<T>> çiftleri, paylaşılan değiştirilebilir durumu kapsar.

Rust'ta akıllı işaretçiler nedir

Akıllı bir işaretçi, işaretçi gibi davranan ancak ek meta veri veya yetenekler taşıyan bir yapıdır. Çoğu, Deref özelliğini uygular; böylece *pointer ve metot çağrıları, işaretçi sıradan bir referansmış gibi çalışır. Ayrıca Drop özelliğini uygular; böylece değer kapsamdan çıktığında temizlik otomatik olarak gerçekleşir. Standart kütüphane burada ele alınan dördünü sağlar ve bunları anlamak, her ayırmayı kimin ve ne zaman serbest bıraktığına karar veren sahiplik ve ödünç alma konusuna sağlam bir hakimiyete bağlıdır.

Referanstan temel farkı sahipliktir. &T, işaret ettiği verinin asla sahibi olmaz, bu yüzden değerden daha uzun yaşayamaz. Akıllı bir işaretçi verinin sahibidir, yaşam süresini denetler ve onu belirleyici biçimde serbest bırakır. Rust Kitabı'nın akıllı işaretçiler bölümü, bunları tam da bu sahiplik-artı-davranış biçimini paylaştıkları için bir kategori olarak ele alır.

Box<T>: özyinelemeli ve boyutu belli türler için yığın ayırma

Box<T>, en basit akıllı işaretçidir. Bir değeri yığında saklar ve ona işaret eden bir işaretçiyi yığıtta tutar; tek sahiplidir ve ayırmanın kendisi dışında çalışma zamanı yükü yoktur. En yaygın görevi, özyinelemeli türlere belli bir boyut kazandırmaktır: kendisini doğrudan içeren bir tür sonsuz büyüklükte olurdu, ancak Box yalnızca bir işaretçidir, bu yüzden neye işaret ettiğinden bağımsız olarak boyutu sabittir.

Aşağıdaki örnek bir ikili ağaç tanımlar. Her Node iki çocuk tutar ve dolaylama olmadan derleyici Tree boyutunu hesaplayamaz. Her çocuğu bir Box içine sarmak, özyinelemeyi tür düzeyinde keser.

tree.rsrust
#[derive(Debug)]
enum Tree {
    Leaf(i32),
    // Box<Tree> puts each child on the heap, so Node has a fixed size (two pointers).
    Node(Box<Tree>, Box<Tree>),
}

fn sum(tree: &Tree) -> i32 {
    match tree {
        Tree::Leaf(value) => *value,                        // base case: return the leaf
        Tree::Node(left, right) => sum(left) + sum(right),  // recurse into both children
    }
}

fn main() {
    // Build (1) + ((2) + (3)) = 6
    let tree = Tree::Node(
        Box::new(Tree::Leaf(1)),
        Box::new(Tree::Node(
            Box::new(Tree::Leaf(2)),
            Box::new(Tree::Leaf(3)),
        )),
    );
    println!("sum = {}", sum(&tree)); // sum = 6
}

Box, büyük bir değeri taşımanın maliyetli olacağı durumlarda veya Box<dyn Error> gibi bir özellik nesnesi döndürülürken de önemlidir. Her durumda kural aynıdır: tek sahip, Box düşürüldüğünde otomatik olarak serbest bırakılır.

Rc<T>: tek iş parçacıklı kodda paylaşılan sahiplik

Bazen bir değerin, hiçbiri açıkça sonuncusu olmayan birden çok sahibe ihtiyacı vardır. Rc<T> (referans sayımlı), kaç sahibin var olduğunu tutan güçlü bir sayaç tutarak bunu çözer. Rc::clone, alttaki veriyi kopyalamadan bu sayacı artırır ve her düşürme onu azaltır. Sayaç sıfıra ulaştığında değer serbest bırakılır.

Buna örnek olarak, birkaç çalışanın okuduğu bir yapılandırma nesnesi verilebilir. Her çalışan, yapılandırmayı ihtiyaç duyduğu sürece tutmalı ve ayırma yalnızca son çalışan gittiğinde ortadan kalkmalıdır.

shared_config.rsrust
use std::rc::Rc;

#[derive(Debug)]
struct Config {
    endpoint: String,
    timeout_ms: u32,
}

fn main() {
    // Rc::new moves Config onto the heap with a strong count of 1.
    let config = Rc::new(Config {
        endpoint: "https://api.example.com".to_string(),
        timeout_ms: 5000,
    });

    // Rc::clone only bumps the reference count; it does not deep-copy Config.
    let worker_a = Rc::clone(&config);
    let worker_b = Rc::clone(&config);

    // All three handles point to the same allocation.
    println!("endpoint: {}", worker_a.endpoint);
    println!("timeout:  {}", worker_b.timeout_ms);

    // strong_count reports how many owners currently hold the value.
    println!("owners: {}", Rc::strong_count(&config)); // owners: 3
}

Rc::clone'un açıkça çağrılması (config.clone() yerine) deyimseldir: okuyuculara işlemin ucuz olduğunu ve yalnızca bir sayaca dokunduğunu belirtir. Püf nokta şudur: Rc<T> yalnızca paylaşılan, değiştirilemez referanslar dağıtır. Paylaştığı değeri değiştiremez ve iş parçacıkları arasında gönderilmesi güvenli değildir. Rc belgeleri her iki kısıtlamayı da açıklar.

RefCell<T> ve Rust'ta iç değiştirilebilirlik

Rust normalde bir değerin ya birçok değiştirilemez referans aracılığıyla paylaşılmasını ya da tam olarak bir değiştirilebilir referans aracılığıyla değiştirilmesini zorunlu kılar ve bunu derleme zamanında denetler. RefCell<T>, iç değiştirilebilirlik sağlar: aynı denetimi çalışma zamanına taşıyarak kodun bir değeri paylaşılan bir referans üzerinden değiştirmesine izin verir. borrow(), paylaşılan bir okuma koruması döndürür; borrow_mut(), özel bir yazma koruması döndürür. Kurallar aynıdır, ancak ihlaller derlenememek yerine panik oluşturur.

Rc ile birleştiğinde bu, tek iş parçacıklı paylaşılan-değiştirilebilir kanonik deseni Rc<RefCell<T>> üretir: aynı değeri güncelleyebilen birden çok sahip.

counter.rsrust
use std::rc::Rc;
use std::cell::RefCell;

// Rc gives shared ownership; RefCell allows mutation through a shared reference.
type SharedCounter = Rc<RefCell<u32>>;

fn increment(counter: &SharedCounter) {
    // borrow_mut() hands out an exclusive reference, checked at runtime.
    *counter.borrow_mut() += 1;
}

fn main() {
    let counter: SharedCounter = Rc::new(RefCell::new(0));

    let handle_a = Rc::clone(&counter);
    let handle_b = Rc::clone(&counter);

    increment(&handle_a);
    increment(&handle_b);
    increment(&counter);

    // borrow() gives a shared read guard; the value is now 3.
    println!("count = {}", counter.borrow()); // count = 3
}
RefCell, ödünç denetimini çalışma zamanına taşır

Başka bir borrow() veya borrow_mut() koruması hâlâ yaşarken borrow_mut() çağrılması, already borrowed: BorrowMutError ile panik oluşturur. Güvenlik garantileri korunur, ancak derleyicinin yakalayacağı bir mantık hatası çalışma zamanında bir çökmeye dönüşür. Korumalar kısa ömürlü tutulmalı ve aynı RefCell'e yeniden girebilecek bir fonksiyon çağrısı boyunca bir ödünç tutmaktan kaçınılmalıdır.

İç değiştirilebilirlik, referans döngülerinin de gizlendiği yerdir. Birbirine işaret eden iki Rc<RefCell<T>> değeri, birbirinin güçlü sayacını sonsuza dek sıfırın üzerinde tutar, bu yüzden hiçbiri asla serbest bırakılmaz. Çözüm, güçlü sayacı etkilemeyen sahipsiz bir tutamaç olan Weak<T>'dir; klasik anlatım Learn Rust With Entirely Too Many Linked Lists içinde yer alır.

Arc<T>: eşzamanlılık için iş parçacığı güvenli referans sayımı

Arc<T> (atomik referans sayımlı), Rc<T>'nin çok iş parçacıklı kardeşidir. Genel API neredeyse aynıdır, ancak sayaç atomik işlemler kullanır; böylece bir Arc'ın birden çok iş parçacığından aynı anda klonlanması ve düşürülmesi doğru kalır. Bu atomiklik, Arc'ın yalnızca paylaşım bir iş parçacığı sınırını aştığında seçilmesinin nedenidir: atomik artırma, Rc'nin kullandığı sıradan artırmadan ölçülebilir biçimde yavaştır.

Arc<T> hâlâ değiştirilemez referanslar dağıttığı için, iş parçacıkları arasında değiştirme bir eşzamanlama ilkeli gerektirir. Mutex<T> her zamanki ortaktır ve eşzamanlı kod için Rc<RefCell<T>>'i yansıtan Arc<Mutex<T>> desenini verir. Örnek, her biri paylaşılan bir toplamı bin kez artıran dört iş parçacığı başlatır.

parallel_sum.rsrust
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // Arc is safe to share across threads; Mutex serializes access to the inner value.
    let total = Arc::new(Mutex::new(0u64));
    let mut handles = Vec::new();

    for _ in 0..4 {
        // Each thread gets its own Arc handle (an atomic count bump).
        let total = Arc::clone(&total);
        handles.push(thread::spawn(move || {
            for _ in 0..1000 {
                // lock() blocks until the mutex is free, then returns a guard.
                let mut value = total.lock().unwrap();
                *value += 1;
            }
        }));
    }

    for handle in handles {
        handle.join().unwrap(); // wait for every thread before reading
    }

    println!("total = {}", *total.lock().unwrap()); // total = 4000
}

Derleyici bu sınırı zorunlu kılar: Rc<T>, Send değildir, bu yüzden onu thread::spawn içine taşıma girişimi derlenmez ve Arc'a yönlendirir. Tek bir iş parçacığı içinde Arc'a başvurmak, yalnızca asla kullanılmayan atomikler için ödeme yapmak demektir. Arc belgeleri bellek sıralama garantilerini ayrıntılı olarak ele alır ve Arc<Mutex<T>>, Tokio ile asenkron Rust içindeki paylaşılan durumun büyük bölümünün temelini oluşturur.

Rust mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Box vs Rc vs Arc vs RefCell: hangisi ne zaman kullanılır

Bu dört tür iki eksende bir araya gelir: bir değerin kaç sahibi olduğu ve paylaşılan bir tutamaç üzerinden değiştirilip değiştirilemeyeceği. Tablo, ödünleşimleri özetler.

| Tür | Sahiplik | Paylaşılan tutamaç üzerinden değiştirme | İş parçacığı güvenli | Ek yük | |------|-----------|----------------------------|-------------|----------| | Box<T> | Tekli | Hayır | Evet, T: Send ise | Yalnızca yığın ayırma | | Rc<T> | Paylaşılan | Hayır | Hayır | Atomik olmayan sayaç | | Arc<T> | Paylaşılan | Hayır | Evet | Atomik sayaç | | RefCell<T> | Tekli | Evet (çalışma zamanında denetlenir) | Hayır | Çalışma zamanı ödünç bayrağı |

Pratik karar ağacı kısadır. Yığında tek sahip gerekiyorsa: Box. Tek iş parçacığında birçok sahip gerekiyorsa: Rc. İş parçacıkları arasında birçok sahip gerekiyorsa: Arc. Paylaşılan bir değeri değiştirmek gerekiyorsa: iç tür RefCell (tek iş parçacığı) veya Mutex (çok iş parçacığı) içine sarılır. Effective Rust'ın referans ve işaretçi türleri rehberi de aynı katmanlamaya ulaşır. En az güçlü seçenekle başlamak ve yeteneği yalnızca derleyici zorladığında eklemek en iyisidir.

Rust akıllı işaretçiler mülakat soruları

Akıllı işaretçiler, bir adayın yalnızca söz dizimini değil sahipliği anlayıp anlamadığını sınadığı için güvenilir bir mülakat konusudur. Aşağıdaki sorular sıkça ortaya çıkar ve daha fazlası Rust akıllı işaretçiler mülakat modülünde toplanmıştır.

Rc ile Arc arasındaki fark nedir? Her ikisi de referans sayımı aracılığıyla paylaşılan sahiplik sağlar. Rc, atomik olmayan bir sayaç kullanır ve tek bir iş parçacığıyla sınırlıdır; Arc, atomik işlemler kullanır ve küçük bir çalışma zamanı maliyetiyle iş parçacıkları arasında paylaşılabilir. Derleyici bu ayrımı zorunlu kılar: Rc ne Send ne de Sync'tir, bu yüzden bir iş parçacığı sınırını aşamaz.

Rc neden RefCell ile birleştirilir? Rc, paylaşılan sahiplik verir ancak yalnızca değiştirilemez erişim. RefCell, iç değiştirilebilirlik ekleyerek sahiplerin çalışma zamanı ödünç denetimleriyle değeri paylaşılan bir referans üzerinden değiştirmesine olanak tanır. Birlikte Rc<RefCell<T>>, deyimsel tek iş parçacıklı paylaşılan-değiştirilebilir yapı taşıdır.

Rust'ta referans sayımı belleği nasıl sızdırabilir? Birbirine referans veren iki Rc (veya Arc) değeri, güçlü sayaçları asla sıfıra ulaşmayan bir döngü oluşturur, bu yüzden ayırma asla serbest bırakılmaz. Sahipsiz bir referans tutan Weak<T> ile döngüyü kırmak, doğru temizliği geri getirir.

Box, Rc'ye ne zaman tercih edilir? Tek bir sahip yeterli olduğunda. Box'ın referans sayımı yükü yoktur, bu yüzden yığın ayırma, özyinelemeli türler ve özellik nesneleri için varsayılandır. Rc'ye yalnızca gerçek paylaşılan sahiplik gerektiğinde başvurulur.

Sonuç

Rust akıllı işaretçileri, sahipliği bir kısıtlamadan bir araya getirilebilir yapı taşları kümesine dönüştürür. Aralarındaki seçim doğrudan problemin biçiminden çıkar:

  • Bir değer tek bir yığın sahibine ihtiyaç duyduğunda, özyinelemeli bir türün sabit bir boyuta ihtiyacı olduğunda veya bir fonksiyon bir özellik nesnesi döndürdüğünde Box<T> tercih edilir.
  • Tek iş parçacığında birden çok sahip için Rc<T> kullanılır ve sahiplik bir iş parçacığı sınırını aştığı anda Arc<T>'ye geçilir.
  • Tek iş parçacıklı kodda iç değiştirilebilirlik için RefCell<T>, eşzamanlı eşdeğeri için Mutex<T> eklenir; ödünç ve kilit korumaları kısa ömürlü tutulur.
  • Bunlar bilinçli biçimde birleştirilir: tek iş parçacığında paylaşılan değiştirilebilir durum için Rc<RefCell<T>>, iş parçacıkları arasında Arc<Mutex<T>>.
  • Sayımlı işaretçiler arasındaki referans döngülerine dikkat edilir ve sızıntıları önlemek için Weak<T> ile kırılır.
  • Varsayılan olarak en az güçlü tür seçilir ve daha fazla yeteneğin ne zaman gerektiğini derleyicinin söylemesine izin verilir.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Etiketler

#rust
#smart-pointers
#memory-management
#interview
#rust-2024-edition

Paylaş

İlgili makaleler