مقدمه

تاکنون، ما با قوانین مالکیت Rust کار کرده‌ایم که می‌گوید هر مقدار در هر لحظه تنها می‌تواند یک مالک داشته باشد. این قانون یکی از ستون‌های اصلی ایمنی حافظه در Rust است. اما سناریوهایی وجود دارد که در آنها ما نیاز داریم یک داده واحد، چندین مالک داشته باشد. برای مثال، در یک گراف، ممکن است چندین یال به یک گره یکسان اشاره کنند و آن گره تا زمانی که حداقل یک یال به آن اشاره می‌کند، باید در حافظه باقی بماند.

برای این نوع سناریوهای مالکیت چندگانه، Rust یک اشاره‌گر هوشمند به نام Rc<T> ارائه می‌دهد. نام این نوع مخفف Reference Counting یا «شمارش ارجاع» است.

کار با Rc<T>

اشاره‌گر هوشمند Rc<T> تعداد کل رفرنس‌های فعال به یک مقدار را پیگیری می‌کند. وقتی یک نمونه Rc<T> جدید از روی یک نمونه موجود clone می‌شود، به جای کپی کردن داده‌ها، تنها شمارنده ارجاع آن یک واحد افزایش می‌یابد. داده تنها زمانی از حافظه پاک می‌شود که شمارنده ارجاع آن به صفر برسد، یعنی زمانی که آخرین مالک آن از حوزه خارج شود.

مهم است بدانید که Rc<T> تنها برای استفاده در سناریوهای تک-ریسمانی (single-threaded) طراحی شده است. برای مدیریت مالکیت مشترک در محیط‌های چند-ریسمانی، Rust یک نوع دیگر به نام Arc<T> (Atomic Reference Counting) ارائه می‌دهد که در فصل‌های بعدی به آن خواهیم پرداخت.

بیایید با یک مثال، نحوه استفاده از Rc<T> را برای ساخت دو لیست که بخشی از ساختار خود را به اشتراک می‌گذارند، ببینیم.

Copy Icon src/main.rs
use std::rc::Rc;

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after creating a: {}", Rc::strong_count(&a));

    // Create another list `b` that shares ownership of `a`'s tail
    let b = Cons(3, Rc::clone(&a));
    println!("count after creating b: {}", Rc::strong_count(&a));

    {
        // Create a third list `c` that also shares ownership
        let c = Cons(4, Rc::clone(&a));
        println!("count after creating c: {}", Rc::strong_count(&a));
    }

    println!("count after c goes out of scope: {}", Rc::strong_count(&a));
}

در این کد، ابتدا یک لیست به نام a می‌سازیم که با Rc احاطه شده است. ما با استفاده از تابع Rc::strong_count() می‌توانیم تعداد مالکان (شمارنده ارجاع) را مشاهده کنیم. سپس دو لیست دیگر، b و c، را می‌سازیم که هر دو با فراخوانی Rc::clone(&a)، مالکیت مشترکی بر روی لیست a پیدا می‌کنند. توجه کنید که Rc::clone() یک کپی کامل از داده‌ها انجام نمی‌دهد، بلکه تنها شمارنده ارجاع را افزایش می‌دهد که عملیاتی بسیار سریع است.

خروجی این برنامه به شکل زیر خواهد بود:

count after creating a: 1
count after creating b: 2
count after creating c: 3
count after c goes out of scope: 2
                    

همانطور که می‌بینید، با هر بار clone کردن، شمارنده یک واحد زیاد می‌شود و زمانی که c از حوزه خود خارج می‌شود، شمارنده یک واحد کم می‌شود. حافظه لیست a تنها زمانی آزاد خواهد شد که شمارنده به صفر برسد (یعنی زمانی که a و b نیز از حوزه خارج شوند).

خلاصه

Rc<T> یک ابزار بسیار مفید برای مدیریت مالکیت داده‌ها در سناریوهایی است که یک داده واحد باید توسط چندین بخش از برنامه به اشتراک گذاشته شود و طول عمر آن تا زمانی که آخرین بخش به آن نیاز دارد، ادامه یابد. این اشاره‌گر هوشمند با استفاده از شمارش ارجاع، به صورت خودکار و ایمن این کار را مدیریت می‌کند.

یک محدودیت Rc<T> این است که تنها به شما اجازه می‌دهد تا رفرنس‌های تغییرناپذیر (immutable) به داده‌های خود داشته باشید. اگر نیاز به تغییر دادن داده‌های اشتراکی داشته باشید چه؟ در درس بعدی، با الگوی «تغییرپذیری داخلی» یا Interior Mutability و اشاره‌گرهای هوشمند مرتبط با آن آشنا خواهیم شد.