דילוג לתוכן המרכזי
מודול 6 · שיעור 2 מתוך 212/14 בקורס~15 min
שיעורי מודול (2/2)

מצביעים חכמים: Box ו-Rc

מצביע חכם הוא מבנה נתונים שמתנהג כמו מצביע, אך יש לו מטא נתונים ויכולות נוספות (כגון ספירת הפניות או ניהול זיכרון אוטומטי).

ב-Rust, הפניות רגילות (כגון &) שואלות רק נתונים. מצביעים חכמים, לעומת זאת, מחזיקים לעתים קרובות בנתונים שהם מצביעים עליהם. דוגמאות נפוצות כוללות את String ו-Vec<T>, אך הספרייה הסטנדרטית מספקת ספרים מיוחדים נוספים.


תיבה<T> להקצאת ערימה

המצביע החכם הפשוט ביותר הוא Box<T>, המאחסן נתונים על ערימה ולא בערימה. רק המצביע לנתונים שהוקצו בערימה נשאר בערימה.

הוא משמש בעיקר במקרים הבאים:

  1. כאשר יש לך סוג שלא ניתן לדעת את גודלו בזמן ההידור (כמו טיפוס רקורסיבי), ואתה רוצה להשתמש בערך מהסוג הזה בהקשר שדורש גודל מדויק.
  2. כאשר רוצים להעביר בעלות על כמות גדולה של נתונים מבלי להעתיק אותם.

דוגמה בסיסית:

Rust
fn main() {
    let b = Box::new(5); // allocates the value 5 on the heap
    println!("b = {}", *b); // dereferences with * to read the value
}

רשימות סוגים וחסרונות רקורסיביים

טיפוס רקורסיבי הוא טיפוס שיכול להכיל ערך של עצמו כחלק מהגדרתו. חלודה חייבת לדעת את הגודל המדויק של סוג בזמן ההידור. מכיוון שהעומק של טיפוס רקורסיבי יכול להיות אינסופי, המהדר יוצר שגיאה אלא אם כן אתה משתמש ב-Box (שיש לו גודל קבוע, מכיוון שהוא מצביע):

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

Rc<T>: ספירת הפניות

ישנם מקרים שבהם לערך בודד יכולים להיות מספר בעלים. המצביע החכם Rc<T> (ספירת הפניות) עוקב אחר מספר ההפניות לערך כדי לקבוע אם הערך עדיין בשימוש. אם הספירה יורדת לאפס, המשאב מוקצה בבטחה.

[!הערה] Rc<T> מתאים רק לתרחישים עם חוטים בודדים. עבור תרחישים מרובי-הליכים (במקביל), עליך להשתמש ב-Arc<T> (נספרת התייחסות אטומית).

Rust
use std::rc::Rc;

fn main() {
    let a = Rc::new(5);
    let b = Rc::clone(&a); // increments the reference count to 2
    println!("References: {}", Rc::strong_count(&a));
}

נסה זאת בעצמך

תרגיל 1: הקצאת ערמות עם תיבה

פעילות גופנית#rust.m6.l2.e1
ניסיונות: 0טוען...

הקצו ערך שלם של 42 בערימה באמצעות Box::new והקצו אותו למשתנה בשם val. לאחר מכן, הדפס את הערך המאוחסן ב-Box על ידי הפניה מפורשת שלו עם האופרטור *.

טוען עורך...
הצג רמז

השתמש ב-'let val = Box::new(42);' כדי לאחסן את הערך בערימה ולהגדיר אותו עם '*val' בתוך 'println!'.

הפתרון זמין לאחר 3 ניסיונות

תרגיל 2: מבנה נתונים רקורסיבי (רשימת חסרונות)

פעילות גופנית#rust.m6.l2.e2
ניסיונות: 0טוען...

הגדר רשימה רקורסיבית בשם רשימה המכיל שתי גרסאות: Cons אשר עוטף tuple (i32, Box<List>), ו-Nil המייצג את סוף הרשימה. בפונקציה הראשית, הצג רשימה מקושרת המכילה את האלמנט 1 ואחריו Nil.

טוען עורך...
הצג רמז

ה-enum מוכרז בתור `enum List { Cons(i32, Box<List>), Nil }`. הצג את הרשימה באמצעות `List::Cons(1, Box::new(List::Nil))`.

הפתרון זמין לאחר 3 ניסיונות

תרגיל 3: שיתוף בעלות עם Rc

פעילות גופנית#rust.m6.l2.e3
ניסיונות: 0טוען...

השתמש ב-std::rc::Rc כדי ליצור מספר שלם משותף המכיל את הערך 100 במשתנה a. שכבו את ההפניה למשתנה b באמצעות Rc::clone. לבסוף, הדפס את ספירת הפניות הפעילות באמצעות הפונקציה Rc::strong_count.

טוען עורך...
הצג רמז

צור את הערך עם `Rc::new(100)`. שכפל עם `Rc::clone(&a)`והצג את ספירת הבעלים עם`Rc::strong_count(&a)`.

הפתרון זמין לאחר 3 ניסיונות