Rust: المؤشرات الذكية في Rust

آخر تحديث: 2026-08-26

المؤشر الذكي هو «بنية بيانات ذات سلوك إضافي» — فهو يشير إلى الذاكرة تمامًا مثل المؤشر العادي، لكنه يوفر إمكانيات تتجاوز تلك التي يوفرها المؤشر العادي، مثل الإدارة التلقائية للذاكرة، وعدّ المراجع، والتحقق من الاستعارة.

إذا كان المؤشر العادي يشبه «ورقة مكتوب عليها عنوان»، فإن المؤشر الذكي يشبه «نظام إدارة عقارات مزود بحراس أمن خاصين به، وعمال نظافة، ودفتر حسابات». تضمن المؤشرات الذكية في لغة Rust السلامة في وقت التحويل البرمجي ولا تتسبب في أي عبء إضافي في وقت التشغيل (بصرف النظر عن العبء الضئيل الناجم عن عد المراجع).


1. ما ستتعلمه



2. قصة المكاتب المشتركة

(1) المشكلة: يرغب عدة مستخدمين في عرض نفس المستند وتحريره في الوقت نفسه

في صباح يوم الاثنين، طبقت شركة «لونا» سياسة «المكاتب المشتركة». ولم يكن هناك سوى ثلاثة مكاتب لفريق مكون من خمسة أشخاص.

«لو كنا نعرف فقط عدد الأشخاص الذين ما زالوا يطالعون هذا المستند، لكان بإمكاننا إتلافه بمجرد أن يتوقف الجميع عن الاطلاع عليه...» «لو كان بإمكاننا ضمان أنه عندما يقوم شخص ما بتحريره، لا يستطيع أي شخص آخر إجراء تغييرات عليه...»

(2) حلول المؤشرات الذكية في لغة Rust

RUST
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    // Box: One person has exclusive access to a document,Destroy after reading
    let doc_box = Box::new(String::from("Project Proposal v1"));
    println!("Box Hold: {}", doc_box);
    // doc_box Automatically destroyed upon leaving the scope——No manual intervention required free

    // Rc: Shared Read-Only Access for Multiple Users
    let doc_rc = Rc::new(String::from("Shared Project Proposal"));
    let alice = Rc::clone(&doc_rc);
    let bob = Rc::clone(&doc_rc);
    println!("Reference Count: {}", Rc::strong_count(&doc_rc)); // 3

    // Rc<RefCell<T>>: Share + Variable
    let doc_shared = Rc::new(RefCell::new(String::from("Collaborative Documents")));
    let alice_view = Rc::clone(&doc_shared);
    let bob_view = Rc::clone(&doc_shared);

    // Bob Add annotations to a document(Needs to be revised)
    bob_view.borrow_mut().push_str("\nBob Comments on:Part 2 needs to be completed");
    // Alice View the documentation(Read-only)
    println!("Alice See: {}", alice_view.borrow());
    // Carol See the documentation as well(Read-only)
    println!("Carol See: {}", doc_shared.borrow());
}

تستخدم لغة Rust ثلاثة أنواع من المؤشرات الذكية لمعالجة مشكلات أمان الخيوط المتعلقة بـ«المشاركة» و«التعديل»: Box الملكية الحصرية والتخصيص من الـ«هياب»، Rc الملكية المشتركة للقراءة فقط، وRefCell القابلية للتغيير الداخلية التي تتحقق من خلال عمليات التحقق في وقت التشغيل لقواعد الاستعارة.



3. المفاهيم الأساسية

(1) نظام المؤشر الذكي

100%
graph TB
    A[Rust Smart Pointers] --> B[Box&lt;T&gt; Heap Allocation]
    A --> C[Rc&lt;T&gt; Reference Count]
    A --> D[RefCell&lt;T&gt; Internal Variability]

    B --> B1["let b = Box::new(42)"]
    B --> B2["Automatically leaves scope drop"]

    C --> C1["Rc::clone Increase the reference count"]
    C --> C2["strong_count == 0 Destroy on time"]
    C --> C3["Use in a single-threaded environment"]

    D --> D1["borrow() → Immutable References"]
    D --> D2["borrow_mut() → Variable References"]
    D --> D3["Runtime Check of Borrowing Rules"]

    A --> E[Combination Mode]
    E --> E1["Rc&lt;RefCell&lt;T&gt;&gt;"]
    E --> E2["Shared Ownership + Internal Variability"]

    A --> F[Core Trait]
    F --> F1["Deref: Dereference Operator *"]
    F --> F2["Drop: Destructor"]

(2) مقارنة بين ثلاثة أنواع من الإبر الذكية

الخاصية Box<T> Rc<T> RefCell<T>
الملكية الملكية الفردية الملكية المشتركة (عدّ المراجع) الملكية الفردية
القابلية للتغيير قابلة للتغيير (Box::new متبوعة بـ *b = val) غير قابلة للتغيير (مشتركة للقراءة فقط) قابلة للتغيير داخليًّا (يتم التحقق منها أثناء وقت التشغيل)
التحقق من التوقيت وقت التحويل البرمجي وقت التحويل البرمجي وقت التشغيل
الأعباء الإضافية على الأداء لا توجد أعباء إضافية تغيرات في عدد المراجع (ضئيلة) عمليات التحقق من الاستعارة أثناء التشغيل (ضئيلة)
آمن للاستخدام في بيئة متعددة الخيوط نعم لا (خيط واحد) لا (خيط واحد)
حالات الاستخدام مجموعات البيانات الكبيرة المخصصة في الـ«هياب»، والأنواع التكرارية البيانات المشتركة للقراءة فقط ذات المسارات المتعددة الحاجة إلى تعديل البيانات تحت مراجع غير قابلة للتغيير

(3) سمات «Deref» و«Drop»

السمة الطريقة الدالة
Deref fn deref(&self) -> &T السماح بتطبيق عمليات إزالة الإشارة *x على الأنواع المخصصة
Drop fn drop(&mut self) يتم استدعاء منطق التنظيف تلقائيًا عندما تخرج القيمة من نطاقها

(4) مقارنة بين نماذج Box وRc وArc

الخصائص Box<T> Rc<T> Arc<T>
الملكية مالك واحد مالكون متعددون (عد المراجع) مالكون متعددون (عد المراجع الذري)
آمن للاستخدام عبر الخيوط لا (يعمل بخيط واحد فقط) لا (يعمل بخيط واحد فقط) نعم (يمكن مشاركته عبر الخيوط)
عدد المراجع لا شيء عملية غير ذرية، عبء تشغيل منخفض عملية ذرية، عبء تشغيل أعلى قليلاً
الوصول إلى المتغيرات &mut متغير مباشر يتطلب RefCell يتطلب Mutex / RwLock
السيناريوهات النموذجية الأنواع التكرارية / تخصيص الذاكرة في كومة البيانات الضخمة DAG / البيانات المشتركة للقراءة فقط مشاركة البيانات متعددة الخيوط
الأداء الأسرع المتوسط أبطأ قليلاً (العمليات الذرية)

Deref يجعل المؤشرات الذكية تعمل تمامًا مثل المراجع العادية — يمكن استخدام Box<T> تمامًا مثل &T. Drop يضمن تحرير الموارد تلقائيًا — دون الحاجة إلى free أو delete يدويًّا.



4. أمثلة على المؤشرات الذكية

(1) ▶ المثال:الصندوق — التخصيص في الكومة والأنواع التكرارية (مستوى الصعوبة ⭐)

RUST
// ============================================
// Box<T> Three Typical Uses of:Heap Allocation、Recursive Types、Deref
// ============================================

// Recursive Types:Cons list(Must use Box,Because the size is unknown at compile time)
#[derive(Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

// Custom Smart Pointers (Demo Deref and Drop)
use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0  // A reference to the internal value
    }
}

impl<T> Drop for MyBox<T> {
    fn drop(&mut self) {
        // In practical applications, resources are released here(For example, closing a file、Release the network connection)
        // println!("MyBox It was destroyed.~");
    }
}

fn main() {
    // --- 1. Box Basic Heap Allocation ---
    let b = Box::new(42);
    println!("Box the value in: {}", b);  // Automatic Dereferencing

    // --- 2. Recursive Types:Cons List ---
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
    println!("Recursive List: {:?}", list);

    // --- 3. Custom MyBox + Deref ---
    let my_box = MyBox::new(String::from("Hello"));
    // Deref lets &MyBox<String> automatically convert to &String, then to &str
    greet(&my_box);

    // --- 4. Dereference Operator ---
    let x = 10;
    let y = MyBox::new(x);
    assert_eq!(10, *y);  // *y Equivalent to *(y.deref())
    println!("*y == {}", *y);
}

fn greet(name: &str) {
    println!("Greeting: {}", name);
}

الناتج:

TEXT 📖 للعرض فقط
Box the value in: 42
Recursive List: Cons(1, Cons(2, Cons(3, Nil)))
Greeting: Hello
*y == 10

تتمثل القيمة الأساسية لـ Box<T> في جانبين: أولاً، أنها تضع البيانات في الـ heap (مع الاحتفاظ بالمؤشرات فقط في الـ stack)؛ ثانياً، أنها تسمح باستخدام الأنواع التي يكون حجمها غير معروف في وقت التحويل البرمجي (مثل الأنواع التكرارية) بشكل طبيعي. تتيح السمة Deref إمكانية إلغاء الإشارة إلى Box<T> تمامًا مثل &T — ومن هنا يأتي مصطلح «ذكي» في «المؤشر الذكي».


(2) ▶ المثال:Rc — عدّ المراجع مع الملكية المشتركة (الصعوبة ⭐⭐)

RUST
// ============================================
// Rc<T> Reference Count:Multipath Sharing of Read-Only Data
// ============================================

use std::rc::Rc;

#[derive(Debug)]
enum BookList {
    Cons(String, Rc<BookList>),
    Nil,
}

use BookList::{Cons, Nil};

fn main() {
    // --- Scene:Three books are shared by two readers ---

    // Create a Basic Reading List
    let book_c = Rc::new(Cons("Rust Programming".to_string(),
        Rc::new(Cons("Introduction to Algorithms".to_string(),
            Rc::new(Cons("Design Patterns".to_string(),
                Rc::new(Nil))))));

    println!("Initial reference count: {}", Rc::strong_count(&book_c));

    // Alice Borrowed the reading list(Rc::clone Increase the reference count only,Do not deep-copy data)
    let alice = Rc::clone(&book_c);
    println!("Alice Reference Count After Borrowing: {}", Rc::strong_count(&book_c));

    {
        // Bob I also borrowed the reading list(In the inner scope)
        let bob = Rc::clone(&book_c);
        println!("Bob Reference Count After Borrowing: {}", Rc::strong_count(&book_c));

        // Both of them can read it.
        println!("Alice Book List: {:?}", *alice);
        println!("Bob Book List: {:?}", *bob);
    }  // Bob Out of scope,Decrease in reference count

    println!("Bob Citation Count After Returning the Book: {}", Rc::strong_count(&book_c));

    // Alice Still reading
    println!("Alice Still reading: {:?}", *alice);

    // All Rc After they have all gone out of scope,Only then is the data truly destroyed
    // Rc::strong_count == 0 When triggered drop
}

الناتج:

TEXT 📖 للعرض فقط
Initial reference count: 1
Alice Reference Count After Borrowing: 2
Bob Reference Count After Borrowing: 3
Alice Book List: Cons("Rust Programming", Cons("Introduction to Algorithms", Cons("Design Patterns", Nil)))
Bob Book List: Cons("Rust Programming", Cons("Introduction to Algorithms", Cons("Design Patterns", Nil)))
Bob Citation Count After Returning the Book: 2
Alice Still reading: Cons("Rust Programming", Cons("Introduction to Algorithms", Cons("Design Patterns", Nil)))

يشير Rc<T> إلى «العدد المرجعي». لا يقوم Rc::clone(&x) بعمل نسخة عميقة للبيانات؛ بل يكتفي بزيادة العدد المرجعي بمقدار 1. ولا يتم إتلاف البيانات إلا عندما تخرج جميع معالجات Rc من نطاق الصلاحية (ويصل العدد المرجعي إلى الصفر). لا يمكن استخدام Rc إلا في بيئة أحادية الخيط — أما في حالة تعدد الخيوط، فيجب استخدام Arc<T>.


(3) ▶ المثال:RefCell — التباين الداخلي (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// RefCell<T>:Runtime Borrowing Checks + Internal Variability
// ============================================

use std::cell::RefCell;

// Simulate a "Log Entry" Messenger
// External code can only obtain immutable references, but the log needs to be modified internally.
trait Messenger {
    fn send(&self, msg: &str);
}

// Logger:For internal use only RefCell Store Messages
struct Logger {
    // Even if Logger Inherently immutable,messages It can still be edited
    messages: RefCell<Vec<String>>,
}

impl Logger {
    fn new() -> Logger {
        Logger {
            messages: RefCell::new(Vec::new()),
        }
    }
}

impl Messenger for Logger {
    fn send(&self, msg: &str) {
        // borrow_mut() gets a mutable reference — even if self is &self
        self.messages.borrow_mut().push(msg.to_string());
    }
}

fn main() {
    let logger = Logger::new();

    // Calling via an immutable reference send——But it has actually been modified internally.
    logger.send("User Login");
    logger.send("Click the button");
    logger.send("Data submitted successfully");

    // borrow() Get an immutable reference
    let msgs = logger.messages.borrow();
    for (i, msg) in msgs.iter().enumerate() {
        println!("Log #{}: {}", i + 1, msg);
    }

    // --- Examples of Runtime Borrowing Violations(Cancel the Commentary Session panic)---
    // let mut_ref = logger.messages.borrow_mut();
    // let ref1 = logger.messages.borrow();    // panic! Both mutable and immutable references
    // println!("{}", ref1[0]);

    println!("RefCell The presentation is over.");
}

الناتج:

TEXT 📖 للعرض فقط
Log #1: User Login
Log #2: Click the button
Log #3: Data submitted successfully
RefCell The presentation is over.

جوهر RefCell<T> هو «قابلية التغيير الداخلية»: حتى لو كان Logger نفسه مرجعًا ثابتًا إلى &self، فإن RefCell لا يزال يسمح لك بتعديل البيانات الداخلية. والفرق هو أن قواعد الاستعارة لا يتم التحقق منها في وقت التحويل البرمجي، بل في وقت التشغيل — فإذا تم انتهاك إحدى القواعد، فإن البرنامج سيدخل في حالة ذعر بدلاً من إصدار خطأ في وقت التحويل البرمجي.


▶ Example:Rc<RefCell<T>>—Shared + Mutable (Difficulty ⭐⭐⭐)

RUST
// ============================================
// Rc<RefCell<T>> Combination Mode:Shared Ownership + Internal Variability
// ============================================

use std::rc::Rc;
use std::cell::RefCell;

// Simulate a"Shared Whiteboard"——Team members can write and read
#[derive(Debug)]
struct Whiteboard {
    content: RefCell<String>,
}

impl Whiteboard {
    fn new() -> Whiteboard {
        Whiteboard {
            content: RefCell::new(String::new()),
        }
    }

    fn write(&self, text: &str) {
        let mut content = self.content.borrow_mut();
        content.push_str(text);
        content.push('\n');
    }

    fn read(&self) -> String {
        self.content.borrow().clone()
    }
}

fn main() {
    // Create a shared whiteboard, wrapped with Rc so it can be held by multiple people
    let board = Rc::new(Whiteboard::new());

    // Alice and Bob both hold references to the whiteboard
    let alice_board = Rc::clone(&board);
    let bob_board = Rc::clone(&board);
    let carol_board = Rc::clone(&board);

    println!("Current reference count: {}", Rc::strong_count(&board));  // 4

    // Alice Write on the whiteboard
    alice_board.write("Alice: Today's Discussion Rust Smart Pointers");
    // Bob Additional Information
    bob_board.write("Bob: I'll explain the difference between Box and Rc");
    // Carol Write as well
    carol_board.write("Carol: RefCell Runtime borrowing checks are important");

    // Everyone can view the full content(Because they share the same RefCell)
    println!("=== Whiteboard Content ===");
    println!("{}", board.read());

    // Verify that all references point to the same data
    println!("Alice Length of content viewed: {}", alice_board.read().len());
    println!("Bob Length of content viewed: {}", bob_board.read().len());
    println!("Carol Length of content viewed: {}", carol_board.read().len());

    // board When leaving the scope,Reference Count Reset to Zero,Whiteboard Automatic Destruction
}

الناتج:

TEXT 📖 للعرض فقط
Current reference count: 4
=== Whiteboard Content ===
Alice: Today's Discussion Rust Smart Pointers
Bob: I'll explain the difference between Box and Rc
Carol: RefCell Runtime borrowing checks are important

Alice Length of content viewed: 73
Bob Length of content viewed: 73
Carol Length of content viewed: 73

Rc<RefCell<T>> هو أحد أقوى أنماط البرمجة القابلة للتركيب في برمجة Rust أحادية الخيط: Rc يحل مشكلة «رغبة عدة أشخاص في الاحتفاظ» بشيء ما، بينما RefCell يحل مشكلة «رغبة الأشخاص الذين يحتفظون بمراجع غير قابلة للتغيير في تعديلها» أيضًا. وإذا قارنا Rc بـ«بطاقة مكتبة مشتركة»، فإن RefCell هي «نسخة خاصة تسمح بإضافة تعليقات توضيحية».


(5) ▶ المثال:تمرين شامل — تمثيل هياكل البيانات بيانيًّا (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:Rc<RefCell<T>> Graph Node Implementation
// ============================================

use std::rc::{Rc, Weak};
use std::cell::RefCell;
use std::fmt;

struct Node {
    value: String,
    neighbors: Vec<Weak<RefCell<Node>>>,
}

impl Node {
    fn new(value: &str) -> Rc<RefCell<Node>> {
        Rc::new(RefCell::new(Node {
            value: value.to_string(),
            neighbors: Vec::new(),
        }))
    }

    fn add_neighbor(node: &Rc<RefCell<Node>>, neighbor: &Rc<RefCell<Node>>) {
        node.borrow_mut().neighbors.push(Rc::downgrade(neighbor));
    }
}

impl fmt::Debug for Node {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let neighbor_names: Vec<String> = self.neighbors.iter()
            .filter_map(|w| w.upgrade())
            .map(|n| n.borrow().value.clone())
            .collect();
        write!(f, "{} -> {:?}", self.value, neighbor_names)
    }
}

fn main() {
    let a = Node::new("A");
    let b = Node::new("B");
    let c = Node::new("C");
    let d = Node::new("D");

    Node::add_neighbor(&a, &b);
    Node::add_neighbor(&a, &c);
    Node::add_neighbor(&b, &c);
    Node::add_neighbor(&b, &d);
    Node::add_neighbor(&c, &d);

    println!("=== Graph Structure ===");
    for node in [&a, &b, &c, &d] {
        println!("{:?}", node.borrow());
    }

    println!("\n=== Nodes Reachable from Starting Point A ===");
    if let Some(neighbor) = a.borrow().neighbors.first().and_then(|w| w.upgrade()) {
        println!("A's first neighbor: {}", neighbor.borrow().value);
        let second_hop: Vec<String> = neighbor.borrow().neighbors.iter()
            .filter_map(|w| w.upgrade())
            .map(|n| n.borrow().value.clone())
            .collect();
        println!("Take it a step further from that neighbor: {:?}", second_hop);
    }

    println!("\n=== Strong reference counting ===");
    println!("A's Rc reference count: {}", Rc::strong_count(&a));
    let a_clone = Rc::clone(&a);
    println!("After clone, A's Rc reference count: {}", Rc::strong_count(&a));
    drop(a_clone);
    println!("After drop, A's Rc reference count: {}", Rc::strong_count(&a));
}

الناتج:

TEXT 📖 للعرض فقط
=== Graph Structure ===
A -> ["B", "C"]
B -> ["C", "D"]
C -> ["D"]
D -> []

=== Nodes Reachable from Starting Point A ===
A's first neighbor: B
Take it a step further from that neighbor: ["C", "D"]

=== Strong reference counting ===
A's Rc reference count: 1
After clone, A's Rc reference count: 2
After drop, A's Rc reference count: 1

تستخدم عقد الرسم البياني Rc<RefCell<Node>> لتنفيذ الملكية المشتركة والقابلية للتغيير الداخلي؛ بينما تستخدم علاقات الجوار Weak<RefCell<Node>> لمنع تسربات الذاكرة الناتجة عن المراجع الدائرية. يقوم Rc::downgrade بإنشاء مرجع ضعيف، بينما يحاول weak.upgrade() ترقيته إلى مرجع قوي.



❓ أسئلة شائعة

س ما الفرق بين Box<T> والمرجع العادي &T؟
ج Box<T> يمتلك البيانات، بينما &T يقتصر دوره على استعارتها.
س ما الفرق بين Rc<T> وArc<T>؟
ج Rc<T> هي عملية عد المراجع أحادية الخيط، بينما Arc<T> هي عملية عد المراجع الذرية متعددة الخيوط.
س ما الفرق بين RefCell<T> وCell<T>؟
ج تُنفِّذ Cell<T> القابلية للتغيير الداخلية من خلال نسخ القيم (أو نقلها)، بينما تُنفِّذها RefCell<T> من خلال المراجع.
س متى يجب استخدام RefCell<T> بدلاً من &mut T؟
ج استخدم RefCell عندما تكون لديك مرجع غير قابل للتغيير ولكنك تحتاج إلى تعديل البيانات.
س ما الفرق في الأداء بين Rc<RefCell<T>> و&mut T؟
ج تتسبب Rc<RefCell<T>> في عبء إضافي ناتج عن عد المراجع وفحوصات الاستعارة أثناء وقت التشغيل، بينما لا تتسبب &mut T في أي عبء إضافي.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: اكتب برنامجًا يستخدم Box<T> لإنشاء قائمة Cons List متكررة (تحتوي على 3 عناصر على الأقل)، ثم يطبع القائمة بأكملها. يجب عليك استخدام #[derive(Debug)] لطباعة القائمة.
  2. الصعوبة ⭐⭐: قم بتنفيذ نظام «الملاحظة المشتركة»: استخدم Rc<RefCell<String>> للسماح لثلاثة أشخاص (أليس، وبوب، وكارول) بمشاركة سلسلة ملاحظة واحدة. يمكن للجميع قراءتها، ويمكن للجميع الكتابة فيها (إضافة محتوى). تحقق من أن الجميع يمكنهم رؤية المحتوى الكامل بعد أن ينتهي الجميع من الكتابة.
  3. الصعوبة ⭐⭐⭐: قم بتنفيذ نمط «مراقب الرسم البياني». عرّف trait Observer { fn notify(&self, msg: &str); }، ثم قم بتنفيذ LoggerObserver (الذي يستخدم RefCell<Vec<String>> داخليًّا لتسجيل الرسائل). بعد ذلك، استخدم Rc<RefCell<dyn Observer>> للسماح بإخطار عدة مراقبين من قبل نفس الموضوع. في الدالة main، أنشئ مراقبين اثنين؛ واجعل الموضوع يرسل ثلاث رسائل، وتأكد من أن كلا المراقبين قد تلقيا جميع الرسائل.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%