Skip to content

Add compile-time typestates and infallible accessors - #277

Open
yi-json wants to merge 4 commits into
google:rust_dev_typestatesfrom
yi-json:rust_dev_typestates
Open

Add compile-time typestates and infallible accessors#277
yi-json wants to merge 4 commits into
google:rust_dev_typestatesfrom
yi-json:rust_dev_typestates

Conversation

@yi-json

@yi-json yi-json commented Aug 3, 2026

Copy link
Copy Markdown

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 changes

What's included in this stack:

  1. rust: Implement compile-time typestates and infallible accessors: Defines typestates markers (UncheckedState, CompleteState) and traits (IsComplete, CheckComplete, InfallibleRead, InfallibleWrite) in typestate.rs and implements .read() / .write(val) on primitive and enum views when a view is in a complete state.
  2. rust: Add Typestate support to generated Rust struct views: Updates the Rust backend templates to emit the ST typestate parameter, implement CheckComplete on generated views, and clean up imported types from the prelude.

Testing

  • cargo test in runtime/experimental/rust
  • bazel test //compiler/back_end/experimental/rust/testcode/...

@yi-json
yi-json marked this pull request as draft August 3, 2026 21:52
@yi-json
yi-json marked this pull request as ready for review August 3, 2026 21:53
Comment thread compiler/back_end/experimental/rust/generated_code_templates Outdated
}

impl<S: MutStorage> ${struct_name}Mut<S> {
impl<S: MutStorage> ${struct_name}Mut<S, UncheckedState> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two aspects to this:

  1. 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
  2. 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 self setters cannot downgrade typestates. Would you prefer we keep StructMut typestates (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))
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These impls just assert that every struct is complete, that's not right, we need to check the struct's completeness

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread compiler/back_end/experimental/rust/generated_code_templates
Comment thread compiler/back_end/experimental/rust/generated_code_templates Outdated
Comment thread runtime/experimental/rust/src/lib.rs Outdated
Comment thread runtime/experimental/rust/src/lib.rs Outdated
Comment thread runtime/experimental/rust/src/lib.rs Outdated
Comment thread runtime/experimental/rust/src/typestate.rs Outdated
Comment thread runtime/experimental/rust/src/typestate.rs
yi-json added 2 commits August 5, 2026 15:04
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.
@yi-json
yi-json force-pushed the rust_dev_typestates branch from b3952e7 to fc18c79 Compare August 5, 2026 15:06
Comment thread compiler/back_end/experimental/rust/generated_code_templates
Comment thread runtime/experimental/rust/src/prelude.rs
Comment thread runtime/experimental/rust/src/typestate.rs
Comment thread runtime/experimental/rust/src/typestate.rs
Comment thread runtime/experimental/rust/src/typestate.rs
Comment thread runtime/experimental/rust/src/typestate.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants