Menu

Type Assertion ב-Golang: x.(T), comma-ok ו-type switch

type assertion מוציא את הערך הקונקרטי מתוך interface. למדו את x.(T), את צורת ה-comma-ok שאף פעם לא גורמת ל-panic, assertion ל-interface אחר, type switches ו-errors.As לשגיאות עטופות.

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

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

מה שיש לכםמה שאתם רוציםבמה להשתמש
intfloat64המרה: float64(n)
any שמחזיק intאת ה-intassertion: 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 עוברת על שרשרת העטיפות.

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

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

להתחיל