לחבילת fmt יש שלוש משפחות של פונקציות הדפסה, ולכל משפחה אותן שלוש גרסאות:
| פונקציה | לאן הפלט הולך | פורמט |
|---|---|---|
Print, Println, Printf | הפלט הסטנדרטי | ברירת מחדל, ברירת מחדל עם רווחים ושורה חדשה, מחרוזת פורמט |
Sprint, Sprintln, Sprintf | string מוחזר | אותם שלושה סגנונות |
Fprint, Fprintln, Fprintf | כל io.Writer (קובץ, buffer, תשובת HTTP) | אותם שלושה סגנונות |
Errorf | error מוחזר | מחרוזת פורמט, ועוד %w לעטיפה |
Println מוסיפה רווחים בין האופרנדים ושורה חדשה בסוף. Printf לא מוסיפה כלום: את \n כותבים בעצמכם. Print מוסיפה רווחים רק בין אופרנדים ששניהם אינם מחרוזות, וזה מפתיע מספיק כדי שרוב הקוד ישתמש ב-Println או ב-Printf.
Format verbs
כלליים
| Verb | מדפיס | פלט לדוגמה |
|---|---|---|
%v | את הערך בפורמט ברירת מחדל | {Ana 31 [admin]} |
%+v | structs עם שמות השדות | {Name:Ana Age:31 Tags:[admin]} |
%#v | תחביר Go של הערך | main.User{Name:"Ana", Age:31, Tags:[]string{"admin"}} |
%T | את הטיפוס | main.User |
%% | סימן אחוז מילולי | % |
%+v הוא זה שכדאי להשתמש בו בדיבוג. מצביע ל-struct מודפס כ-&{...} ולא ככתובת. maps מודפסים עם מפתחות ממוינים, כך שהפלט יציב גם אם סדר המעבר על map אקראי.
מספרים שלמים
| Verb | משמעות | fmt.Sprintf(verb, 255) |
|---|---|---|
%d | עשרוני | 255 |
%b | בינארי | 11111111 |
%o | אוקטלי | 377 |
%O | אוקטלי עם הקידומת 0o | 0o377 |
%x / %X | הקסדצימלי, באותיות קטנות או גדולות | ff / FF |
%#x | הקסדצימלי עם הקידומת 0x | 0xff |
%c | התו עם נקודת הקוד הזאת | ÿ |
%q | ליטרל תו בגרשיים | 'ÿ' |
%U | פורמט Unicode | U+00FF |
מספרים עשרוניים
| Verb | משמעות | fmt.Sprintf(verb, 1234.5678) |
|---|---|---|
%f | עשרוני, 6 ספרות כברירת מחדל | 1234.567800 |
%.2f | עשרוני, 2 ספרות | 1234.57 |
%e | כתיב מדעי | 1.234568e+03 |
%g | %e או %f, הקצר מביניהם, בלי אפסים נגררים | 1234.5678 |
%v | כמו %g | 1234.5678 |
%.2f מעגל את הערך הבינארי המדויק של ה-float, ולכן fmt.Sprintf("%.2f", 2.675) הוא 2.67: ה-float64 הקרוב ביותר ל-2.675 קטן ממנו במעט. אף פעם אל תעצבו כסף מ-float; שמרו אגורות במספר שלם.
מחרוזות ובתים
| Verb | משמעות | fmt.Sprintf(verb, "go\n") |
|---|---|---|
%s | המחרוזת כמו שהיא | go ושורה חדשה |
%q | במירכאות כפולות, עם תווי escape גלויים | "go\n" |
%x | הקסדצימלי של כל בית | 676f0a |
% x | הקסדצימלי עם רווחים | 67 6f 0a |
%s על []byte מדפיס אותו כטקסט; %v מדפיס את המספרים ([104 105]).
טיפוסים אחרים
| Verb | טיפוס | מדפיס |
|---|---|---|
%t | bool | true או false |
%p | מצביע, slice, map, channel, פונקציה | את הכתובת, כמו 0xc000012345 |
%w | error (רק ב-Errorf) | את ההודעה של השגיאה, ועוטף אותה |
רוחב, דיוק וריפוד
בין % ל-verb אפשר לשים דגלים, רוחב ודיוק:
| צורה | השפעה |
|---|---|
%5d | רוחב 5, מיושר לסוף השדה (ריפוד ברווחים לפני הערך) |
%-5d | רוחב 5, מיושר לתחילת השדה |
%05d | ריפוד באפסים |
%.2f | 2 ספרות אחרי הנקודה העשרונית |
%8.2f | רוחב 8 ו-2 ספרות אחרי הנקודה |
%.3s | לכל היותר 3 תווים מהמחרוזת |
%+d | תמיד להציג את הסימן |
%*d | הרוחב נלקח מהארגומנט הבא |
שימו לב ש-%.0f של 2.5 מדפיס 2: כאן Go מעגלת חצי למספר הזוגי. במחרוזות, הרוחב סופר runes ולא עמודות תצוגה, כך שתווי CJK ואימוג'י עדיין יכולים לשבש את היישור של טבלה. לעמודות מיושרות של טקסט באורך משתנה, text/tabwriter עושה את המדידה בשבילכם.
אינדקסים של ארגומנטים
%[n] בוחר ארגומנט לפי המיקום שלו, וזה מאפשר להשתמש באחד שוב:
fmt.Printf("%[2]s %[1]s\n", "world", "hello") // hello world
fmt.Printf("%d %[1]x %[1]b\n", 10) // 10 a 1010
Errorf ו-%w
fmt.Errorf בונה error ממחרוזת פורמט. עם %w היא גם עוטפת שגיאה אחרת, כך שמי שקורא עדיין יכול לזהות את המקורית:
השתמשו ב-%w כשמי שקורא עשוי להצטרך לבדוק את הסיבה, וב-%v כשאתם מסתירים אותה בכוונה. החל מ-Go 1.20 קריאה אחת ל-Errorf יכולה להכיל כמה verbs של %w. הדף על טיפול בשגיאות מסביר עטיפה לעומק.
עיצוב מותאם עם String()
כל טיפוס עם מתודת String() string קובע איך %v, %s ו-Println מציגים אותו:
%d עוקף את String() ומדפיס את המספר שמתחת. עבור טיפוסי שגיאה המתודה המקבילה היא Error() string, והיא קודמת ל-String().
כשה-verb שגוי
fmt אף פעם לא נכנסת ל-panic בגלל פורמט שגוי. היא מדפיסה את הבעיה בתוך הפלט:
fmt.Printf("%d\n", "oops")
fmt.Printf("%d %d\n", 1)
fmt.Printf("%d\n", 1, 2)
%!d(string=oops)
1 %!d(MISSING)
1
%!(EXTRA int=2)
פלט כזה נוטה להגיע ל-production, כי הוא לא מפיל כלום. go vet תופס את שלושת המקרים בזמן הבנייה:
./main.go:8:2: fmt.Printf format %d has arg "oops" of wrong type string
./main.go:9:2: fmt.Printf format %d reads arg #2, but call has 1 arg
./main.go:10:2: fmt.Printf call needs 1 arg but has 2 args
הערות על ביצועים
fmt מקבלת כל ארגומנט כ-any ובוחנת את הטיפוס שלו בזמן ריצה (ונופלת חזרה ל-reflection עבור structs, slices ו-maps), וזה בסדר ללוגים ולפלט אבל מורגש בלולאות צפופות. להמרת מספר בודד, strconv.Itoa ו-strconv.FormatFloat מהירות יותר מ-Sprintf. לבניית מחרוזת ארוכה בלולאה, כתבו לתוך strings.Builder עם fmt.Fprintf(&b, ...) במקום לשרשר תוצאות של Sprintf.
שאלות נפוצות
מה ההבדל בין Println, Printf ו-Sprintf ב-Go?
fmt.Println מדפיסה את הארגומנטים שלה מופרדים ברווחים, עם שורה חדשה בסוף. fmt.Printf מדפיסה לפי מחרוזת פורמט ולא מוסיפה שורה חדשה. fmt.Sprintf מעצבת באותו אופן כמו Printf אבל מחזירה את התוצאה כמחרוזת במקום להדפיס אותה. fmt.Errorf עושה אותו דבר ומחזירה error.
איך מדפיסים struct עם שמות השדות ב-Go?
השתמשו ב-%+v: fmt.Printf("%+v\n", user) מדפיס {Name:Ana Age:31}. %v מדפיס רק את הערכים, {Ana 31}, ו-%#v מדפיס תחביר Go כולל הטיפוס, main.User{Name:"Ana", Age:31}.
איך מעצבים float לשתי ספרות אחרי הנקודה ב-Go?
השתמשו ב-%.2f: fmt.Sprintf("%.2f", 3.14159) מחזירה "3.14". הוסיפו רוחב כדי ליישר עמודות, %8.2f, או סימן מינוס כדי ליישר לתחילת השדה, %-8.2f. strconv.FormatFloat(f, 'f', 2, 64) נותנת את אותה תוצאה בלי מחרוזת פורמט.
מה עושה %w ב-fmt.Errorf?
%w מעצב שגיאה כמו %v וגם עוטף אותה, כך שהשגיאה החדשה נושאת את המקורית. errors.Is ו-errors.As יכולות אז למצוא את השגיאה העטופה: err := fmt.Errorf("load config: %w", os.ErrNotExist) גורם ל-errors.Is(err, os.ErrNotExist) להיות true. %w עובד רק ב-fmt.Errorf.
למה בפלט שלי מופיע %!d(string=...)?
ה-verb לא מתאים לטיפוס של הארגומנט, למשל %d שקיבל מחרוזת. fmt מדפיסה את הבעיה בתוך הפלט במקום להיכנס ל-panic: %!d(string=oops). ארגומנטים חסרים מודפסים כ-%!d(MISSING) ועודפים כ-%!(EXTRA int=2). go vet תופס את שלושת המקרים עוד לפני שמריצים את התוכנית.