Rust: ملكية Rust: المبادئ الأساسية الثلاثة لأمان الذاكرة
آخر تحديث: 2026-08-26
تعد «الملكية» السمة الأكثر تميزًا في لغة Rust — فهي تتيح لهذه اللغة ضمان أمان الذاكرة دون الحاجة إلى أداة جمع القمامة.
تعد «الملكية» الفرق الأساسي بين لغة «Rust» واللغات الأخرى. وبمجرد فهمها، فإنك تفهم جوهر تصميم لغة «Rust».
1. ما ستتعلمه
- المبادئ الثلاثة للملكية: لكل قيمة مالك واحد فقط
- يتم تحرير القيم (إلغاؤها) تلقائيًا عندما تخرج من نطاقها
- معنى كلمة «نقل»: انتقال الملكية
- الفرق بين السمتين
CopyوClone - طرق تخزين البيانات في المكدسات (Stacks) والكومات (Heaps)
- نقل الملكية في استدعاءات الدوال
2. الرسوم التخطيطية المفاهيمية
flowchart LR
A["Value Creation<br>let s = String::from(...)"] --> B["Owner Binding<br>s Has this value"]
B --> C["Transfer of Ownership move<br>let s2 = s; s Failure"]
C --> D["Out of scope drop<br>} Automatic Memory Release"]
D --> E["Memory Security<br>No Double Release,Non-swinging pointer"]
3. قصة مكتبة
(1) المأزق: استعار شخصان الكتاب في نفس الوقت
يدير توم مكتبة صغيرة. وقد واجه مشكلة شائعة:
- استعارت أليس نسخة من كتاب البرمجة بلغة Rust وقرأت حتى الصفحة 50.
- أراد بوب نفس الكتاب، لذا أعاره توم مرة أخرى.
- اتضح أن أليس كتبت ملاحظة في الصفحة 50، وأن بوب كتب ملاحظة في الصفحة 100.
- وعندما أُعيد الكتاب أخيرًا، كانت الملاحظات مختلطةً مع بعضها البعض، وكان من المستحيل معرفة من كتبها.
- وما زاد الطين بلة أن شخصين قاما بتحرير نفس الصفحة في الوقت نفسه، مما أدى إلى تجزئة الصفحة.
«لو كان من الممكن أن يستعير الكتاب شخص واحد فقط في كل مرة...»
(2) قواعد الملكية في Rust
fn main() {
let book = String::from("Rust Programming"); // book is the owner of this book
// let book2 = book; // ❌ If you write it this way,book Ownership was transferred to book2
// println!("{}", book); // ❌ book Can no longer be used
// ✅ The Correct Approach:Only one person can hold ownership at a time.
println!("{} Belongs to Tom The Library", book);
} // book Automatically destroyed here——No manual intervention required free
قواعد الملكية في لغة Rust تشبه سياسة «شخص واحد في كل مرة» المتبعة في المكتبات: لكل قيمة مالك واحد فقط في أي وقت معين. وعندما يخرج المالك من نطاق الصلاحية، يتم إتلاف القيمة تلقائيًا.
4. المبادئ الثلاثة للملكية
graph TB
A[The Three Principles of Ownership] --> B[Principles1: Each value has exactly one owner.]
A --> C[Principles2: It is destroyed as soon as it leaves its scope.]
A --> D[Principles3: Ownership can be transferred(move)]
B --> E[let s = String::from("hi")]
C --> F[} Automatically call at the end drop]
D --> G[let s2 = s; // s No longer valid]
(1) شرح مفصل للمبادئ
| المبدأ | الوصف | القياس |
|---|---|---|
| مالك واحد | في أي وقت من الأوقات، لا «يمتلك» كل قيمة سوى شخص واحد | لا يمكن استعارة كتاب من المكتبة إلا لشخص واحد في كل مرة |
| نهاية النطاق = التدمير | يتم استدعاؤها تلقائيًا عندما يخرج المتغير من نطاقه drop |
يتم إرجاع الكتب تلقائيًا عند حلول موعد استحقاقها |
| نقل الملكية (النقل) | يتم نقل الملكية أثناء التعيين أو تمرير المعلمات؛ وتصبح المتغير الأصلي غير صالح | يتم تمرير كتاب من شخص إلى آخر |
5. المكدسات والكومات
لفهم مفهوم الملكية، يجب عليك أولاً فهم الفرق بين المكدس (stack) والكومة (heap):
| البعد | المكدس | الكومة |
|---|---|---|
| سرعة التخصيص | سريعة جدًا (push/pop) | أبطأ (تتطلب العثور على ذاكرة خالية) |
| تخزين البيانات | بيانات ذات حجم معروف وقت التحويل البرمجي | بيانات ذات حجم غير معروف أو متغير ديناميكيًا |
| الأنواع النموذجية | i32، bool، f64، Array |
String، Vec، Box |
| إدارة الذاكرة | تلقائية (إطارات مكدس استدعاءات الدوال) | تتطلب إدارة يدوية أو إدارة قائمة على الملكية |
(3) مقارنة بين «النسخ» و«الاستنساخ» و«النقل»
| العملية | الصيغة | هل المتغير الأصلي صالح؟ | العبء على الأداء | الأنواع القابلة للتطبيق |
|---|---|---|---|---|
| نسخ | let b = a; |
صالح | منخفض جدًّا (نسخة بتية للمكدس) | i32، f64، bool، char، المجموعات (بما في ذلك أنواع النسخ) |
| نسخة | let b = a.clone(); |
صالحة | عالية (تخصيص ذاكرة المكدس) | String، Vec<T>، Box<T> |
| نقل | let b = a; |
تنتهي صلاحيتها | 0 (تنسخ المؤشر فقط) | String، Vec<T>، Box<T> |
إذا كان النوع يُنفِّذ
Copy، فإن عملية التعيين تؤدي إلى نسخ تلقائي؛ أما إذا لم يُنفِّذCopy، فإن عملية التعيين تؤدي إلى نقل تلقائي. استخدم.clone()لإجراء نسخ عميق صريح عندما تحتاج إلى الحفاظ على المتغير الأصلي.
fn main() {
// Data on the Stack:Fixed size,Copy Semantics
let x: i32 = 5; // Allocation on the Stack 4 Byte
let y = x; // Make a copy, x and y are both valid.
println!("x={}, y={}", x, y); // ✅ All are acceptable
// Load the data:Varies in size,move Semantics
let s1 = String::from("hello"); // s1 On the stack(ptr/len/cap),The actual data is on the heap.
let s2 = s1; // ❌ Ownership from s1 Transfer to s2
// println!("{}", s1); // ❌ Compilation Error:s1 Has been moved
println!("{}", s2); // ✅ s2 It's the new owner.
}
6. أمثلة على الملكية
(1) ▶ المثال:دلالة "move" — نقل الملكية (مستوى الصعوبة ⭐⭐)
// ============================================
// Demonstration of Ownership Transfer:Occurs during assignment move
// ============================================
fn main() {
let s1 = String::from("Rust");
let s2 = s1; // s1 Ownership was transferred to s2
// println!("s1: {}", s1); // ❌ Compilation Error!s1 Has been moved
println!("s2: {}", s2); // ✅ s2 I am the owner now
// Integer types are Copy, So it won't move
let a = 42;
let b = a; // a The value is copied to b
println!("a: {}, b: {}", a, b); // ✅ Both are effective.
}
الناتج:
s2: Rust
a: 42, b: 42
الأنواع القياسية مثل الأعداد الصحيحة تُنفِّذ السمة
Copy، لذا فإن عمليات التعيين تنطوي على النسخ بدلاً من النقل. أماStringفلا تُنفِّذCopy، لذا فإن عمليات التعيين تنطوي على النقل. بعد عملية النقل، يصبح المتغير الأصلي غير صالح؛ وهذه ميزة تصميمية أساسية في لغة Rust تهدف إلى منع «التحرير المزدوج».
(2) ▶ المثال:تمرير الملكية في استدعاءات الدوال (مستوى الصعوبة ⭐⭐)
// ============================================
// Transfer of Ownership in Parameter Passing and Return Values
// ============================================
fn take_ownership(s: String) {
println!("Acquire ownership: {}", s);
} // s Destroyed here(drop)
fn give_ownership() -> String {
let s = String::from("Newly created string");
s // Ownership is returned to the caller
}
fn main() {
let s1 = String::from("hello");
take_ownership(s1); // s1 Ownership was transferred to the function
// println!("{}", s1); // ❌ s1 Expired
let s2 = give_ownership(); // Acquiring Ownership from a Function
println!("s2: {}", s2); // ✅ s2 Ownership
// Send it in and then send it back
let s3 = String::from("Passing back and forth");
let s3_back = takes_and_returns(s3);
// println!("{}", s3); // ❌ s3 Has been moved
println!("s3_back: {}", s3_back); // ✅
}
fn takes_and_returns(s: String) -> String {
println!("Inside a function: {}", s);
s // Return ownership to the caller
}
الناتج:
Acquire ownership: hello
s2: Newly created string
Inside a function: Passing back and forth
s3_back: Passing back and forth
عند استدعاء دالة ما، تنتقل ملكية المعلمات التي تم تمريرها إلى الدالة. وعندما تُرجع الدالة قيمةً ما، تنتقل الملكية مرةً أخرى إلى المُستدعي. وهذا هو المقصود بعبارة «تدفق الملكية بين الدوال» — وهو نفس مبدأ «من يملك المفتاح يمكنه فتح الباب».
(3) ▶ المثال:مقارنة بين «النسخ» و«الاستنساخ» (مستوى الصعوبة: ⭐⭐⭐)
// ============================================
// Copy(Auto-Copy) vs Clone(Explicit Cloning)
// ============================================
fn main() {
// --- Copy Type:Automatic Copy on Assignment ---
let num1 = 100;
let num2 = num1; // Auto-Copy,num1 Still valid
println!("Copy: num1={}, num2={}", num1, num2); // ✅
// --- Clone Type:Must be explicitly called .clone() ---
let s1 = String::from("Cloning required");
let s2 = s1.clone(); // Explicit Cloning,s1 Still valid
println!("Clone: s1={}, s2={}", s1, s2); // ✅
// --- Neither Clone Nor Copy: only move ---
let v1 = vec![1, 2, 3];
let v2 = v1; // move!v1 Failure
// println!("{:?}", v1); // ❌
println!("v2: {:?}", v2); // ✅
}
الناتج:
Copy: num1=100, num2=100
Clone: s1=Cloning required, s2=Cloning required
v2: [1, 2, 3]
النسخ: يقوم تلقائيًا بنسخ البيانات بتًا بتًا أثناء عملية التعيين؛ ويظل المتغير الأصلي ساريًا (ينطبق على البيانات الموجودة في المكدس). الاستنساخ: يتطلب استدعاءً صريحًا لطريقة
.clone()؛ وينطبق على البيانات الموجودة في الكومة. عملية الاستنساخ أكثر تكلفة (تتطلب تخصيص ذاكرة في الكومة)، لذا صُممت لغة Rust بحيث «يجب عليك اختيار الاستنساخ صراحةً».
(4) ▶ المثال:أنماط الحصول على الملكية وإعادتها في الدوال (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Common Patterns:Send it in and then send it back(Get+Return)
// ============================================
struct User {
name: String,
score: i32,
}
fn boost_score(mut user: User, bonus: i32) -> User {
user.score += bonus;
user
}
fn format_user(user: &User) -> String {
format!("{}: {} pts", user.name, user.score)
}
fn main() {
let alice = User {
name: String::from("Alice"),
score: 80,
};
println!("Before the upgrade: {}", format_user(&alice));
let alice = boost_score(alice, 15);
println!("After the upgrade: {}", format_user(&alice));
let alice = boost_score(alice, 10);
println!("Upgrade Again: {}", format_user(&alice));
let mut bob = User {
name: String::from("Bob"),
score: 60,
};
let bonus = 20;
bob.score += bonus;
println!("Bob After the bonus points are added: {}", format_user(&bob));
let names = vec![alice.name.clone(), bob.name.clone()];
println!("All users: {:?}", names);
println!("Alice Final Score: {}", alice.score);
println!("Bob Final Score: {}", bob.score);
}
الناتج:
Before the upgrade: Alice: 80 pts
After the upgrade: Alice: 95 pts
Upgrade Again: Alice: 105 pts
Bob After the bonus points are added: Bob: 80 pts
All users: ["Alice", "Bob"]
Alice Final Score: 105
Bob Final Score: 80
يتم نقل ملكية الهياكل بنفس الطريقة المتبعة مع الأنواع الأساسية.
boost_scoreيتم الحصول على الملكية من خلال معلمة ويتم إرجاعها بعد التعديل — وهذا هو نمط «الحصول والإرجاع». ويتيح استخدام المرجع (&User) الوصول للقراءة فقط دون الحصول على الملكية.
(5) ▶ المثال:التفاعل بين الملكية والمجموعات (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Vec,String Common Interaction Patterns with Ownership
// ============================================
fn take_first_word(words: &Vec<String>) -> Option<&str> {
words.first().map(|s| s.as_str())
}
fn remove_and_return(vec: &mut Vec<i32>, index: usize) -> Option<i32> {
if index < vec.len() {
Some(vec.remove(index))
} else {
None
}
}
fn main() {
let mut words = vec![
String::from("hello"),
String::from("rust"),
String::from("world"),
];
println!("Original vector: {:?}", words);
if let Some(first) = take_first_word(&words) {
println!("The First Word: {}", first);
}
let removed = remove_and_return(&mut words, 1);
println!("Remove Index 1: {:?}, Remaining: {:?}", removed, words);
let mut numbers = vec![10, 20, 30, 40, 50];
while let Some(val) = numbers.pop() {
println!("Pop up: {}", val);
}
println!("The vector is empty: {:?}", numbers);
let s1 = String::from("foo");
let s2 = String::from("bar");
let combined = s1 + &s2;
println!("Stitching Results: {}", combined);
let data = vec![String::from("a"), String::from("b")];
for word in &data {
println!("Borrow: {}", word);
}
println!("The vector is still available: {:?}", data);
let data2 = vec![1, 2, 3];
for num in data2 {
println!("Consumption: {}", num);
}
}
الناتج:
Original vector: ["hello", "rust", "world"]
The First Word: hello
Remove Index 1: Some(20), Remaining: ["hello", "world"]
Pop up: 50
Pop up: 40
Pop up: 30
Pop up: 20
Pop up: 10
The vector is empty: []
Stitching Results: foobar
Borrow: a
Borrow: b
The vector is still available: ["a", "b"]
Consumption: 1
Consumption: 2
Consumption: 3
for item in &vecعملية تجول «استعارة» (يظل المتجه متاحًا)،for item in vecعملية تجول «استهلاك» (يتم نقل المتجه).+يستهلك العامل السلسلة الموجودة على اليسار.pop()يُرجعOption<T>ويستهلك العنصر الأخير.
❓ أسئلة شائعة
move؟.clone() أم move؟move أي عبء إضافي، بينما تتطلب clone تخصيص ذاكرة من الكومة.📖 ملخص
- الملكية في Rust تستند إلى ثلاثة مبادئ أساسية: مالك واحد، والتدمير التلقائي عند انتهاء نطاق المتغير، وقابلية نقل الملكية.
- تتبع البيانات الموجودة في المكدس (الأنواع القياسية) دلالات النسخ؛ حيث تؤدي عملية التعيين إلى نسخ القيمة تلقائيًا.
- تتبع البيانات الموجودة في الـ«هياب» (String، Vec) دلالات النقل؛ حيث يؤدي التعيين إلى نقل الملكية.
- بعد إعادة التخصيص، تصبح المتغير الأصلي غير صالح، ويمنع المُترجم الوصول إليه — وهذا فحص يتم في وقت الترجمة
- كما أن استدعاءات الدوال والقيم المرجعة تنقل الملكية أيضًا
.clone()نسخ عميق صريح يحافظ على المتغير الأصلي — ولكن على حساب الأداء
📝 تمارين
- الصعوبة ⭐: اكتب برنامجًا يقوم بإنشاء متغير
String، وتعيينه إلى متغير آخر، ثم يحاول طباعة المتغير الأول — لاحظ رسالة الخطأ التي يعرضها المُترجم. - الصعوبة ⭐⭐: اكتب دالة
fn append_world(s: String) -> Stringتضيف كلمة "world" في نهاية سلسلة أحرف وتُرجعها. استدعِها فيmainولاحظ كيف يتم تمرير الملكية إلى الدالة ومنها. - الصعوبة ⭐⭐⭐: عرّف دالة
fn calculate_length(s: String) -> (String, usize)تُرجع سلسلة نصية وطولها، مع الاحتفاظ بملكية السلسلة النصية. تأكد من أنه لا يزال من الممكن استخدام المتغير الأصلي بعد استدعاء الدالة.