ל-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 |
bool | false |
string | "" |
| מצביעים, slices, maps, channels, פונקציות, interfaces | nil |
| מערכים, 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 כותבות אליהם.