Make DefaultViewInstance safe via compiler-checked covariance#71
Merged
Make DefaultViewInstance safe via compiler-checked covariance#71
Conversation
Replace the unsafe HasDefaultViewInstance + raw pointer cast in MessageFieldView::deref with a single safe DefaultViewInstance trait implemented for FooView<'v> at every lifetime. The impl body returns a &'static FooView<'static> at the caller-chosen &'a FooView<'v>; Rust accepts that coercion iff FooView is covariant in 'v, so the compiler now verifies what was previously an unsafe-trait promise. Removes the last unsafe from the default-instance machinery.
Mirrors the DefaultInstance doc pattern so hand-implementers see the canonical OnceBox shape and the compiler-checked covariance note in rustdoc, not just the changelog. Also align the TinyView test impl with the codegen-emitted form.
3d4aeec to
4726f62
Compare
Contributor
|
LGTM, I suggest adding a compile_fail doctest on the trait that tries to impl it for an invariant type (e.g. one holding |
rpb-ant
previously approved these changes
Apr 27, 2026
CI-enforces the covariance check at the heart of the safe DefaultViewInstance design: a type invariant in its lifetime parameter cannot return its 'static default at a shorter lifetime, so the recommended impl pattern fails to compile. Uses PhantomData<fn(&'v ()) -> &'v ()> for invariance (rather than Cell) so the only error is the lifetime coercion, not a Sync bound on the static.
Collaborator
Author
|
[claude code] Good call — added in the latest push. One tweak: which is the property we actually want pinned. |
rpb-ant
approved these changes
Apr 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #70.
Summary
Removes the last
unsafefrom the default-instance machinery by collapsing the two view-default traits into one safe trait whose covariance contract is checked by the compiler.Previously,
DefaultViewInstancewas implemented only forFooView<'static>, and a separateunsafe trait HasDefaultViewInstancelinkedFooView<'a>to its'staticinstantiation so thatDeref for MessageFieldView<V>could serve the static default at any lifetime via a raw pointer cast:That cast is sound only if
FooViewis covariant in'a— a promise the compiler can't verify through a*const u8, hence theunsafe trait.This PR moves the lifetime coercion into the per-type impl body, where the compiler checks covariance via ordinary subtyping:
The return expression has type
&'static FooView<'static>; the function must return&'a FooView<'v>. Rust accepts that coercion iffFooViewis covariant in'v— and rejects it with a lifetime error otherwise. So a non-covariant view type now fails to compile at the impl site instead of being a silently-unsoundunsafe impl.Deref for MessageFieldView<V>becomes plain safe code:Why this is sound
A safe implementation cannot cause UB: there is no associated type to point at the wrong layout, and any
&'a Selfproduced in safe Rust is valid by construction. The only contract not expressible in the old type signature — covariance — is now verified per-impl by the borrow checker.Covariance verified end-to-end
All generated view field types are covariant in
'a:&'a str,&'a [u8]— covariantMessageFieldView<V>isOption<Box<V>>— covariant inVRepeatedView<'a, T>isVec<T>+PhantomData<&'a ()>— covariantMapView<'a, K, V>isVec<(K, V)>+PhantomData<&'a ()>— covariant (notably notHashMap, which would be the one risk)The workspace (including
buffa-testwhich exercises every field shape, andStructViewwhich has aMapViewfield) compiles cleanly under the new scheme, confirming this in practice.Changes
buffa/src/view.rs: newDefaultViewInstancesignature;HasDefaultViewInstanceanddefault_view_ptrdeleted;Deref/PartialEq/EqforMessageFieldViewnow bound onDefaultViewInstancewith nounsafeblock;TinyViewtest impl updated.buffa/src/lib.rs:HasDefaultViewInstanceremoved from public exports.buffa-codegen/src/view.rs: emit the single lifetime-genericimpl<'v> DefaultViewInstance for #view_ident<'v>; old two-impl emission removed.CHANGELOG.md: extended the DefaultInstance trait is not actually unsafe #68/DefaultViewInstance is not actually unsafe #69 entry.buffa-types/src/generated/*.__view.rs.Migration
Hand-written view types replace their two impls with one:
Breaking change
Yes — targeted for v0.4.0 alongside #70.