Add compile-time typestates and infallible accessors - #277
Conversation
rust: Ignore Cargo build artifacts in .gitignore
| } | ||
|
|
||
| impl<S: MutStorage> ${struct_name}Mut<S> { | ||
| impl<S: MutStorage> ${struct_name}Mut<S, UncheckedState> { |
There was a problem hiding this comment.
I'm not sure a StructMut is safe to use typestates with at all. What happens when someone mutates a field that invalidates the state? Without consuming the StructMut you can't change the typestate, and unsafe behavior can occur.
There was a problem hiding this comment.
There are two aspects to this:
- Memory Safety: Because all Accessors wrap checked slice operations (
try_read/try_write), mutating a field that invalidates a layout will at worst trigger a safe runtime panic (.expect()), never undefined behavior - For fixed-layout structs (vast majority of protocol headers), field mutations can never alter offsets of invalidate
CompleteState. For dynamic or conditional structs where mutating a tag/length field invalidates dependent fields, we agree that&mut selfsetters cannot downgrade typestates. Would you prefer we keepStructMuttypestates (since fixed structs benefit immensely from infallible writes), or restrict infallible mutable Accessors on conditional/dynamic schemas?
| fn check_complete(self) -> Result<Self::Completed, Error> { | ||
| Result::Ok(${struct_name}::new_in_state(self.storage)) | ||
| } | ||
| } |
There was a problem hiding this comment.
These impls just assert that every struct is complete, that's not right, we need to check the struct's completeness
There was a problem hiding this comment.
In that case, in emboss_codegen_rust.py, we added a new field called min_size_in_bytes and passed it to CheckComplete::check_complete(), which now checks let _ = self.storage.slice(0, ${min_size_in_bytes}}?;. If the backing storage is smaller than the minimum byte length required by the struct schema, .check_complete() returns Err(Error::OutOfBounds). Let me know if this check is sufficient for completeness!
There was a problem hiding this comment.
Not quite, the new check only checks for minimally complete, not complete. We don't want the complexity of minimally complete for an initial implementation of typestates, we may end up dropping that state entirely. Complete is necessary, though.
The frontend gives us everything we need to compute complete for a given view. It's just an expression.
Introduce compile-time typestate tracking (UncheckedState, CompleteState, etc) and marker traits (State, IsComplete, CheckComplete) to verify struct layout completeness. Define standalone InfallibleRead and InfallibleWrite traits across primitive and enum view wrappers. When a view is in CompleteState, callers can use infallible read() and write() methods without runtime bounds checks or unwrap() calls.
Update generated code templates so that Emboss schemas compile into typestate-aware Rust struct views and field accessors. Generated views default to UncheckedState to preserve backward compatibility while enabling optional compile-time typestate tracking. Implement CheckComplete on generated views to verify layout rules and return views in a complete state and propagate the typestate parameter across feild accessors, requiring inner struct views to be independently validated.
b3952e7 to
fc18c79
Compare
Compile-time Type States and Checked Field Accessors
This PR adds optional compile-time typestate tracking (
UncheckedState,CompleteState, etc.) to the experimental Rust runtime and compiler backend.The main goal is to let callers read and write checked fields without needing
.try_read().unwrap()or.expect()everywhere in consumer code. Once a view is checked with.check_complete(), any field that is guaranteed to be present can be accessed directly via.read()and.write(val).All generated views default to
ST: State = UncheckedState, so existing code continues to Compiler and work without breaking any changesWhat's included in this stack:
rust: Implement compile-time typestates and infallible accessors: Defines typestates markers (UncheckedState,CompleteState) and traits (IsComplete,CheckComplete,InfallibleRead,InfallibleWrite) intypestate.rsand implements.read()/.write(val)on primitive and enum views when a view is in a complete state.rust: Add Typestate support to generated Rust struct views: Updates the Rust backend templates to emit theSTtypestate parameter, implementCheckCompleteon generated views, and clean up imported types from the prelude.Testing
cargo testinruntime/experimental/rustbazel test //compiler/back_end/experimental/rust/testcode/...