Rust-Typen statt Laufzeitprüfungen: Wie „Parse, don't validate“ Invarianten erzwingt

Rusty thoughts on "Parse, don't validate"

Alexis Kings Idee „Parse, don't validate“ auf Rust angewendet: Statt Vec mit nachträglicher Prüfung auf Nicht-Leere nutzt man NonEmpty, dessen first() direkt &T liefert. Beispiele aus rust-analyzer (AbsPathBuf, Utf8PathBuf), NonZero für available_parallelism und serde-Deserialisierung zeigen, wie der Typzustand Invarianten trägt. Das eliminiert unreachable!-Zweige, vermeidet Laufzeitfehler und macht den Code klarer.

Sobald der Parser mode in das Mode-Enum umgewandelt hat, ist keine weitere Validierung erforderlich.
  1. articulatepang

    Ich bevorzuge eine etwas allgemeinere Regel: Make Illegal States Unrepresentable. Die Regel „Parse, don't validate“ ist ein Spezialfall von MISU.

    Was ist der Unterschied? MISU gilt auch dann, wenn keine parsing-ähnliche Transformation stattfindet. Wenn man zum Beispiel eine Variable hat, die den aktuellen Zustand einer Netzwerkverbindung darstellt, und sagen wir, sie kann Disconnected sein oder Connected zu einer IP-Adresse (das ist eine starke Vereinfachung).

    Dann wäre eine Möglichkeit:

    struct {

    connected: bool,

    peer_ip: int32

    }

    Das Problem ist, dass dies erlaubt, einen illegalen/sinnlosen Zustand darzustellen: Wir sind disconnected, aber es hängt noch irgendein alter Müll-peer_ip herum. Noch schlimmer, wir hätten schreiben können:

    struct {

    connected: bool,

    peer_ip: Option<int32>

    }

    Jetzt könnten wir connected = true haben, aber peer_ip = None.

    Die Lösung ist ein Summentyp:

    type connection =

    Disconnected

    | Connected of int32

    (Entschuldigung für die erfundene Syntax; ich hoffe, es ist jedem klar, der mit Rust vertraut ist.)

    „Make Illegal States Unrepresentable“ gilt im gesamten Programm, an jeder Schnittstelle zwischen Modulen oder Funktionen im Programm, einschließlich, aber nicht beschränkt auf das Parsen von Eingaben.

  2. Fluorescence

    Ich bin mir nicht sicher, ob dieser Typ ein guter Rat ist:

    pub struct NonEmpty<T> {

    pub head: T,

    pub tail: Vec<T>,

    }

    Man müsste die Traits manuell implementieren, um die Ergonomie von Slices und Iteration zu unterstützen, und teure Neuzuweisung, wenn man Eigentum als Vec übergeben muss:

    Ich würde erwarten:

    pub struct NonEmpty<T> {

    v: Vec<T>,

    }

    Der Konstruktor würde die Invariante erzwingen, und dann würde man Deref und DerefMut für [T] implementieren, um normales len/is_empty/Indexing/Iteration zu erhalten, Übergabe als &[T] an andere Funktionen und Mutation von Werten (was die Invariante nicht brechen kann).

    Um die Länge zu ändern, während die Invariante erhalten bleibt, ist es Geschmackssache, z. B.:

    - .into_vec() hinzufügen für unwrap/mutate/rewrap

    - invariant-erhaltende Mutatoren nach Wahl hinzufügen

  3. OptionOfT

    Zur Fußnote:

    > [1] Andere Sprachen – wie Go oder Python – haben eine Laufzeitprüfung, die eine Art Ausnahme oder Panik auslöst, wenn auf lst[0] bei einer leeren Liste oder einem leeren Slice zugegriffen wird.

    Rust hat dasselbe. Der Zugriff auf einen `Vec` per Index erfolgt über das Index-Trait: https://doc.rust-lang.org/std/ops/trait.Index.html#tymethod....

    Vec implementiert Index hier: https://github.com/rust-lang/rust/blob/d080e7dff1b0fc5454154...

    Vecs Index delegiert an den von slice: https://github.com/rust-lang/rust/blob/d080e7dff1b0fc5454154...

    Slice delegiert an... Intrinsics: https://github.com/rust-lang/rust/blob/d080e7dff1b0fc5454154...

    Was eine Grenzprüfung einfügt: https://github.com/rust-lang/rust/blob/d080e7dff1b0fc5454154...

    Die Grenzprüfung: https://github.com/rust-lang/rust/blob/d080e7dff1b0fc5454154...

    Also würde ich sagen, die Fußnote ist nicht korrekt.

  4. jelder

    Das ist großartig. Alexis King hat tatsächlich gesagt, dass sie, hätte sie gewusst, wie populär „Parse, Don’t Validate“ werden würde, es in einer weiter verbreiteten Sprache als Haskell geschrieben hätte.

  5. jph

    Guter Artikel über Rusts Stärken mit Typen. Wenn dir das gefällt, könnte es dich interessieren, wie du deine eigenen Parse-Fähigkeiten aufbauen könntest. Ich mag die Rust-Crates Winnow und Nom, und auch die Rust-Traits From und Into.

Mehr von diesem Tag

2026-09-27