
Rust ו-Parse, don't validate: איך הופכים בדיקה לטיפוס
אלי בנדרסקי מחזיר לדיון את הרעיון של אלקסיס קינג עבור Rust: לא להסתפק בבדיקת תנאי, אלא להמיר נתונים לטיפוס שבו הפרת התכונה המובטחת בלתי אפשרית. במאמר מובאות דוגמאות מ-NonEmpty, NonZero ו-rust-analyzer.
אלי בנדרסקי פרסם בבלוג שלו מאמר שמחזיר לדיון רעיון ותיק: מה שפרסמה אלקסיס קינג ב-2019, ולפיו קוד לא אמור רק לבדוק תנאי ואחר כך לבטוח בו, אלא להמיר את הנתונים לטיפוס שבו הפרה של התכונה המובטחת היא בלתי אפשרית. המאמר המקורי נכתב בשביל Haskell, ובנדרסקי מיישם את אותה עדשה על Rust ומחפש דוגמאות מאלפות בספרייה התקנית של השפה ובפרויקטים מוכרים.
וקטור שאינו יכול להיות ריק
נקודת המוצא היא Vec הרגיל והמתודה first(), שמחזירה הפניה אופציונלית מפני שווקטור עשוי שלא להכיל איברים כלל. בדוגמה של בנדרסקי פונקציה קוראת רשימת תיקיות קונפיגורציה ממשתנה הסביבה CONFIG_DIRS ודוחה תוצאה ריקה, ובכל זאת הקוד הקורא נאלץ לכתוב ענף למקרה שלעולם לא יקרה. המחבר מדמה אינווריאנטים כאלה לפצצה מתקתקת: המהדר לא אוכף אותם, ולכן בהמשך הם עלולים להישבר בשקט. הפתרון הוא טיפוס נפרד, למשל NonEmpty מהקרייט nonempty, שאין לו בנאי לערך ריק והמתודה first() שלו מחזירה הפניה רגילה. החזרת Vec שנבדק היא ולידציה, ואילו החזרת NonEmpty היא פרסינג, אם מפרשים פרסינג באופן רחב: המרת נתונים מפורמט אחד לאחר.
צינורות פקודות, נתיבים ועידון הדרגתי
בהמשך המאמר יוצא מדוגמאות צעצוע. בפרויקט posixutils-rs, שכתב מחדש ב-Rust את כלי הליבה של POSIX, פקודות של צינור מעטפת נשמרות כ-NonEmpty, כך שצינור מפוענח תמיד מכיל פקודה אחת לפחות, והמפענח מחזיר None רק כשאין מה להריץ. ל-rust-analyzer יש טיפוס נפרד לנתיב מוחלט: ההמרה נכשלת אם הנתיב אינו מוחלט, והקוד הבא נשען על הטיפוס במקום לבדוק את התנאי שוב. בנדרסקי מכנה זאת פרסינג הדרגתי: נתיב נקבע תחילה כתקף UTF-8 בעזרת הקרייט camino ורק אחר כך כמוחלט.
מספרים שאינם אפס ו-JSON
בספרייה התקנית של Rust קיים הטיפוס הגנרי NonZero למספרים ללא סימן שידוע שאינם אפס. הפונקציה available_parallelism מחזירה ערך כזה כשהקריאה מצליחה, וחילוק במכנה NonZero מתועד כפעולה שלא יכולה להיכשל בפאניקה. יש גם יתרון נוסף: אפס הוא דפוס ביטים לא חוקי עבור הטיפוס, ולכן NonZero אופציונלי תופס בדיוק אותו מקום כמו מספר שלם רגיל. אותו רעיון עובד גם בפרסינג של JSON: עם serde אפשר להצהיר על מבנה קונפיגורציה שבו מספר העובדים אינו אפס ושדה המצב הוא enum בעל שני ערכים, והדסריאליזציה אוכפת בבת אחת את טיפוסי השדות, את הערכים המותרים ואת איסור האפס. בשפות דינמיות התהליך ידני בהרבה: json.loads בפייתון מחזיר מילון ומשאיר את הבדיקה לקוד הקורא, וספריות כמו Pydantic מתקרבות לגישה של Rust אך אינן בשימוש בכל מקום.
SiTech — פיתוח אתרים בכוח ה-AI
אנחנו בונים אתרים מהירים ומודרניים ומשלבים AI בתהליכי עבודה אמיתיים. יש לכם פרויקט או שאלה? נשמח לעזור.