# C# Language Design Meeting for May 13th, 2026

## Agenda

- [Unsafe evolution, continued](#unsafe-evolution-continued)
    - [`safe` marker syntax](#safe-marker-syntax)
    - [`unsafe` on types](#unsafe-on-types)
    - [`unsafe` fields](#unsafe-fields)
    - [Explicit and extended layout fields](#explicit-and-extended-layout-fields)
    - [Implicit calls and constructor edge cases](#implicit-calls-and-constructor-edge-cases)

## Quote of the Day

- "AAAAAAAAAAAAAAAAAAAAA"

## Discussion

### Unsafe evolution, continued

Champion issue: https://github.com/dotnet/csharplang/issues/9704  
Spec: https://github.com/dotnet/csharplang/blob/70322a245030729befed7aed4ce43c6a5e292e04/proposals/unsafe-evolution.md

Continuing from our [previous discussion](./LDM-2026-04-06.md#unsafe-evolution-continued), we worked through a
number of remaining questions in the `unsafe` evolution proposal. The common thread across the discussion was how to
preserve a clear distinction between a member that is caller-unsafe and code that merely happens to contain `unsafe`
implementation details. We also spent time on cases where the language or runtime implicitly performs work on behalf
of user code, since those cases can accidentally bypass the normal requirement that caller-unsafe operations occur in
an `unsafe` context.

#### `safe` marker syntax

We started by returning to `extern` members. We had already decided that, under the updated rules, `extern` members
should be explicitly annotated as either `safe` or `unsafe`, because most such members are caller-unsafe but the rare
safe cases should be intentional. The open question was how to spell the safe side.

The main options were a `safe` keyword or an attribute. An attribute has some attractive properties: it is cheaper to
add initially, it is easier to remove or replace before the feature ships, and it avoids introducing a keyword for what
might be a rare escape hatch. Several of us were also concerned that a `safe` keyword could be read as a stronger
guarantee than intended, as though it enabled special checking or strictness rather than simply recording that the
member does not require an unsafe context.

On the other hand, a keyword better matches the surface model. `unsafe` is represented syntactically, and `safe` is the
natural opposite when a declaration site must make an explicit choice. There are also plausible future uses beyond
`extern`: a member with pointer-shaped signatures might want to document that its safety was deliberately considered,
and we may later want some form of `safe` region or expression. If `safe` is only allowed on `extern` members, that is
probably not enough to justify a keyword; if it is part of a broader declaration model, the keyword becomes more
reasonable.

We briefly leaned toward an attribute as the cheaper option, then reconsidered the cost of needing an API design for
that attribute and the mismatch of using metadata to represent the absence of caller-unsafety. In the end, we decided
to try the keyword for now while keeping the door open to changing course if implementation or customer feedback
shows that it is the wrong tradeoff.

#### Conclusion

We will use the `safe` keyword to mark members that require an explicit `safe` annotation, such as `extern` members
marked `safe`.
We are still open to revisiting this before the feature ships.

#### `unsafe` on types

We then revisited `unsafe` on type declarations. Under today's language, `unsafe` on a type creates an unsafe context
for the members of that type. We had previously determined that, under the updated model, this keyword is a no-op on
types: `unsafe` on a member says that the member is caller-unsafe, while `unsafe` in a body establishes an unsafe
implementation context. A type itself is not called and cannot directly be caller-unsafe.

The compatibility concern is substantial. Searches of public code show many uses of `unsafe` on types, and making those
uses errors will create visible migration work. However, leaving the modifier as a warning or no-op risks a more
confusing model. It would allow source to retain a modifier whose old meaning has gone away, and it could obscure the
need to make a real decision for each member or body. In many migrated codebases, those type-level uses will already
need other edits because unsafe operations have to move to member-level annotations or local unsafe contexts.

We also want to preserve future design space. It is not clear that we will ever want `unsafe` on a type to mean that
all members are caller-unsafe, but it remains a plausible future direction, especially if `safe` exists as a way to
carve out exceptions. If we leave `unsafe` on types accepted with no meaningful behavior now, changing it later would
be another breaking change. Making it an error gives us a clearer model and gives us a chance to learn from any
unexpected migration pain before the feature is final.

#### Conclusion

In the updated rules, `unsafe` on a type declaration will be an error. We will use the resulting feedback as a scream
test and revisit if compelling scenarios appear.

#### `unsafe` fields

Next we discussed whether fields can be caller-unsafe. Today, `unsafe` on a field primarily affects the field
initializer. The working group proposed allowing fields themselves to be marked caller-unsafe, so that reading or
writing the field requires an unsafe context.

We think this is necessary. Some fields carry invariants that are not enforced by the language or runtime but are
nevertheless required for memory safety. Examples included fields whose values must stay in a particular set of valid
states, `Span<T>`-like representation fields with runtime assumptions, and fields involved in explicit layout. In those
cases, the owner of the member needs a way to tell callers that direct access requires an audit.

This also keeps the model uniform: any member can be caller-unsafe, and the annotation means the same thing across
member kinds. We explicitly separated that from initializer safety. Marking a field `unsafe` should not make its
initializer an unsafe context. If the initializer needs to perform `unsafe` work, that is a separate local operation
and may motivate an `unsafe` expression or similar future syntax.

#### Conclusion

Fields can be marked caller-unsafe. Accessing such a field requires an unsafe context, but the field initializer is not
made an unsafe context merely because the field is marked `unsafe`.

#### Explicit and extended layout fields

We then applied the field decision to explicit and extended layout structs. The question was whether all fields in such
layouts should be required to carry an explicit `safe` or `unsafe` annotation.

Explicit layout can allow overlapping storage, expose padding or uninitialized data, and create representation patterns
whose validity depends on conventions outside the type system. Even when a particular layout is carefully designed, the
runtime and language are generally not proving that it is safe. The safe use of the layout often depends on additional
caller-side knowledge, such as which union case is active, whether offsets and alignment are correct, or whether a
different API should have been used instead of layout reinterpretation.

There was some concern that requiring an annotation on every field in an explicit or extended layout struct is a broad
break. We looked at rough public-code counts and concluded that the impact is real but not prohibitive, especially
because the updated unsafe rules are already an opt-in migration. The requirement is also useful: it forces authors of
these layouts to make their safety judgment explicit, and it avoids allowing a safe field to overlap an unsafe one in a
way that bypasses the caller-unsafe model.

#### Conclusion

All fields in an explicit-layout or extended-layout struct must be explicitly marked `safe` or `unsafe` under the
updated rules.

#### Implicit calls and constructor edge cases

Finally, we discussed edge cases where there may be no obvious syntactic place for a user to write an unsafe context.
Examples included attribute applications, compiler-generated calls for constructs such as `params`, the `new()`
generic constraint, and `base` or `this` constructor initializers.

We separated declarative constructs from executable calls. Applying an attribute is not itself an invocation of the
attribute constructor in user code; it is an encoding of metadata, and any later construction through reflection is part
of the broader reflection story. However, we did not decide to allow attribute applications to use caller-unsafe
attribute constructors. Since there is no place to put an unsafe context on the application itself, selecting a
caller-unsafe attribute constructor will be an error. We do not expect this to be a real scenario in practice, and we
would rather wait for actual feedback before designing a way to support it.

For cases where the compiler does synthesize or select a real call, we do not want to introduce holes. If the generated
or implicit call targets a caller-unsafe member, then either the surrounding construct must occur in an unsafe context
or the language must reject the program. In several edge cases, we are choosing the simpler rejection for now rather
than inventing new syntax.

The parameterless constructor question was primarily about the `new()` constraint. We considered whether to ban
caller-unsafe parameterless constructors outright, which would avoid the generic constraint problem, but decided not to
add that restriction. Caller-unsafe parameterless constructors remain valid. However, we will not introduce an
`unsafe new()` constraint at this time. If a type with a caller-unsafe parameterless constructor would be used to
satisfy `new()`, we will block that scenario rather than silently allowing a caller-unsafe construction.

Finally, we looked at `base` and `this` constructor initializers. The called `base` or `this` constructor can be
caller-unsafe, and there is no ordinary expression or block around the initializer. Rather than add new syntax for that
location, we will allow a constructor initializer to call a caller-unsafe constructor only when the declared constructor
is itself caller-unsafe. That propagates the responsibility outward without introducing a new spelling for this narrow
case.

#### Conclusion

Attribute applications cannot use caller-unsafe attribute constructors. Selecting one will be an error. We do not
expect this to be a real scenario, and will wait for user feedback before deciding whether it needs support.

When compiler-generated or implicit execution would call a caller-unsafe member, we will either require an unsafe
context or produce an error; we will not silently allow the call. For now, we will prefer errors over new syntax for
edge cases such as `new()` constraints.

Caller-unsafe parameterless constructors are allowed, but types with such constructors do not satisfy the `new()`
constraint.

A constructor initializer may call a caller-unsafe `base` or `this` constructor only if the declaring constructor is
also caller-unsafe.
