פונקציות או לא להיות

18/8/2021, לפני חודשיים
תגיות: rust, ראסט

פונקציות הן כלי חשוב בכמעט כל שפת תכנות. הן מאפשרות שימוש חוזר בקוד בקלות יחסית וכך לשמור על העיקרון החשוב של DRY – Don’t Repeat Yourself, שבתורו מאפשר לקוד להיות קריא הרבה יותר, וכתוב בצורה שמאפשרת בקלות יותר לעקוב אחרי מה שהתוכנה שכתבנו עושה. בעצם, כבר פגשנו פונקציה אחת בפוסטים הקודמים, אולי אפילו הפונקציה החשובה ביותר: main, שכפי שראינו, שם מתחילה התוכנה שלנו.

פונקציה בראסט מוגדרת באמצעות החתימה (signature) הבאה:

fn function_name(arguments: argumentType) -> returnValue {
}

החתימה מתחילה בשימוש במילה השמורה fn, ולאחריה שם הפונקציה. מקובל כי שמות הפונקציות בראסט כתובות ב snake_case, כלומר, אותיות קטנות בלבד ושימוש בקו תחחתון (_) כדי להפריד בין מילים. לאחר מכן, בסוגריים, נתונה רשימת הארגומנטים (נקראים גם פרמטרים) המועברים לפונקציה, כאשר נתון השם של הארגומנט ולאחריו הסוג של הארגומנט. אם לא מועברים פרמטרים לפונקציה, יש צורך בסוגריים ריקות. לבסוף, לאחר סימן חץ (מקף אמצעי ולאחריו סוגריים משולשים ימינה <-), ישנו הסוג של ערך החזרה של הפונקציה, כאשר אם היא לא מחזירה שום ערך (כמו void בשפות אחרות), אפשר לא לכתוב את החלק הזה כלל. גוף הפונקציה מוכל בתוך סוגריים מסולסלים.

נכתוב קצת קוד! ניצור פרויקט חדש בעזרת קרגו, ונקרא לו functions. בקובץ src/main.rs נכתוב את הקוד הבא:

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {}", x);
}

ונריץ את התוכנה המתוחכמת שלנו.

cargo run
   Compiling functions v0.1.0 …
    Finished dev [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/functions`
The value of x is: 5

נעבור על מה שעשינו: בשורה 2 קראנו לפונקציה בשם another_function (שניתן לראות אותה בשורה 5), תוך העברת הפרמטר 5. כאמור, בשורה 5 הצהרנו על הפונקציה הנ”ל, כאשר היא מקבלת ארגומנט בשם x, מסוג i32. בשורה 6, שהיא גוף הפונקציה, אנחנו מדפיסים באמצעות המקרו println את השורה ”The value of x is 5”, כאשר במקום הסוגריים המסולסלים הריקים, המקרו יציג את הערך של המשתנה שהועבר לו, במקרה הזה x. ואכן, זה מה שראינו על כתוצאה מריצת התוכנה שלנו.

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

מה יקרה אם נעביר סוג אחר של ארגומנט לפונקציה ממה שהיא מצפה לו? ננסה: נחליף את שורה 2 בקוד כך שהיא תעביר לפונקציה את הארגומנט ’hello’ במקום 5.

    another_function(“hello”);

ונריץ:

cargo run
   Compiling functions v0.1.0 ….
error[E0308]: mismatched types
 --> src/main.rs:2:22
  |
2 |     another_function("hello");
  |                      ^^^^^^^ expected `i32`, found `&str`

אז כן, התוכנה לא תתקמפל ותסרב לרוץ, כיוון שהפונקציה מצפה לסוג מסוים של פרמטר, מספר בגודל 32 ביט, אבל קיבלה רפרנס למחרוזת (&str). שגיאה אחרת נוכל לקבל גם אם נעביר מספר לא מספיק או יותר מדי פרמטרים לפונקציה. מוזמנים לנסות בעצמכם!

מה לגבי החזרה של ערך מהפונקציה? נכתוב את הפונקציה הבאה:

fn add(a: i32, b: i32) -> i32 {
    let val = a + b;
    return val;
}

בשורה 2, הצהרנו (מלשון statement) על הפעולה הבאה סכמנו את הפרמטרים a ו b לתוך המשתנה val. הצהרות, בהגדרה, הן פעולות שלא מחזירות ערך. כלומר, לפעולה let val… אין שום ערך, ולא ניתן להציב את התוצאה שלה למשתנה (בשונה למשל משפת C). בשורה 3, החזרנו את הערך של val באמצעות השימוש במילה השמורה return. נקרא לפונקציה מהפונקציה main:

fn main() {
    let val = add(5, 6);
    println!("5 + 6 = {}", val);
}

כשנריץ, אכן נקבל את הפלט הרצוי:

5 + 6 = 11

בשורה 2 של הפונקציה main, הצבנו את הערך החוזר לתוך משתנה בשם val, והדפסנו אותו בשורה 3. ניתן להשתמש באותו שם למשתנה כיוון שבראסט, כמו בשפות אחרות, שמות של משתנים מוכלים לתוך ה Scope , טווח ההכרה, של הפונקציה, כך שהפונקציה שקוראת או הפונקציה שנקראת לא מכירות מה קורה בתוך הפונקציה האחרת.

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

fn add(a: i32, b: i32) -> i32 {
    let val = a + b;
    val
}

עדיין נקבל את אותה התוצאה, ואפילו עם נכתוב -

fn add(a: i32, b: i32) -> i32 {
    a + b
}

נקבל את אותה התוצאה בדיוק.

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


הערות, מענות וכו'
נכתב על ידי אסף ספיר מתכנת, לשעבר פרמדיק ואח.
© Assaf Sapir, 2021, Built with Gatsby. Hosted with GitHub Pages.
Source code on my GitHub.