meetings/2026/LDM-2026-01-26.md
Champion issue: https://github.com/dotnet/csharplang/issues/9704
Spec: https://github.com/dotnet/csharplang/blob/61f06216967ed264a8f83c71bff482f3eb6ac113/proposals/unsafe-evolution.md
Working group notes: https://github.com/dotnet/csharplang/blob/61f06216967ed264a8f83c71bff482f3eb6ac113/meetings/working-groups/unsafe-evolution/unsafe-alternative-syntax.md
Continuing from last week's discussion, the LDM had a strong lean toward not repurposing the
existing unsafe keyword for caller-unsafe semantics. Today we explored the remaining design space: should we use an
attribute like [RequiresUnsafe], or a new keyword other than unsafe?
Several arguments were raised in favor of an attribute approach. An attribute is a safer incremental choice; we could ship an attribute, learn from user feedback during the .NET 11 preview cycle, and add a keyword later if needed. We cannot remove a keyword once introduced. Attributes also provide more flexibility for distinguishing between existing members that became caller-unsafe (where consumers may need an escape hatch during migration) versus new members that are caller-unsafe from the start (where there's no conceptual reason to allow opting out).
On the other hand, strong arguments were made for a dedicated keyword. A keyword conveys the seriousness with which C# is treating memory safety and makes it a first-class language concept. If caller-unsafe were just an attribute, it would be effectively the same as shipping an analyzer; it wouldn't feel like part of the language. The signal value matters: as the industry increasingly demands memory-safe languages, C# needs to demonstrate that safety is built into the language itself, not bolted on as an afterthought.
The discussion also touched on audit ergonomics. The guidance that the libraries team is working on is to keep unsafe
blocks as small as possible, drawing the eye to precisely where unsafety occurs. This has implications for the syntax
design: having both unsafe at the method level and [RequiresUnsafe] on the same member is effectively incorrect,
you probably never want both. When unsafe is at the method level, it suppresses all warnings internally, making it
impossible to audit which specific operations are unsafe. This means that when users enable the feature, they cannot
easily audit their existing code; they would need to go through every usage of unsafe line by line. Or, we have to
make the breaking change even larger, and change unsafe on a member to not create an unsafe block inside the member
at all. This is currently an active question in the proposal, but if we proceed with unsafe as the keyword, then we
would need to answer that question.
We then brainstormed extensively on possible syntaxes:
[RequiresUnsafe][CallerUnsafe][Requires("unsafe")], [Requires("experimental")]callerunsaferequiresunsafeunsafe(caller)requires(unsafe)requires unsafeunsafe required#require unsaferequires-unsafeunsafe-callermodifier(unsafe)unprotecteddangerousaccess unsafe[RequiresUnsafeContext]propagate unsafeunsafe(propagate)[UnsafeOnly]leakunsafeexportunsafeAfter an initial round of voting, the options were narrowed down to [RequiresUnsafe], [CallerUnsafe],
requiresunsafe, unsafe(caller), requires unsafe, and unsafe required. A second round of voting further narrowed
the field to [RequiresUnsafe], [CallerUnsafe], [UnsafeOnly], requires unsafe, unsafe(caller), and
requiresunsafe. None of the keyword options were liked by a majority of the LDM, with most preferring the attribute
approach, with [RequiresUnsafe] being the preferred option. We then compared this against the original proposal:
repurposing unsafe itself. [RequiresUnsafe] is preferred over repurposing the unsafe keyword, so we will proceed
with the alternative syntax proposal.
We will use an attribute, [RequiresUnsafe], for communicating when the caller of a method or user of a field/property
must be in an unsafe context.