Rust: المؤشرات الذكية في Rust
آخر تحديث: 2026-08-26
المؤشر الذكي هو «بنية بيانات ذات سلوك إضافي» — فهو يشير إلى الذاكرة تمامًا مثل المؤشر العادي، لكنه يوفر إمكانيات تتجاوز تلك التي يوفرها المؤشر العادي، مثل الإدارة التلقائية للذاكرة، وعدّ المراجع، والتحقق من الاستعارة.
إذا كان المؤشر العادي يشبه «ورقة مكتوب عليها عنوان»، فإن المؤشر الذكي يشبه «نظام إدارة عقارات مزود بحراس أمن خاصين به، وعمال نظافة، ودفتر حسابات». تضمن المؤشرات الذكية في لغة Rust السلامة في وقت التحويل البرمجي ولا تتسبب في أي عبء إضافي في وقت التشغيل (بصرف النظر عن العبء الضئيل الناجم عن عد المراجع).
1. ما ستتعلمه
Box<T>تخصيص الذاكرة في الكومة ونقل الملكية- السمة
Deref— إعادة تحميل عامل إزالة الإشارة - السمة
Drop— التنظيف التلقائي عبر دالات التدمير Rc<T>عد المراجع — المشاركة متعددة المالكين أحادية الخيطRefCell<T>التباين الداخلي — عمليات التحقق من الاستعارة أثناء وقت التشغيلRc<RefCell<T>>الوضع المركب — مشترك + قابل للتعديل
2. قصة المكاتب المشتركة
(1) المشكلة: يرغب عدة مستخدمين في عرض نفس المستند وتحريره في الوقت نفسه
في صباح يوم الاثنين، طبقت شركة «لونا» سياسة «المكاتب المشتركة». ولم يكن هناك سوى ثلاثة مكاتب لفريق مكون من خمسة أشخاص.
- تحتاج أليس إلى الاطلاع على مقترح مشروع (PDF)، لكنها لا تريد طباعة نسخة بنفسها — بل تريد الاطلاع على النسخة التي بحوزة شخص آخر.
- يجب على بوب أن يراجع هذا الاقتراح أيضًا، ويجب عليه تدوين ملاحظات عليه.
- تريد كارول الاطلاع عليه في نفس الوقت، لكنها تريد قراءته فقط — دون تعديله.
- المشكلة هي: إذا انتهت أليس من قراءة الوثيقة وقامت بإتلافها، بينما لا يزال بوب وكارول يقرآنها — فهنا تبدأ المشاكل.
- وما زاد الطين بلة، أنه بينما كان بوب يُجري تعديلات، كانت أليس تُجري تعديلات هي الأخرى — فاصبحت الصفحة في حالة فوضى.
«لو كنا نعرف فقط عدد الأشخاص الذين ما زالوا يطالعون هذا المستند، لكان بإمكاننا إتلافه بمجرد أن يتوقف الجميع عن الاطلاع عليه...» «لو كان بإمكاننا ضمان أنه عندما يقوم شخص ما بتحريره، لا يستطيع أي شخص آخر إجراء تغييرات عليه...»
(2) حلول المؤشرات الذكية في لغة 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) نظام المؤشر الذكي
graph TB
A[Rust Smart Pointers] --> B[Box<T> Heap Allocation]
A --> C[Rc<T> Reference Count]
A --> D[RefCell<T> 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<RefCell<T>>"]
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) ▶ المثال:الصندوق — التخصيص في الكومة والأنواع التكرارية (مستوى الصعوبة ⭐)
// ============================================
// 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);
}
الناتج:
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 — عدّ المراجع مع الملكية المشتركة (الصعوبة ⭐⭐)
// ============================================
// 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
}
الناتج:
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 — التباين الداخلي (مستوى الصعوبة ⭐⭐)
// ============================================
// 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.");
}
الناتج:
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 ⭐⭐⭐)
// ============================================
// 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
}
الناتج:
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) ▶ المثال:تمرين شامل — تمثيل هياكل البيانات بيانيًّا (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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));
}
الناتج:
=== 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 في أي عبء إضافي.📖 ملخص
Box<T>هو مؤشر ذكي مخصص في الـ«هياب» يُستخدم للأنواع التكرارية ومجموعات البيانات الكبيرة وكائنات السمات — وهو يضمن ملكية فريدة دون أي عبء إضافي.Derefتتيح للمؤشرات الذكية دعم عامل إزالة الإشارة*، بينما تضمن السمةDropإزالة الموارد تلقائيًا عند انتهاء نطاقهاRc<T>هو مؤشر ذكي يعتمد على عدّ المراجع، ويتيح الملكية المشتركة في بيئة أحادية الخيط — حيث يقومRc::cloneبزيادة العدد فقط دون نسخ البياناتRefCell<T>يوفر قابلية التغيير الداخلية، مما يؤجل عمليات التحقق من الاستعارة من وقت التحويل البرمجي إلى وقت التشغيل — ويؤدي انتهاك القواعد إلى حدوث حالة ذعرCell<T>تنفيذ قابلية التغيير الداخلية باستخدام نسخ القيم (يتطلبT: Copy)؛RefCell<T>ينفذها باستخدام المراجع (يُسمح بأي نوع)Rc<RefCell<T>>الوضع المركب = الملكية المشتركة + قابلية التغيير الداخلية، هو الحل الأكثر شيوعًا لمشاركة البيانات بمرونة في البرمجة أحادية الخيط بلغة Rust
📝 تمارين
- الصعوبة ⭐: اكتب برنامجًا يستخدم
Box<T>لإنشاء قائمةCons Listمتكررة (تحتوي على 3 عناصر على الأقل)، ثم يطبع القائمة بأكملها. يجب عليك استخدام#[derive(Debug)]لطباعة القائمة. - الصعوبة ⭐⭐: قم بتنفيذ نظام «الملاحظة المشتركة»: استخدم
Rc<RefCell<String>>للسماح لثلاثة أشخاص (أليس، وبوب، وكارول) بمشاركة سلسلة ملاحظة واحدة. يمكن للجميع قراءتها، ويمكن للجميع الكتابة فيها (إضافة محتوى). تحقق من أن الجميع يمكنهم رؤية المحتوى الكامل بعد أن ينتهي الجميع من الكتابة. - الصعوبة ⭐⭐⭐: قم بتنفيذ نمط «مراقب الرسم البياني». عرّف
trait Observer { fn notify(&self, msg: &str); }، ثم قم بتنفيذLoggerObserver(الذي يستخدمRefCell<Vec<String>>داخليًّا لتسجيل الرسائل). بعد ذلك، استخدمRc<RefCell<dyn Observer>>للسماح بإخطار عدة مراقبين من قبل نفس الموضوع. في الدالةmain، أنشئ مراقبين اثنين؛ واجعل الموضوع يرسل ثلاث رسائل، وتأكد من أن كلا المراقبين قد تلقيا جميع الرسائل.