Proposal: Add parent
attribute for pointers to struct fields
#9724
Labels
proposal
This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone
Motivation
We'd like to prevent some of the existing foot-guns with Zig's current preferred pattern for run-time polymorphism via
@fieldParentPtr
(#591)In particular, the foot-guns associated with this pattern seem to be of two kinds:
Since lifetime-related problems are a well-understood (although un-resolved) footgun in Zig and have impacts well beyond this particular usage pattern, these are out of scope for this proposal. Instead, the goal will to be to tackle the unique foot-guns that occur with using a pointer to an embedded vtable: the sub-object mistake.
The central idea is to add additional pointer attribute(s) to make pointers to embedded objects (
&x.f
) distinct from pointers to isolated objects (&f
).Doesn't
pinned
already solve this?The pinned proposal claims to solve both of these problems for this usage pattern, but it does so by over-restricting the behavior on these objects. For self-referencing structs, the move restrictions associated with
pinned
are appropriate, but in this case, the struct is not self-referencing. Thepinned
solution prevents moving an object with an embedded vtable, a much stronger restriction than necessary for safely using an object with a runtime-polymorphic function.Furthermore,
pinned
does not prevent the accidental usage of a vtable that was instantiated outside of an object to begin with (and therefore is still not embedded in any object).Proposal
This proposal builds on the
erased
type parameters of #9723. Please take a look at that proposal to see howerased
is intended to work in general.We add an additional pointer attribute
parent(T, field_name)
, or when unambiguous simplyparent(T)
, which is automatically added to a pointer generated via sub-field access (&x.a
).We can now adapt
fieldParentPtr
to use our new attribute:@fieldParentPtr(type, []const u8, *T) *ParentType
becomes@parentPtr(* parent(U, ...) T) *U
.Here's what an extremely basic example of an embedded vtable looks like now:
The immediate benefits are:
VTable
object.fn(* parent(@This()) VTable) void
) to a runtime-erased function pointer (*fn(* parent(erased) VTable) void
), it is in theory able to insert a debug-mode check to verify that theerased
attribute matches the type of the function ultimately called. This verifies that the dispatch table embedded inside the struct is type-compatible with it.Casting Behavior
As you'd expect, a pointer with this attribute eagerly "strips off". That is,
* parent(...) T
casts automatically to* T
. Functions accepting these parameters naturally vary in the opposite way. That is,fn(* parent(U) T) void
casts automatically tofn(* parent(erased) T) void
(inserting a debug check), but does not cast automatically tofn(*T) void
Extension: Sub-ranges of a slice
Since we're tracking sub-pointers in general, it may be reasonable to also add a
parent(U, offset)
attribute, possibly with a different name likesubrange
, which would allow for statically encoding an "offset" pointer into a buffer. This allows the type-system to encode, e.g. a slice that is actually used to access a region with extra data slightly out-of-bounds (effectively a header or footer)Appendix: Syntax
I'm not completely satisfied with the verbosity of the
* parent(P, field_name) T
syntax or the overloaded nature of the attribute. Improvements in this area are more than welcome, if anyone has ideasThe text was updated successfully, but these errors were encountered: