Rust: المراجع والاستعارات في لغة Rust
آخر تحديث: 2026-08-26
الاقتباس يشبه بطاقة المكتبة — يمكنك استعارته (اقتباس للقراءة فقط) أو طلب إذن حصري لإعادة صياغته (اقتباس قابل للتعديل)، لكن لا يمكنك القيام بالأمرين معًا في الوقت نفسه.
تتيح آلية المراجع في لغة Rust الوصول إلى البيانات دون نقل الملكية. ويُطلق على ذلك اسم «الاستعارة».
1. ما ستتعلمه
- استخدم
&Tلإنشاء مرجع غير قابل للتغيير (استعارة للقراءة فقط) - إنشاء مرجع قابل للتعديل (استعارة للقراءة والكتابة) باستخدام
&mut T - قواعد الاستعارة: مرجع واحد قابل للتغيير أو عدة مراجع غير قابلة للتغيير (وليس كلاهما في آن واحد)
- نطاق المراجع و«NLL» (مدد الحياة غير اللغوية)
- المراجع المعلقة وإجراءات الحماية في المُجمِّع
- استخدام المراجع في معلمات الدوال
2. الرسوم التخطيطية المفاهيمية
flowchart LR
subgraph "Transfer of Ownership"
A1["Data String"] -->|"let s2 = s1<br>move"| B1["New Owner s2"]
B1 -->|"s1 Failure"| C1["Compilation Error<br>println!(s1)"]
end
subgraph "Citation, Borrowing"
A2["Data String"] -->|"let r = &s<br>borrow"| B2["Quote r"]
B2 -->|"r Return after use<br>s Still valid"| C2["✅ Secure Access"]
end
3. قصة مكتبة
(1) حسرة: تمزق الكتاب إلى أشلاء
يوجد في مكتبة «توم» كتاب شهير بعنوان البرمجة بلغة Rust، ولا يمكن استعارته إلا مرة واحدة:
- استعارت أليس هذا الكتاب، وانتقلت ملكيته إلى أليس
- أراد بوب قراءته، لكن الكتاب لم يعد موجودًا في المكتبة — فقد كان مقتنيًا لشخص واحد فقط.
- سيتعين على المكتبة شراء نسخة أخرى.
«ليتني أستطيع أن أسمح لعدة أشخاص بقراءة نفس الكتاب في الوقت نفسه دون أن يمزقوه إربًا...»
(2) حلول لمراجع Rust
fn main() {
let book = String::from("Rust Programming");
// Multiple immutable references: Multiple people at the same time "read-only" check out
let reader1 = &book; // Reader 1 checks out (read-only)
let reader2 = &book; // Reader 2 checks out (read-only)
println!("Readers 1 See: {}", reader1);
println!("Readers 2 See: {}", reader2);
// Both references are valid at the same time -- because everyone has read-only access
let mut notebook = String::from("Notebook");
// Mutable References: write-only permission
let writer = &mut notebook; // Only one person can make changes
writer.push_str("-- I wrote some notes");
println!("Rewriter: {}", writer);
// writer ends scope here, only then can others borrow it again.
}
قواعد الاقتباس تشبه قواعد المكتبة: إما أن يتمكن عدة أشخاص من القراءة في الوقت نفسه (متعدد &T)، أو أن يتمكن شخص واحد من التحرير بمفرده (واحد &mut T) — لكن لا يمكن أن يحدث الأمران في آن واحد.
4. قواعد الاستعارة
graph TB
A[Borrowing Rules] --> B[You can only choose one of them.]
B --> C[Multiple immutable references &T]
B --> D[Or a mutable reference &mut T]
B --> E[Cannot coexist]
A --> F[References must always be valid.]
F --> G[There must be no dangling references.]
| القاعدة | الوصف | عواقب المخالفة |
|---|---|---|
| عدة &T أو &mut T واحد | لا يمكن أن تتواجد المراجع الثابتة والقابلة للتغيير معًا | خطأ في الترجمة |
| يجب أن تكون المراجع صالحة | لا يمكن أن تستمر صلاحية المرجع بعد انتهاء صلاحية الكائن الذي يشير إليه | خطأ في الترجمة |
| عندما تكون المرجعية غير قابلة للتغيير، لا يمكن تعديل القيمة الأصلية أيضًا | في حالة وجود &T، لا يمكن تعديل القيمة الأصلية عبر &mut T | خطأ في التحويل البرمجي |
| نطاق NLL | ينتهي نطاق &mut T بعد آخر استخدام له | تحسين يعتمد على المُجمِّع |
(2) مقارنة أنواع المراجع
| نوع الاقتباس | الصيغة | الأذونات | الحد الأقصى المسموح به | الاستخدامات الشائعة |
|---|---|---|---|---|
| مرجع ثابت | &T |
للقراءة فقط | متعدد | وصول للقراءة فقط إلى معلمات الدالة |
| مرجع المتغير | &mut T |
قراءة/كتابة | 1 | معلمة الدالة تُعدِّل البيانات |
| مؤشر فات (شريحة) | &[T] |
للقراءة فقط | متعددة | شريحة مصفوفة/سلسلة |
(3) مرجع سريع لسيناريوهات الاقتراض
| السيناريو | النهج الموصى به | مثال |
|---|---|---|
| الوصول إلى الوظائف للقراءة فقط | &T |
fn len(s: &String) -> usize |
| الدوال التي تُعدِّل البيانات | &mut T |
fn push(s: &mut String, ch: char) |
| استخدام بيانات الدالة | T (التمرير بالقيمة) |
fn consume(s: String) |
| بيانات إرجاع الدالة | T (قيمة الإرجاع) |
fn create() -> String |
| وظائف متعددة تشترك في الوصول للقراءة فقط | &T |
وظائف متعددة تتلقى نفس &T |
| التعديلات المتناوبة | &mut T (التعدد الزمني) |
تتيح تقنية NLL إنشاء &mut T بالتناوب |
5. أمثلة على الاستشهادات
(1) ▶ المثال:المراجع الثابتة — الوصول للقراءة فقط (مستوى الصعوبة ⭐⭐)
// ============================================
// Immutable References &T: Multiple readers can borrow items at the same time
// ============================================
fn calculate_length(s: &String) -> usize {
// s is a String reference, no ownership
s.len()
} // s goes out of scope, but since it's a reference, it will not destroy the String
fn main() {
let s = String::from("Hello, Rust!");
let len = calculate_length(&s); // Borrow s, no transfer of ownership
println!("'{}' The length of is: {}", s, len); // ✅ s Still available
// Multiple immutable references can coexist
let r1 = &s;
let r2 = &s;
println!("r1: {}, r2: {}", r1, r2); // ✅ Read simultaneously
}
الناتج:
'Hello, Rust!' The length of is: 12
r1: Hello, Rust!, r2: Hello, Rust!
&sتنشئ مرجعًا إلىs؛ وعند تمريرها إلى دالة، لا يتم نقل الملكية. وبعد عودة الدالة، يصبح المرجع غير صالح، لكنsلا تزال موجودة. وهذا ما يُعرف بـ«الاستعارة» — أي أنك تستخدمها ثم تعيدها.
(2) ▶ المثال:المراجع القابلة للتغيير — أذونات التعديل الحصرية (مستوى الصعوبة ⭐⭐)
// ============================================
// Mutable References &mut T: Only one writer at a time
// ============================================
fn main() {
let mut s = String::from("Hello");
// Create a mutable reference
let r = &mut s;
r.push_str(", world!");
println!("Modifying Through a Variable Reference: {}", r);
// r is used here for the last time, NLL ends
// After the mutable reference ends, you can create a new mutable reference.
let r2 = &mut s;
r2.push_str("!!");
println!("Another mutable reference: {}", r2);
// --- Examples of Errors (Uncomment to view compilation errors) ---
// let mut s2 = String::from("test");
// let ref1 = &mut s2;
// let ref2 = &mut s2; // ❌ There cannot be two mutable references at the same time.
// println!("{}, {}", ref1, ref2);
}
الناتج:
Modifying Through a Variable Reference: Hello, world!
Another mutable reference: Hello, world!!!
&mutقابلة للتعديل — أي يمكن تعديل قيمة المرجع. ومع ذلك، تفرض لغة Rust قاعدة مفادها أنه لا يمكن وجود سوى مرجع واحد قابل للتعديل في كل مرة. وهذا يمنع حدوث تضارب في الوصول إلى البيانات — دون الحاجة إلى أقفال أو عمليات ذرية؛ حيث يتم حل المشكلة بالكامل في مرحلة الترجمة.
(3) ▶ المثال:لا يمكن أن تتعايش المراجع الثابتة والمتغيرة (الصعوبة ⭐⭐⭐)
// ============================================
// Demonstration of Conflicts Between Immutable and Mutable References
// ============================================
fn main() {
let mut data = String::from("Key Data");
let r1 = &data; // ✅ Immutable References 1
let r2 = &data; // ✅ Immutable References 2
println!("r1: {}, r2: {}", r1, r2);
// r1 and r2 are used here for the last time
let r3 = &mut data; // ✅ At this point, you can create a mutable reference.
r3.push_str("--Modified");
println!("r3: {}", r3);
// --- Examples of Errors: A mutable reference cannot be created while an immutable reference still exists. ---
// let mut s = String::from("Demo");
// let r_a = &s; // Immutable References
// let r_b = &mut s; // ❌ Compilation Error: immutable reference already exists
// println!("{}, {}", r_a, r_b);
}
الناتج:
r1: Key Data, r2: Key Data
r3: Key Data--Modified
النقطة الأساسية: تخرج المرجعان غير القابلان للتغيير
r1وr2من نطاقهما بعد آخر استخدام لهما (println!)، وعندها فقط يمكن إنشاء المرجع القابل للتغييرr3. وهذا هو NLL (Non-Lexical Lifetime) — حيث يحدد المُجمِّع بذكاء متى تصبح المرجعية غير مستخدمة، بدلاً من انتظار نهاية الكتلة.
(4) ▶ المثال:منع حدوث «المراجع المعلقة» (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// How Do Compilers Prevent Dangling References?
// ============================================
// Examples of Errors: Return a reference to a local variable
// fn dangle() -> &String {
// let s = String::from("hello");
// &s // ❌ Compilation Error: s is destroyed at the end of the function, the reference would be dangling
// }
// The Correct Approach: Return String directly (transfer of ownership)
fn no_dangle() -> String {
let s = String::from("hello");
s // Return String directly, ownership is transferred to the caller
}
fn main() {
let s = no_dangle();
println!("Safe Return Values: {}", s);
// The Correct Way to Reference Local Variables: Use within the scope
let local = String::from("Local Data");
let r = &local; // Quote
println!("Referencing Local Variables: {}", r);
// r ends here, local is still valid
} // local is destroyed here (later than r), safe
الناتج:
Safe Return Values: hello
Referencing Local Variables: Local Data
تحدث «الإشارة المعلقة» عندما يتم تحرير الذاكرة التي تشير إليها إحدى الإشارات. يمكن لمُترجم لغة Rust اكتشاف هذه الحالة في مرحلة الترجمة — فإذا تم تدمير الكائن الذي تشير إليه إحدى الإشارات بعد إنشاء هذه الإشارة، فسيُبلغ المُترجم عن وجود خطأ. وهذا يقضي تمامًا على الأخطاء مثل «المؤشرات المعلقة».
(5) ▶ المثال:تمرين شامل — معاملات الحساب المصرفي (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Comprehensive Example: Applications of References in Real-Life Scenarios
// ============================================
struct Account {
owner: String,
balance: f64,
}
fn check_balance(account: &Account) -> String {
format!("{} balance: {:.2} yuan", account.owner, account.balance)
}
fn deposit(account: &mut Account, amount: f64) {
if amount <= 0.0 {
println!("The deposit amount must be greater than 0");
return;
}
account.balance += amount;
println!("Deposit {:.2} Yuan Chenggong", amount);
}
fn transfer(from: &mut Account, to: &mut Account, amount: f64) -> bool {
if amount <= 0.0 || from.balance < amount {
println!("Transfer Failed: Insufficient balance or invalid amount");
return false;
}
from.balance -= amount;
to.balance += amount;
println!("Transfer {:.2} yuan: {} -> {}", amount, from.owner, to.owner);
true
}
fn show_accounts(accounts: &[Account]) {
println!("--- Account List ---");
for (i, acc) in accounts.iter().enumerate() {
println!("{}. {} balance: {:.2} yuan", i + 1, acc.owner, acc.balance);
}
}
fn main() {
let mut alice = Account { owner: String::from("Alice"), balance: 1000.0 };
let mut bob = Account { owner: String::from("Bob"), balance: 500.0 };
println!("{}", check_balance(&alice));
println!("{}", check_balance(&bob));
deposit(&mut alice, 200.0);
println!("{}", check_balance(&alice));
transfer(&mut alice, &mut bob, 300.0)?;
let accounts = [&alice, &bob];
show_accounts(&accounts);
let total: f64 = accounts.iter().map(|a| a.balance).sum();
println!("Total Deposits: {:.2} yuan", total);
}
الناتج:
Alice balance: 1000.00 yuan
Bob balance: 500.00 yuan
Deposit 200.00 Yuan Successful
Alice balance: 1200.00 yuan
Transfer 300.00 yuan: Alice -> Bob
--- Account List ---
1. Alice balance: 900.00 yuan
2. Bob balance: 800.00 yuan
Total Deposits: 1700.00 yuan
يوضح هذا المثال التطبيق العملي لأنواع المراجع الثلاثة:
&T(استعلام للقراءة فقط)، و&mut T(عملية تعديل)، و&[T](تجول للقراءة فقط). يتطلبtransferمرجعين من نوع&mut Tفي آن واحد، وهو أمر آمن في لغة Rust لأنهما يشيران إلى بيانات مختلفة.
❓ أسئلة شائعة
📖 ملخص
- تتيح لك ميزة المرجع الوصول إلى البيانات دون نقل ملكيتها — أي عن طريق «استعارتها».
- مرجع ثابت
&T: يمكن أن توجد عدة مثيلات في آن واحد؛ للقراءة فقط - مرجع متغير
&mut T: واحد فقط في كل مرة؛ للقراءة/الكتابة - قواعد الاستعارة: لا يمكن أن يتواجد
&Tمتعدد أو&mut Tواحد في آن واحد - يجب أن تكون المراجع صالحة دائمًا؛ حيث يمنع المُترجم وجود المراجع المعلقة
- تؤدي NLL إلى إنهاء المرجع بعد آخر استخدام له، بدلاً من الانتظار حتى نهاية الكتلة
📝 تمارين
- الصعوبة ⭐: اكتب دالة
fn print_message(msg: &String)تعرض السلسلة التي تُمرر إليها. فيmain، أنشئStringثم استدعِ هذه الدالة للتحقق من أن المتغير الأصلي لا يزال متاحًا بعد الاستدعاء. - الصعوبة ⭐⭐: اكتب برنامجًا لإنشاء
mut String. أولاً، أنشئ مرجعين غير قابلين للتعديل واعرضهما؛ ثم أنشئ مرجعًا قابلاً للتعديل، وقم بتعديل محتواه، واعرض النتيجة المعدلة. - الصعوبة ⭐⭐⭐: حاول كتابة دالة تُرجع مرجعًا (مثل
fn get_ref() -> &String)، ولاحظ رسائل الخطأ التي يعرضها المُترجم. ثم عدّلها بحيث تُرجع ملكيةString، وافهم سبب منع لغة Rust إرجاع المراجع إلى المتغيرات المحلية.