ב-Go אין מילת מפתח while. כתבו for ואחריו תנאי, וקיבלתם לולאת while:
התנאי נבדק לפני כל איטרציה, כך שאם הוא שקרי כבר בהתחלה, גוף הלולאה לא רץ אף פעם. זה זהה ל-while (balance < 2000) ב-C, ב-Java או ב-JavaScript, רק בלי הסוגריים ובלי מילת המפתח. המתכננים של Go בחרו במילת מפתח אחת לכל צורות הלולאה; בעמוד של לולאת for תמצאו את כל ארבע הצורות.
while(true): הלולאה האינסופית
השמיטו את התנאי לגמרי, והלולאה תרוץ עד שמשהו בתוכה יעצור אותה:
for { } היא הכתיבה האידיומטית. גם for true { } מתקמפל, אבל הוא נקרא כאילו הכותב הגיע משפה אחרת.
ארבעה דברים עוצרים לולאה אינסופית: break (יוצא מהלולאה), return (יוצא מהפונקציה), panic ו-os.Exit. תוכנית שה-main שלה יושב בתוך for {} בלי אף אחד מאלה רצה עד שמישהו הורג אותה, וזה בדיוק מה שצריך בשרת או ב-worker.
do-while
do-while מריצה את גוף הלולאה פעם אחת לפני שהיא בודקת את התנאי. ל-Go אין תחביר ייעודי לכך. שימו את הבדיקה בסוף לולאה אינסופית:
גוף הלולאה רץ לפחות פעם אחת בכל מקרה, וזו התכונה שמגדירה do-while. מקור האקראיות משתמש ב-seed קבוע, כך שהפלט זהה בכל הרצה; לאקראיות אמיתית השתמשו ב-rand.IntN בלי מקור עם seed.
צורה שנייה משאירה את התנאי בכותרת הלולאה בכך שהיא מכריחה את המעבר הראשון:
for first := true; first || condition(); first = false {
// body
}
זה עובד, אבל הלולאה האינסופית עם break בסוף קריאה יותר, וזה מה שרוב קוד ה-Go עושה.
קריאה עד הסוף
לולאת ה-while הנפוצה ביותר בקוד Go אמיתי קוראת קלט עד שהוא נגמר. bufio.Scanner נבנה בדיוק בשביל זה: Scan מחזירה false בסוף הקלט או כשיש שגיאה, ולכן היא משמשת כתנאי הלולאה:
החליפו את strings.NewReader(input) ב-os.Stdin, ואותה לולאה תקרא את הקלט של המשתמש שורה אחר שורה. בדקו את scanner.Err() אחרי הלולאה: העובדה ש-Scan החזירה false לא אומרת אם הקלט הסתיים כרגיל או נכשל.
break ו-continue
שתיהן עובדות בכל הצורות של for:
בלולאה עם תנאי בלבד, continue קופצת ישר לתנאי. זה מקל על באג עדין: אם המונה מתעדכן בסוף גוף הלולאה, continue שנמצאת לפניו מדלגת על העדכון, והלולאה לא נגמרת לעולם. עדכנו את המונה בהתחלה, כמו בדוגמה, או השתמשו ב-for עם שלושה חלקים, שבה פקודת ה-post רצה גם אחרי continue.
המתנה למשהו
לולאה שמחכה לתנאי צריכה להיחסם או לישון, לא להסתובב על ריק:
// Wrong: burns a CPU core while waiting
for !ready {
}
// Better: poll with a delay
for !isReady() {
time.Sleep(100 * time.Millisecond)
}
דגימה חוזרת של משתנה רגיל ש-goroutine אחרת כותבת אליו היא גם data race. בין goroutines, חכו על channel, על sync.WaitGroup או על sync.Cond, או השתמשו ב-select עם טיימר. ללולאות ניסיון חוזר עם הגבלה, העדיפו for עם מונה, כדי שהלולאה לא תוכל לרוץ לנצח:
for attempt := 1; attempt <= 5; attempt++ {
if err := connect(); err == nil {
break
}
time.Sleep(time.Duration(attempt) * 200 * time.Millisecond)
}
טעויות נפוצות
- לשכוח לשנות את משתנה התנאי.
for i < 10 { fmt.Println(i) }לא נגמרת לעולם. כל לולאת while צריכה משהו בגוף הלולאה שמקרב אותה ליציאה. - לכתוב
while.while x < 5 {}נותןsyntax error: unexpected name x at end of statement, כיwhileאינה מילת מפתח; הקומפיילר קורא אותה כשם רגיל. - סוגריים מתוך הרגל.
for (x < 5) {}מתקמפל, ו-gofmtמסיר את הסוגריים. - להצהיר על המשתנה בתוך הלולאה.
for n := 10; n > 0 {}היא שגיאת תחביר; לולאה עם פסוקית אתחול צריכה את כל שלושת החלקים. הצהירו עלnלפני הלולאה, או כתבו את הצורה המלאה עם שלושה חלקים.
שאלות נפוצות
האם יש ב-Go לולאת while?
לא כמילת מפתח. Go משתמשת ב-for לכל הלולאות, ו-for עם תנאי בלבד היא בדיוק לולאת while:
for count < 10 {
count++
}
התנאי נבדק לפני כל איטרציה, בדיוק כמו while (count < 10) ב-C או ב-Java.
איך כותבים while(true) ב-Go?
כותבים for בלי שום דבר אחריו: for { ... }. הלולאה חוזרת עד ש-break, return, panic או os.Exit עוצרים אותה. גם for true { ... } מתקמפל, אבל for { } הוא הצורה האידיומטית.
איך כותבים לולאת do-while ב-Go?
משתמשים בלולאה אינסופית ושמים את בדיקת התנאי בסוף גוף הלולאה:
for {
// body runs at least once
if !condition {
break
}
}
גוף הלולאה תמיד רץ פעם אחת לפני שהתנאי נבדק, וזו בדיוק המשמעות של do-while.