Menu

משתנים ב-Golang: var, :=, ערכי אפס ו-shadowing

איך מצהירים על משתנים ב-Go עם var ועם הצורה הקצרה :=, מה הם ערכי אפס, הצבה מרובה, ושגיאות ה-shadowing והמשתנה שלא בשימוש שתופסות מתכנתי Go חדשים.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

ל-Go יש שתי דרכים להצהיר על משתנה: var, שעובד בכל מקום, ו-:=, צורה קצרה שעובדת בתוך פונקציות ומסיקה את הטיפוס.

לכל משתנה יש טיפוס קבוע שנקבע בזמן הידור. כשנותנים ערך, Go מסיקה ממנו את הטיפוס, כך שכתיבת הטיפוס בנוסף (var city string = "Lisbon") חוקית אבל מיותרת.

var מול :=

var:=
איפהברמת החבילה ובתוך פונקציותרק בתוך פונקציות
טיפוסאופציונלי; חובה כשאין ערךתמיד מוסק
ערך התחלתיאופציונלי (אחרת ערך האפס)חובה
הצהרה מחדשאף פעםמותרת אם לפחות משתנה אחד בצד השמאלי חדש

הבחירה האידיומטית בתוך פונקציה:

  • x := value כשיש ערך התחלתי.
  • var x T כשרוצים את ערך האפס, וזה מסמן "ריק בכוונה": var buf bytes.Buffer, var total int.
  • var x T = value כשהטיפוס שמוסק הוא לא זה שרוצים, למשל var ratio float64 = 1 (ratio := 1 רגיל היה int).

:= מחוץ לפונקציה היא שגיאת תחביר:

./main.go:5:1: syntax error: non-declaration statement outside function body

ערכי אפס

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

טיפוסערך אפס
מספרים שלמים ועשרוניים0
boolfalse
string""
מצביעים, slices, maps, channels, פונקציות, interfacesnil
מערכים, structsכל איבר או שדה מקבל את ערך האפס שלו

APIs טובים ב-Go מתוכננים כך שערך האפס יהיה שימושי: sync.Mutex בערך אפס לא נעול, bytes.Buffer בערך אפס ריק ומוכן, ו-len, range ו-append כולם עובדים על slice שהוא nil. זה שצריך לשים עליו עין הוא ה-map: קריאה מ-map שהוא nil מחזירה ערכי אפס, אבל כתיבה אליו גורמת ל-panic. צרו maps עם make או עם ליטרל לפני ששומרים בהם.

הצהרה על כמה משתנים

הבלוק var ( ... ) מקבץ משתנים קשורים ברמת החבילה. בהצבה מרובה כמו x, y = y, x, כל ביטוי בצד הימני מוערך לפני שמשהו מוצב, ולכן ההחלפה עובדת.

:= עם כמה שמות לא מצהיר מחדש על כלום כל עוד לפחות שם אחד בצד השמאלי חדש. שמות קיימים באותו scope פשוט מקבלים ערך. זה מה שמאפשר לעשות שימוש חוזר ב-err:

f, err := os.Open("a.txt")   // declares f and err
g, err := os.Open("b.txt")   // declares g, assigns to the existing err

אם אף אחד מהשמות לא חדש, זו שגיאה:

./main.go:7:4: no new variables on left side of :=

השתמשו ב-= רגיל כדי להציב ערך במשתנה שכבר קיים.

משתנים שלא בשימוש הם שגיאות

משתנה מקומי שמוצהר אבל אף פעם לא נקרא עוצר את ה-build:

./main.go:9:2: declared and not used: x

זו לא אזהרה שאפשר לכבות. מחקו את המשתנה, השתמשו בו, או, בזמן דיבוג, השתיקו אותו עם המזהה הריק: _ = x. המזהה הריק גם זורק ערכים מוחזרים שלא צריך: _, err := fmt.Println("hi").

משתנים ברמת החבילה ופרמטרים של פונקציות פטורים מהכלל הזה.

Scope ו-shadowing

משתנה קיים מההצהרה שלו ועד סוף הבלוק העוטף ({ ... }). := בבלוק פנימי יוצר משתנה חדש, גם אם קיים בחוץ משתנה עם אותו שם. המשתנה החיצוני מוסתר, לא משתנה:

התוכנית מדפיסה inside: 42 ואז outside: 0. ה-:= בתוך ה-if הצהיר גם על count וגם על err כמשתנים חדשים בבלוק הפנימי, כי אף אחד מהם עוד לא היה קיים שם. זה מתקמפל בלי בעיות והוא מקור קלאסי לבאגים של "הערך שלי נעלם".

התיקון הוא להצהיר קודם על err ולהציב עם =:

var err error
count, err = strconv.Atoi(input)

go vet לא מדווח על shadowing כברירת מחדל. ה-analyzer הנפרד shadow כן מדווח, ו-linters כמו golangci-lint יכולים להפעיל אותו.

מתן שמות

  • השתמשו ב-camelCase: maxRetries, userID. לא max_retries.
  • שמרו ראשי תיבות באותו גודל אותיות: userID, httpClient, parseURL, לא userId.
  • שמות קצרים ל-scopes קצרים: i בלולאה, r ל-reader, err לשגיאה. שמות ארוכים ומתארים יותר למשתנים ברמת החבילה.
  • שם שמתחיל באות גדולה ברמת החבילה (var Timeout) מיוצא לחבילות אחרות. ראו חבילות וייבוא.

משתנים ברמת החבילה

משתנים שמוצהרים מחוץ לפונקציות חיים לאורך כל התוכנית ומאותחלים לפני ש-main רצה, לפי סדר התלויות. הם נוחים להגדרות ולמטמונים, והם משותפים לכל goroutine. אם יותר מ-goroutine אחת כותבת לאחד מהם, הגנו עליו עם mutex או השתמשו ב-sync/atomic, אחרת יש לכם data race.

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

שאלות נפוצות

מה ההבדל בין var לבין := ב-Go?

:= מצהיר ומאתחל משתנה עם טיפוס שמוסק, ועובד רק בתוך פונקציות: count := 10. var עובד בכל מקום (כולל ברמת החבילה), מאפשר לציין את הטיפוס, ויכול להשמיט את הערך ההתחלתי כדי לקבל את ערך האפס: var count int. בתוך פונקציות, Go אידיומטית משתמשת ב-:= כשיש ערך התחלתי וב-var כשרוצים את ערך האפס.

מה הם ערכי אפס ב-Go?

כל משתנה שמוצהר בלי ערך מקבל את ערך האפס של הטיפוס שלו: 0 למספרים, false ל-bool, "" ל-string, ו-nil למצביעים, slices, maps, channels, פונקציות ו-interfaces. structs מקבלים את ערך האפס של כל שדה. ב-Go אין משתנים לא מאותחלים.

איך מתקנים את "declared and not used" ב-Go?

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

איך מצהירים על משתנה גלובלי ב-Go?

השתמשו ב-var ברמת החבילה, מחוץ לכל פונקציה: var counter int. הצורה הקצרה := לא מותרת שם. שם שמתחיל באות גדולה (var Counter int) גלוי לחבילות אחרות; שם באות קטנה גלוי רק בחבילה שלו. הגנו על משתנים ברמת החבילה עם mutex אם goroutines כותבות אליהם.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל