Skip to content

Conversation

chandlerc
Copy link
Contributor

@chandlerc chandlerc commented Aug 5, 2025

Introduce a more formal model for how unformed state is managed for types. This
includes:

  • Specifying the interfaces used by types to opt into unformed state, and how
    they work.
  • Providing a Core.MaybeUnformed(T) wrapper that models the API subset of
    T available both when fully formed and unformed.
  • Initial concepts of unsafe adapt and unsafe as to model unsafe type
    conversions between compatible types.
  • A flow-sensitive set of rules for how unformed types become fully formed.

@chandlerc chandlerc added proposal A proposal proposal draft Proposal in draft, not ready for review labels Aug 5, 2025
@chandlerc chandlerc marked this pull request as ready for review August 6, 2025 01:19
@github-actions github-actions bot requested a review from zygoloid August 6, 2025 01:19
@github-actions github-actions bot added proposal rfc Proposal with request-for-comment sent out and removed proposal draft Proposal in draft, not ready for review labels Aug 6, 2025
Comment on lines +799 to +800
provided the API could legitimately initialize the type. After this cast, the
object should be assumed well formed, as it would be normally.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is saying that the following

var t: T;
F(t unsafe as ref T);

Would consider t to be initialized after.

The proposal doesn't say this AFAICT. It says t is initialized if passed to a fn that takes ref MaybeUnformed(T) (but not MaybeUnformed(T)*).

So two potential gaps here, I think:

  • Does taking the address of the uninitialized var also make it considered as initialized? As most C++ out-params are going to be pointers.
  • Does forming a reference expression to T also make it considered as initialized, or just a reference expression to MaybeUnformed(T)?

Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, It would be t unsafe as T not t unsafe as ref T. ref T is not a type, and this specific conversion will preserve the category so it is enough that t is a reference expression. On the other hand, it might be F(ref ...) if F takes a ref parameter, so this might end up being F(ref t unsafe as T).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the flow sensitive section does restrict this as it restricts the operation set.

Currently, I think even the above would be rejected. I think it has to be: F((ref t as Core.MaybeUnformed(T)) unsafe as T) ... which seems ... suboptimal.

We could build a wrapper to do the explicit escape:

unsafe fn Escape[T:! type](bound ref obj: Core.MaybeUnformed(T)) -> ref T {
  return obj unsafe as T;
}

And then use it:

var t: T;
F(unsafe Escape(ref t));

But it's not clear this is a great experience either.

Is it too ad-hoc to suggest unsafe ref instead of ref in this proposal to do this? Basically, unsafe ref would turn off the flow sensitive restrictions on unformed objects operation usage.

I suppose the down side is that we don't use ref for the object parameter, so this would only support non-methods. Personally, that works for me because methods I feel like are better situated to instead use Core.MaybeUnformed(T) themselves. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps we could allow the use of the name of a variable in an unformed state as the left operand of unsafe as in general? (In addition to allowing it to be implicitly converted to MaybeUnformed(T).)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps we could allow the use of the name of a variable in an unformed state as the left operand of unsafe as in general? (In addition to allowing it to be implicitly converted to MaybeUnformed(T).)

We could, it just feels like it'll be a bit surprising syntax wise:

var t: T;
F(ref (t unsafe as T));

It seems strange to cast t to T right after declaring it with that type.

Relatedly, is this how we want ref-form-preserving as-style expressions (or other such expressions) to interact with ref markings on arguments? Do we need parentheses there? But those are all questions for ref, not unformed...

Comment on lines 156 to 160
choice, which result in its own set of tradeoffs:

- Types with neither of the above properties _cannot support unformed state_.
- Some types may elect to not support it as that may result in a better API
design for that specific type.
Copy link
Contributor

Choose a reason for hiding this comment

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

These two bullets don't seem to be describing the set of tradeoffs for not having unformed state, they seem to just be repeating "by necessity or choice."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, reworded.

Comment on lines 852 to 865
Fundamentally, the goal of modeling unformed state is somewhat different from
`MaybeUninit` -- it is about providing access to types' internal invalid or
no-op states in order to provide improved ergonomics around initialization
without a significant safety loss. It is very much focused on enabling _type
design_ to opt into this, rather than intended for user code dealing with
complex initialization. Carbon may well end up needing something like
`MaybeUninit` to manage cases which cannot be modeled safely. Currently, the
plan is to directly use raw storage for this, but if doing so surfaces an
important abstraction layer on top like `MaybeUninit`, we should add it.

The differences in functionality provided by `Core.MaybeUnformed(T)` and its
expected usage (in assignment and destruction) follows from this different goal
of allowing a type to create an efficient and ergonomic API with initialization
flexibility.
Copy link
Contributor

Choose a reason for hiding this comment

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

This talks a lot about what Carbon's Core.MaybeUnformed(T) is trying to do, but not how that is different from what Rust's MaybeUninit does -- is it only "user code dealing with complex initialization"? I thought it included delaying initialization of things like I/O buffers until data was read -- which seems pretty similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tried to do a better job of spelling out what I meant here, PTAL.

Comment on lines 838 to 845
### Subsetting by fields

We propose subsetting by fields rather than by a more complex approach such as a
[bit-mask](#bit-mask-based-unformed-state) because this is expected to be easy
to implement at low overhead, and meshes nicely with the struct literal syntax
already present in Carbon. This seems like an effective starting position, but
we should revisit it when introducing bitfields more fully to the language and
determining how to reason about them.
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels like maybe this section should be merged with the alternative considered (or just deleted since the alternative might be enough by itself).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, deleted this and the previous section. Not adding anything.

The `UnformedHardenInit.StructT` type has similar restrictions as
`UnformedInit.StructT` -- its fields must be a subset of the types fields, each
a compatible-with type. Further, the `UnformedHardenInit.StructT` must also be a
superset of the fields in `UnformedInit.StructT`. We refer to _hardening_ of an
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it OK for the hardened build mode to use a different representation in the fields in UnformedInit.StructT than the representation that UnformedInit would use, or is the goal here only to allow additional fields to have values specified?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My goal was to let it have a different, but compatible representation. Although that may not have much utility. They're still fields of T, and each type has to be compatible with fields of T. And you can't access the fields (or the types used) from here. Reads always go through MaybeUnformed(T) and that in turn through the non-hardening struct.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess I'm worried about something like:

  • A pointer type says its unformed value is 0
  • We use that value for detecting whether it's unformed
  • It then says that its hardened value is some other bit pattern that's in some way better for hardening purposes

... and our "is it unformed" detection then fails to detect that a hardened unformed value is in fact unformed.

It'd be nice if we could make that kind of bug impossible, by ensuring that the hardened unformed state and the non-hardened unformed state are somehow consistent with each other.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is what the match_first impls for IsUnformed provide when using the UnformedInvalid and UnformedHarden interfaces. It ensures when the harden interface is implemented, it is included in the InUnformed implementation.

The only way to write this bug is to use the lower level UnformedInit and UnformedHardenInit interfaces, manually implementing both, and then manually implementing IsUnformed incorrectly.

I don't really see a way to preclude that, but it at least seems like the well lit path doesn't end up there?

The challenge with making the unformed and hardened states required to be consistent is if the value used in the non-hardened case is lower cost but potentially less secure. In that case you want the value to differ between the two -- that's the whole point. The place where this comes up are things like indices where zero is a good, cheap unformed value, but happens to be "valid" and risky from a security perspective and so hardening prefers to set some other value like INT_MIN. Not expecting this to be common at all, but wanted to leave that flexibility in the design.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm. There's something that's not working well for me in this description: we talk about an object having "an unformed state" / "its unformed state" / "the unformed state" rather than having a set of unformed states, which to my reading implies that the thing that UnformedHarden gives you must either be consistent with what UnformedInit gives you (on the subset of fields that UnformedInit initializes) or is not an unformed state.

I think we don't have the terminology quite right, particularly about whether a type has a singular unformed state or a set of unformed states. If we want UnformedHardenInit to provide an unformed state, but not necessarily one that's consistent with the representation that UnformedInit provides, then the latter is not providing "the unformed state", just one possible unformed state, which means that the impl of IsUnformed in terms of the UnformedInvalid state isn't necessarily right, as there may be other unformed states. But if instead we want there to be a singular unformed state, then the state that an uninitialized variable is initialized to is not necessarily an unformed state (it won't be one in a hardened build), making the terminology there confusing, along with the name IsUnformed.

Perhaps we could use different words for the set of possible representations that an uninitialized variable can be in, versus the state that UnformedInit puts the object into? That might help to clarify that IsUnformed detects the former, not the latter.

This is what the match_first impls for IsUnformed provide when using the UnformedInvalid and UnformedHarden interfaces. It ensures when the harden interface is implemented, it is included in the InUnformed implementation.

I don't think that is a complete solution. For example, if a type provides an UnformedInvalid impl and an UnformedHardenInit impl, and the latter doesn't produce a state that's compatible with the one that UnformedInvalid computes, then you would use the second impl from the match_first, which does the wrong thing for a hardened value. (I think I'd be fine with rejecting that set of impls if there's a good way to do so.)

Do we expect the "hardened" state to be global, or could it vary between (say) packages in the same program? Do we actually need two sets of interfaces, and an or in the impl of IsUnformed, or could we instead have a single UnformedInvalid interface that sometimes produces a hardened value based on the build configuration?

Comment on lines 411 to 416
**Open question:** It isn't clear how implementing another conversion interface
would work, but this is an issue already with adapters: how do you make an
adapter conversion _implicit_? There is already an implementation of `As`, so
implementing `ImplicitAs` would be an error -- it would be subsumed by the
`extend impl as`. And even if you _could_, you couldn't actually write the
Convert function as that would require using the synthesized one.
Copy link
Contributor

Choose a reason for hiding this comment

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

Would

impl as ImplicitAs(C) = C;

work? C is a compatible type that implements ImplicitAs(C).

chandlerc and others added 5 commits August 7, 2025 14:38
Copy link
Contributor Author

@chandlerc chandlerc left a comment

Choose a reason for hiding this comment

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

Thanks so much for all the review!!!

I think I've gotten to most and maybe even all of the comments. PTAL!

Comment on lines 156 to 160
choice, which result in its own set of tradeoffs:

- Types with neither of the above properties _cannot support unformed state_.
- Some types may elect to not support it as that may result in a better API
design for that specific type.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, reworded.

The `UnformedHardenInit.StructT` type has similar restrictions as
`UnformedInit.StructT` -- its fields must be a subset of the types fields, each
a compatible-with type. Further, the `UnformedHardenInit.StructT` must also be a
superset of the fields in `UnformedInit.StructT`. We refer to _hardening_ of an
Copy link
Contributor Author

Choose a reason for hiding this comment

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

My goal was to let it have a different, but compatible representation. Although that may not have much utility. They're still fields of T, and each type has to be compatible with fields of T. And you can't access the fields (or the types used) from here. Reads always go through MaybeUnformed(T) and that in turn through the non-hardening struct.

Comment on lines +799 to +800
provided the API could legitimately initialize the type. After this cast, the
object should be assumed well formed, as it would be normally.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the flow sensitive section does restrict this as it restricts the operation set.

Currently, I think even the above would be rejected. I think it has to be: F((ref t as Core.MaybeUnformed(T)) unsafe as T) ... which seems ... suboptimal.

We could build a wrapper to do the explicit escape:

unsafe fn Escape[T:! type](bound ref obj: Core.MaybeUnformed(T)) -> ref T {
  return obj unsafe as T;
}

And then use it:

var t: T;
F(unsafe Escape(ref t));

But it's not clear this is a great experience either.

Is it too ad-hoc to suggest unsafe ref instead of ref in this proposal to do this? Basically, unsafe ref would turn off the flow sensitive restrictions on unformed objects operation usage.

I suppose the down side is that we don't use ref for the object parameter, so this would only support non-methods. Personally, that works for me because methods I feel like are better situated to instead use Core.MaybeUnformed(T) themselves. Thoughts?

Comment on lines 838 to 845
### Subsetting by fields

We propose subsetting by fields rather than by a more complex approach such as a
[bit-mask](#bit-mask-based-unformed-state) because this is expected to be easy
to implement at low overhead, and meshes nicely with the struct literal syntax
already present in Carbon. This seems like an effective starting position, but
we should revisit it when introducing bitfields more fully to the language and
determining how to reason about them.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, deleted this and the previous section. Not adding anything.

Comment on lines 852 to 865
Fundamentally, the goal of modeling unformed state is somewhat different from
`MaybeUninit` -- it is about providing access to types' internal invalid or
no-op states in order to provide improved ergonomics around initialization
without a significant safety loss. It is very much focused on enabling _type
design_ to opt into this, rather than intended for user code dealing with
complex initialization. Carbon may well end up needing something like
`MaybeUninit` to manage cases which cannot be modeled safely. Currently, the
plan is to directly use raw storage for this, but if doing so surfaces an
important abstraction layer on top like `MaybeUninit`, we should add it.

The differences in functionality provided by `Core.MaybeUnformed(T)` and its
expected usage (in assignment and destruction) follows from this different goal
of allowing a type to create an efficient and ergonomic API with initialization
flexibility.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tried to do a better job of spelling out what I meant here, PTAL.

Comment on lines 411 to 416
**Open question:** It isn't clear how implementing another conversion interface
would work, but this is an issue already with adapters: how do you make an
adapter conversion _implicit_? There is already an implementation of `As`, so
implementing `ImplicitAs` would be an error -- it would be subsumed by the
`extend impl as`. And even if you _could_, you couldn't actually write the
Convert function as that would require using the synthesized one.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like the first one a bit more than the second, because we can't have a conversion to C while we're defining conversions to C...

Chatted a bit with @zygoloid about this live, and came up with the idea of explicitly extending the blanket impl when impl-ing the interface that produces the blanket impl. That seems like the case which we can make work (because we can use the explicit extension of the blanket impl to insist that we see it and everyone else sees it too), and ensure that our extension is coherent.

It does mean that we have yet another thing that makes negative constraints bad, but that seems OK.

I've added a description of this to the proposal, PTAL and let me know if this direction makes sense?

It raises some more open questions around final that I've added. Those at least seem more tactical questions we need to think through and less open-ended "how do we want this to work".

Copy link
Contributor

@danakj danakj left a comment

Choose a reason for hiding this comment

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

Few small things

@danakj
Copy link
Contributor

danakj commented Aug 12, 2025

Not flipping an approval bit - but I am happy with this when @zygoloid and @josh11b are.

Copy link
Contributor Author

@chandlerc chandlerc left a comment

Choose a reason for hiding this comment

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

Actually posting my replies instead of just pushing the commit and leaving them pending... =D PTAL!

The `UnformedHardenInit.StructT` type has similar restrictions as
`UnformedInit.StructT` -- its fields must be a subset of the types fields, each
a compatible-with type. Further, the `UnformedHardenInit.StructT` must also be a
superset of the fields in `UnformedInit.StructT`. We refer to _hardening_ of an
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is what the match_first impls for IsUnformed provide when using the UnformedInvalid and UnformedHarden interfaces. It ensures when the harden interface is implemented, it is included in the InUnformed implementation.

The only way to write this bug is to use the lower level UnformedInit and UnformedHardenInit interfaces, manually implementing both, and then manually implementing IsUnformed incorrectly.

I don't really see a way to preclude that, but it at least seems like the well lit path doesn't end up there?

The challenge with making the unformed and hardened states required to be consistent is if the value used in the non-hardened case is lower cost but potentially less secure. In that case you want the value to differ between the two -- that's the whole point. The place where this comes up are things like indices where zero is a good, cheap unformed value, but happens to be "valid" and risky from a security perspective and so hardening prefers to set some other value like INT_MIN. Not expecting this to be common at all, but wanted to leave that flexibility in the design.

Comment on lines +560 to +561
potentially accessing uninitialized memory. That hardening is expected to be
handled by the compiler and language automatically.
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you say that anywhere?

Comment on lines +495 to +497
fn Convert[self: Self]() -> Core.MaybeUnformed(T) {
// Initialize the fields of `T` that are part of `StructT`.
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems expensive to check that the result causes IsUnformed to return true every time this happens, but the invariants of the system rely on that.

Comment on lines 411 to 416
**Open question:** It isn't clear how implementing another conversion interface
would work, but this is an issue already with adapters: how do you make an
adapter conversion _implicit_? There is already an implementation of `As`, so
implementing `ImplicitAs` would be an error -- it would be subsumed by the
`extend impl as`. And even if you _could_, you couldn't actually write the
Convert function as that would require using the synthesized one.
Copy link
Contributor

Choose a reason for hiding this comment

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

Fine with me. It seems to work with final, as you observed.

The `UnformedHardenInit.StructT` type has similar restrictions as
`UnformedInit.StructT` -- its fields must be a subset of the types fields, each
a compatible-with type. Further, the `UnformedHardenInit.StructT` must also be a
superset of the fields in `UnformedInit.StructT`. We refer to _hardening_ of an
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm. There's something that's not working well for me in this description: we talk about an object having "an unformed state" / "its unformed state" / "the unformed state" rather than having a set of unformed states, which to my reading implies that the thing that UnformedHarden gives you must either be consistent with what UnformedInit gives you (on the subset of fields that UnformedInit initializes) or is not an unformed state.

I think we don't have the terminology quite right, particularly about whether a type has a singular unformed state or a set of unformed states. If we want UnformedHardenInit to provide an unformed state, but not necessarily one that's consistent with the representation that UnformedInit provides, then the latter is not providing "the unformed state", just one possible unformed state, which means that the impl of IsUnformed in terms of the UnformedInvalid state isn't necessarily right, as there may be other unformed states. But if instead we want there to be a singular unformed state, then the state that an uninitialized variable is initialized to is not necessarily an unformed state (it won't be one in a hardened build), making the terminology there confusing, along with the name IsUnformed.

Perhaps we could use different words for the set of possible representations that an uninitialized variable can be in, versus the state that UnformedInit puts the object into? That might help to clarify that IsUnformed detects the former, not the latter.

This is what the match_first impls for IsUnformed provide when using the UnformedInvalid and UnformedHarden interfaces. It ensures when the harden interface is implemented, it is included in the InUnformed implementation.

I don't think that is a complete solution. For example, if a type provides an UnformedInvalid impl and an UnformedHardenInit impl, and the latter doesn't produce a state that's compatible with the one that UnformedInvalid computes, then you would use the second impl from the match_first, which does the wrong thing for a hardened value. (I think I'd be fine with rejecting that set of impls if there's a good way to do so.)

Do we expect the "hardened" state to be global, or could it vary between (say) packages in the same program? Do we actually need two sets of interfaces, and an or in the impl of IsUnformed, or could we instead have a single UnformedInvalid interface that sometimes produces a hardened value based on the build configuration?

valid state for the type. Types can customize the implementation of `IsUnformed`
in the usual way for interfaces, but those need to uphold the contract of
definitively testing for an object being in an unformed (and potentially
hardened) state.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it'd help to rename UnformedHarden to UnformedHardenInvalid to emphasize that it must produce an invalid state, like UnformedInvalid does.

github-merge-queue bot pushed a commit that referenced this pull request Aug 28, 2025
Following the direction of #5913, add support for parsing an `unsafe as`
operator. For now, we allow one additional conversion using `unsafe as`
beyond the conversions supported by `as`: we permit pointer conversions
that remove qualifiers, such as `const T*` -> `T*`.
Co-authored-by: Richard Smith <[email protected]>
Co-authored-by: josh11b <[email protected]>
Comment on lines 436 to 441
`extend impl` will end with a semicolon `;`, instead of a definition block in curly
braces `{`...`}`, but acts as a definition of the members of the extended
interface. This also allows
the found _impl_ to be `final` because this precludes any changes to the aspects
of the `impl` that were final. This allows adapters to extend which layer of
these kinds of interface hierarchies they implement without breaking coherence:
Copy link
Contributor

Choose a reason for hiding this comment

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

[diff] reported by reviewdog 🐶

Suggested change
`extend impl` will end with a semicolon `;`, instead of a definition block in curly
braces `{`...`}`, but acts as a definition of the members of the extended
interface. This also allows
the found _impl_ to be `final` because this precludes any changes to the aspects
of the `impl` that were final. This allows adapters to extend which layer of
these kinds of interface hierarchies they implement without breaking coherence:
`extend impl` will end with a semicolon `;`, instead of a definition block in
curly braces `{`...`}`, but acts as a definition of the members of the extended
interface. This also allows the found _impl_ to be `final` because this
precludes any changes to the aspects of the `impl` that were final. This allows
adapters to extend which layer of these kinds of interface hierarchies they
implement without breaking coherence:

Comment on lines 545 to 551
`IsUnformed` implementation (but do support unformed state) _must_ implement assignment and destruction with
`Core.MaybeUnformed(T)`. When assignment or destruction are implemented with
`Core.MaybeUnformed(T)`, they are called regardless of whether the object is in
the unformed state. When assignment or destruction are implemented with `T`, the
implementation will only call them if `IsUnformed` returns false and using the
normal type. If the `IsUnformed` returns true in these cases, assignment will be
replaced with initialization, and destruction will be skipped.
Copy link
Contributor

Choose a reason for hiding this comment

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

[diff] reported by reviewdog 🐶

Suggested change
`IsUnformed` implementation (but do support unformed state) _must_ implement assignment and destruction with
`Core.MaybeUnformed(T)`. When assignment or destruction are implemented with
`Core.MaybeUnformed(T)`, they are called regardless of whether the object is in
the unformed state. When assignment or destruction are implemented with `T`, the
implementation will only call them if `IsUnformed` returns false and using the
normal type. If the `IsUnformed` returns true in these cases, assignment will be
replaced with initialization, and destruction will be skipped.
`IsUnformed` implementation (but do support unformed state) _must_ implement
assignment and destruction with `Core.MaybeUnformed(T)`. When assignment or
destruction are implemented with `Core.MaybeUnformed(T)`, they are called
regardless of whether the object is in the unformed state. When assignment or
destruction are implemented with `T`, the implementation will only call them if
`IsUnformed` returns false and using the normal type. If the `IsUnformed`
returns true in these cases, assignment will be replaced with initialization,
and destruction will be skipped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal rfc Proposal with request-for-comment sent out proposal A proposal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants