ראסט (שפת תכנות)

ראסט
Rust
פרדיגמות systems programming, תכנות גנרי, תכנות אימפרטיבי, concurrent computing, תכנות פונקציונלי, תכנות מונחה-עצמים עריכת הנתון בוויקינתונים
תאריך השקה 2006 עריכת הנתון בוויקינתונים
מתכנן Graydon Hoare עריכת הנתון בוויקינתונים
מפתח Graydon Hoare, מוזילה, בפסקה זו רשומה אחת נוספת שטרם תורגמה עריכת הנתון בוויקינתונים
הושפעה על ידי OCaml, Newsqueak, Ruby, Scheme, Standard ML, לימבו, ארלנג, C++, Haskell, סי שארפ, Cyclone, Swift, בפסקה זו רשומה אחת נוספת שטרם תורגמה עריכת הנתון בוויקינתונים
השפיעה על Crystal, Elm, Idris, Spark, Swift, Project Verona, Zig, PHP
רישיון אפאצ'י 2.0, רישיון MIT עריכת הנתון בוויקינתונים
סיומת rs, rlib
www.rust-lang.org
לעריכה בוויקינתונים שמשמש מקור לחלק מהמידע בתבנית

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

שפת ראסט משלבת יכולת ביצועים גבוהים כמו של שפת C. בשפה אין איסוף זבל, ובמקום זאת היא משתמשת במנגנון בעלות על ערכים, מה שמשפר את הביצועים של תוכנות בשפה.

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

התחביר של השפה פשוט וקל ללמידה.

השפה זכתה בתואר "שפת התכנות האהובה ביותר" לשנים 2016 עד 2021 בסקר קהילת המתכנתים של StackOverflow[1].

למרות היותה יחסית שפה חדשה, חברות גדולות כמו פייסבוק, דיסקורד ומיקרוסופט משתמשות בראסט כדי לפתח את המוצרים שלהן.

ב-8 בפברואר 2021 הוקמה קרן ראסט (Rust Foundation) במטרה לקדם את שפת התכנות ולפקח עליה[2].

דוגמאות

Hello World

דוגמה לתוכנית Hello world הכתובה בראסט. המאקרו println! מדפיס הודעה להתקן היציאה התקני (מסך).

fn main() {
    println!("Hello World!");
}

חישוב עצרת

fn factorial(i: u64) -> u64 {
	match i {
		0 => 1,
		n => n * factorial(n-1)
	}
}

איטרטיבי

fn factorial(i: u64) -> u64 {
	let mut acc = 1;
	for num in 2..=i {
		acc *= num;
	}
	acc
}

באמצעות איטרטור

fn factorial(i: u64) -> u64 {
	(1..=i).product()
}

מנגנון הטיפוסים

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

המהדר ינסה להסיק את הטיפוס לפי הערך שהושם למשתנה וכן לפי דרך השימוש בו. במקרים בהם קיימים כמה סוגים אפשריים, חובה להגדיר את הסוג במפורש.

מנגנון טיפוסים חזק - אין המרה מרומזת בין טיפוסים.

דוגמאות לטיפוסיים עיקריים

Signed int – i8, i16, i32, i64, i128

Unsigned int – u8, u16, u32, u64, u128

Float – f32, f64

Boolean – bool

Character – char

Tuple – אוסף של ערכים מסוגים שונים. בעל אורך קבוע. פונקציות יכולות להשתמש בזה כדי להחזיר ערכים מרובים, וכך יכולים להחזיר כל מספר של ערכים.

Array – בניגוד ל-Tuple, יכול להכיל סוג טיפוס אחד. בעל אורך קבוע.

Vector – דומה ל-Array אבל בעל אורך משתנה.

String – אוסף של בתים שמהווים מחרוזת תקנית לפי תקן UTF-8[3].

HashMap – מיפוי על ידי שימוש ב-key ו-value.

Struct – מגדירים את שם ה-Struct, שמות השדות והסוג של השדות, ניתן לממש פונקציות.

Enum – טיפוס שיכול להיות כמה וריאנטים שונים, כל וריאנט יכול להכיל מידע מסוג שונה. מאפשר לבדוק בצורה כוללת את כלל האופציות שמוכלות בטיפוס באמצעות match.

Struct, trait ופולימורפיזם

בשפה אין ירושה אך יש מנגנונים אחרים המאפשרים תכנות מונחה עצמים.

Struct – מבנה המאפשר שמירת נתונים והגדרת פונקציות.

Trait – מבנה ייחודי המאפשר להגדיר קבוצה של פונקציות עבור כל סוג.

בשפה אין הורשה או ממשקים, ישנן שתי דרכים ליצור משהו שדומה לאלו:

1 – במקום ירושה פשוטה אפשר להשתמש ב-composition (במקרים שזה מתאים).

2 – אפשר לממש trait אחד עבור כמה struct. זה מאפשר פולימורפיזם כי אפשר להתייחס לאובייקט אחד על פי ה-trait שהוא מממש. מימוש trait עבור struct נותן גם יכולת הדומה למימוש ממשק (ניתן להגדיר trait בלי מימוש וניתן לממש trait אחד עבור כמה סוגים).

Option & Result

Option & Result – טיפוס העשוי להכיל משתנה ממשי (some) או  None (או Ok ו- Err עבור Result), זה מאפשר להתייחס למקרה בו אין את המידע המבוקש (כמו Nullabe ב-#C) ולכן בדרך כלל משמש לבדיקת תקינות הערך המוחזר מפונקציה. הטיפוסים הם סוג מיוחד של Enum.

בנוסף בראסט ישנו מנגנון קפדני המונע שגיאות של גישה ל-None – המהדר יוודא שבכל קוד שמשתמש במשתנה מסוג option יהיה match או unwrap ובשתי האפשרויות חייב להיות טיפול למקרה של None.

שתי דרכים לשימוש בערך הפנימי של משתנה מוכמס:

Match – מספק תבנית התאמה שמשמשת אותו כמו switch ב-C. התבנית מחזירה ערך ו\או מבצעת קוד כתלות בפרמטר. משמש גם לבדיקה האם some וטיפול במקרה של None.

Unwrap – פונקציה זו מחלצת את המשתנה הפנימי מתוך Option & Result במקרה של some – כלומר שקיים ערך ממשי ולא None. הפונקציה מחזירה שגיאה במקרה של None, ועוצרת את ריצת התוכנית[4], ולכן עדיפה לשימוש רק כאשר אין סיבה להמשיך את ריצת התוכנית אם הערך לא קיים.

מנגנון ה-Ownership

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

משתנה יוצא מתחום ההכרה כאשר הוא מועבר כארגומנט לפונקציה (או מתודה).

בדוגמה הבאה הערך year של המשתנה מסוג book (שורה 10) מתעדכן (שורה 11) ומודפס לפלט (שורה 12). לאחמ"כ המשתנה מועבר כארגומנט לפונקציה המחשבת את השנים שעברו מאז 1990 עבור משתנים מסוג book (שורה 14). פעולה זו הוציאה את המשתנה מתחום ההכרה ועל כן לא ניתן להשתמש בו יותר והסרת ההערה משורה 19 תגרור שגיאות הידור.

העברת המשתנה למאקרו println (שורה 12) אינה מוציאה משתנה מתחום ההכרה.

struct Book {
    year: i64,
}

fn since1990(book: Book) -> i64 {
    book.year - 1990
}

fn main() {
    let mut book = Book { year: 2022 };
    book.year = book.year + 2;
    println!("{}", book.year);

    let years_passed = since1990(book);

    println!("{}", years_passed);
    
    // 👇 Error
    // println!("{}",book.year);
}

מנגנון הבעלות מונע טעויות של 'דליפה' של שאריות מידע שאינן בתוקף ומשימוש במידע שכבר היה בשימוש.

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

מנגנון הבעלות מבוסס על שלושה חוקי יסוד הנותנים לשפה את היכולת לפעול עם יותר ביצועים ולהישאר בטוחה:

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

בטיחות

המהדר אוכף סט של חוקים קפדניים המבטיחים במידת האפשר בזמן הידור את שלמות ובטיחות ריצת התוכנית.

בשפה אכיפה של מוסכמות תכנותיות רבות המונעות טעויות של אבטחה וזליגת זיכרון. למשל כאשר מגדירים סתם משתנה הוא יהיה immutable, קבוע לאחר ההגדרה, אלא אם כן הוגדר בפירוש מראש כניתן לשינוי על ידי מילת המפתח mut.

השפה מבטיחה בזמן הידור שלא יהיו גישות למצביע null ושלא ייווצרו מצביעים מתנדנדים (dangling).

דליפת זיכרון היא נדירה אך אפשרית[5] בשפה במהלך שימוש רגיל. בעת שימוש בקוד המוגדר כ-unsafe, מתאפשרות מספר פעולות לא-בטוחות העשויות לגרום לבעיות כמו גלישת חוצץ או קריאה זיכרון לאחר שחרורו (use after free). עם זאת, שימוש בקוד unsafe נחוץ, לדוגמה, כאשר מנסים לכתוב קוד יעיל ככל הניתן ובמהלך גישה לקוד שכתוב בשפות תכנות אחרות (כמו C).

קישורים חיצוניים

הערות שוליים

  1. ^ Stack Overflow Developer Survey 2021, Stack Overflow, ‏2021-08-01
  2. ^ אשלי וויליאמס, Hello World!, אתר קרן ראסט, ‏2021-02-08
  3. ^ String in std::string - Rust, doc.rust-lang.org
  4. ^ Option & unwrap - Rust By Example, doc.rust-lang.org
  5. ^ C++ is faster and safer than Rust: benchmarked by Yandex, PVS-Studio (באנגלית)