x.(T): הוצאת הערך מתוך interface
ערך interface מסתיר את הטיפוס הקונקרטי שהוא מחזיק. type assertion x.(T) מבקש אותו בחזרה.
פלט:
gopher 6
0 false
string of length 6
ל-x חייב להיות טיפוס interface. assertion על טיפוס קונקרטי לא מתקמפל: invalid operation: s (variable of type string) is not an interface.
הצורה עם ערך אחד גורמת ל-panic
כשה-assertion שגוי, הצורה עם ערך יחיד גורמת ל-panic עם הודעה שמציינת את שני הטיפוסים:
פלט:
recovered: interface conversion: interface {} is int, not string
השתמשו בצורה עם ערך אחד רק כשטיפוס אחר יהיה שגיאת תכנות שאתם רוצים לקרוס בגללה. בכל מקום אחר, השתמשו ב-comma-ok. בכישלון, comma-ok נותן את ערך האפס של T ו-false.
assertion על interface שהוא nil גם נכשל: var v any; v.(string) גורם ל-panic עם interface conversion: interface {} is nil, not string, וצורת ה-comma-ok מחזירה "", false.
Assertion ל-interface אחר
T יכול להיות טיפוס interface. ה-assertion מצליח אז אם הערך הדינמי מממש את T, והתוצאה שומרת על אותו ערך דינמי. כך הספרייה הסטנדרטית בודקת יכולות אופציונליות.
io.Copy בודקת אם המקור שלה מממש את io.WriterTo ומשתמשת בזה כשכן. fmt בודקת Stringer ו-error. הדפוס הזה מאפשר ל-interface קטן להישאר קטן, בזמן שהקוראים עדיין נהנים ממימושים עשירים יותר.
Type switch
כשערך יכול להיות אחד מכמה טיפוסים, type switch מחליף שרשרת של assertions. x.(type) תקף רק בתוך switch.
פלט:
hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int
כללים ששווה להכיר:
- במקרה עם טיפוס אחד, ל-
xיש את הטיפוס הזה. במקרה שמפרט כמה טיפוסים, וב-default, ל-xיש את הטיפוס של ה-interface המקורי (כאןany). - המקרים נבדקים לפי הסדר. שימו interfaces ספציפיים יותר לפני רחבים יותר, כי ערך יכול לממש כמה מהם.
case nilמתאים רק ל-interface שהוא nil, לא ל-interface שמחזיק מצביע nil.- אין
fallthroughב-type switch.
errors.As: ה-assertion לשגיאות עטופות
שגיאות נעטפות לעיתים קרובות בהקשר: fmt.Errorf("load config: %w", err). type assertion ישיר מסתכל רק על השגיאה החיצונית ביותר ומפספס את זו שבפנים. errors.As עוברת על השרשרת.
פלט:
type assertion finds it: false
errors.As finds it: open /no/such/file
השתמשו ב-type assertion על שגיאות רק כשאתם יודעים שהשגיאה לא עטופה, ובפועל זה כמעט אף פעם. הדף על שגיאות מותאמות מכסה את errors.As, את errors.Is והגדרה של טיפוסי שגיאה משלכם.
Assertions מול המרות מול generics
| מה שיש לכם | מה שאתם רוצים | במה להשתמש |
|---|---|---|
int | float64 | המרה: float64(n) |
any שמחזיק int | את ה-int | assertion: v.(int) |
any מכמה טיפוסים אפשריים | להתפצל לפי טיפוס | type switch |
error שאולי עטופה | טיפוס שגיאה מסוים | errors.As |
| פונקציה שעובדת עבור הרבה טיפוסים | בטיחות בזמן הידור | generics |
קוד מלא ב-any וב-type switches הוא לעיתים קרובות סימן לכך ש-generics או interface מתאים היו מבטאים את הכוונה טוב יותר ותופסים טעויות בזמן הידור.
טעויות נפוצות
- שימוש בצורה שגורמת ל-panic על נתונים לא מהימנים. JSON מפוענח, ערכי map מטיפוס
any, קלטים של plugins: תמיד comma-ok. - Assertion לטיפוס מספרי שגוי. מספרי JSON מפוענחים לתוך
anyכ-float64, ולכןv.(int)נכשל עליהם. - Assertion לטיפוס ערך כששמור מצביע. אם ה-interface מחזיק
*User, אזv.(User)נכשל. בצעו assertion ל-v.(*User). - Type assertions על שגיאות. השתמשו ב-
errors.As.
שאלות נפוצות
מה זה type assertion ב-Go?
ביטוי x.(T) שבו x הוא ערך interface. אם T הוא טיפוס קונקרטי, הביטוי מחזיר את הערך שנשמר ב-x כ-T. אם T הוא טיפוס interface, הביטוי בודק שהערך השמור מממש גם את T. הצורה עם ערך אחד גורמת ל-panic כשהבדיקה נכשלת; הצורה עם שני ערכים v, ok := x.(T) מדווחת על זה ב-ok.
איך בודקים את הטיפוס של ערך interface ב-Go?
השתמשו ב-type switch: switch v := x.(type) { case int: ...; case string: ...; default: ... }. בתוך כל מקרה, ל-v יש את הטיפוס של המקרה. לטיפוס יחיד, ה-assertion בצורת comma-ok s, ok := x.(string) קצר יותר. fmt.Printf("%T", x) מדפיס את שם הטיפוס לדיבוג.
מה ההבדל בין type assertion להמרת טיפוס ב-Go?
המרה T(x) הופכת ערך מטיפוס אחד לאחר, כמו float64(n); המהדר מחליט אם היא מותרת, והיא לא יכולה להיכשל בזמן ריצה. assertion x.(T) עובד רק על ערכי interface ובודק בזמן ריצה איזה טיפוס שמור בפנים. אי אפשר לכתוב int(x) עבור any שמחזיק int; צריך x.(int).
האם להשתמש ב-type assertion כדי לבדוק טיפוסים של שגיאות?
לא. השתמשו ב-errors.As(err, &target). assertion ישיר err.(*MyError) מסתכל רק על השגיאה החיצונית, ולכן הוא נכשל ברגע שהשגיאה עטופה עם fmt.Errorf("...: %w", err). errors.As עוברת על שרשרת העטיפות.