## Background

Giving users an understanding of memory unsafe code is an important part of building reliable software. Correspondingly while C# has a concept of unsafe code, and many latter languages adopted and evolved its `unsafe` keyword, the language support provided is not as robust as needed to surface all types of memory unsafety. We have several extensive design documents that go into detail about what exactly is "memory safety" here: https://github.com/dotnet/designs/tree/main/accepted/2025/memory-safety. These documents should be referenced to fully understand what memory safety entails and why certain constructs are considered "safe" while others are "unsafe".

As a high level overview, C# has the `unsafe` keyword and this can be placed at the type (such as `class` or `struct`) or member (such as `field`, `method`, `property`) level. This keyword simply allows usage of unsafe constructs within that scope and does not itself indicate the safety or unsafety of the type or member in question. Unsafe constructs are then primarily centered around `pointers` with a few other special cases such as `SkipLocalsInit` and `sizeof(T)` thrown into the bunch. Then while C# does not have a way to directly propagate unsafety up to the caller today, you can view it as indirectly having such support by virtue of a signature containing pointers requiring the caller to have such an `unsafe` context to access it.

### RequiresUnsafeAttribute

In order to better handle these concerns around memory safety, C# needs to gain support for a new feature that allows explicit annotation of methods that are "dangerous" to invoke and which place the onus of memory safety onto the caller rather than abstracting the unsafety itself. There are multiple ways that this can be achieved, but they all fundamentally boil down to having some attribute at the metadata level that specifies `a caller requires an unsafe context to access this member`: `[System.Diagnostics.CompilerServices.RequiresUnsafe]`

The first part of this document is going to talk about the attribute directly in order to alleviate confusion between the different ways that the language could support annotating members, such as using the attribute directly or using a keyword. The proposal section further on gets into specifics of language direction.

### Handling of signatures containing pointers

The memory safety document details that a pointer simply existing is `safe` and it is only indirecting the pointer that is "dangerous"; however, we done various analysis on the dotnet/runtime repo and found that approximately 97% of signatures containing pointers would end up annotated as `[RequiresUnsafe]`. Because of this, there is inherent risk in changing what it means for a pointer to be in a method signature as it would mean enabling the `Unsafe Evolution` feature could now have those methods stating they are safe when they are actually unsafe. Additionally, it would mean that annotating those methods explicitly could add significant metadata bloat as we are making the majority case be explicit and the minority case implicit in metadata.

As such, while it should be fine for the language to not require `unsafe { }` contexts for pointer fields, locals, or any other usages of pointers that don't involve an indirection; it is proposed that the language continue to treat signatures containing pointers as being implicitly `[RequiresUnsafe]`. This has the added benefit of not requiring a change to the language for such signatures and that there will be no difference in thought for them between the current world and the future world where `Unsafe Evolution` is enabled for a codebase. The remaining minority of APIs that are actually safe can then be annotated in metadata as `[RequiresUnsafe(false)]` allowing them to correctly relay their safety and deviation from the majority case.

### Handling of `extern` methods

While .NET uses an intermediate language (CIL) that any higher level language (C#, VB, F#, C++/CLI, etc) can target, the general presumption is first that the vast majority of methods encountered will have been originally authored in C# as it is the largest .NET language by far and the primarily language that the core framework is authored in. Additionally, many of these other languages often have similar safety rules to C# and so it is a reasonable expectation that most methods will themselves be "safe". Finally, it would be prohibitive to the adoption of the feature to make any other kind of presumption as it would effectively make all existing .NET code "unsafe", which is a non-starter.

On the other hand, `extern` methods are explicitly known to not be implemented in C# and go through a P/Invoke layer that transfers control out of "managed" and to "native" code (even if native ultimately returns control back to managed, such as via a reverse P/Invoke or specialized runtime handling). This transition fundamentally suspends (even if temporarily) core C# safety features such as bounds checking, GC tracking, lifetime management, type safety, and other support. As such, it is desirable that `extern` methods should be treated as implicitly `[RequiresUnsafe]`. This is a notable break from how such signatures are interpreted today where they are only unsafe if the signature contains a pointer and for any `extern` methods that didn't have pointers in their signature it will be a source of upgrade pain for libraries that heavily rely on such P/Invokes.

For the purposes of this document, `extern` also includes methods annotated as `[LibraryImport]`, which is the modern source-generator-based alternative to `[DllImport]` when a user needs to support marshalling of managed concepts (such as arrays, strings, spans, ref/out/in parameters, classes, etc) to unmanaged concepts.

### Handling of non-upgraded dependencies

There will exist, likely for many years, a number of projects that have yet to adopt the `Unsafe Evolution` feature. This migration period is expected due to differing ship schedules, requiring all dependencies to make the migration, the need to continue supporting downlevel frameworks or multitarget, and more. We have seen this repeatedly in other features, including nullable reference types, and there is no reason to expect that this feature will see more or less rapid adoption. As such, in order for a project that does enable the feature to not have "reduced safety" as compared to today's world, it is desirable that we define how it handles such non-enabled projects. The simplest stance to take is that at minimum we must maintain the "status quo" which is that all signatures containing pointers are implicitly `requires unsafe`. This matches the above handling of signatures containing pointers in the feature-enabled world.

There is then an open question on how `extern` methods which do not contain pointers in their signatures are handled. We could choose to maintain the status quo and keep these "safe" or match the presumptive default where they are unsafe. It is the recommendation of this proposal that we go with the latter as it matches the expected majority case and is more likely to lead to a path of success. It may even serve as a forcing function to encourage such libraries to enable the feature and annotate themselves.

## Proposal

Now that the core concepts have been enumerated and a case has been made for how that metadata should be represented and interpreted by default, it is important to discuss how the language should support users annotating methods that deviate from these defaults. This would include annotating methods that are `extern` or contain pointers in their signature as being `[RequiresUnsafe(false)]` in metadata. It also would include annotating regular methods as being `[RequiresUnsafe]` in metadata, despite not containing any of the default markers that indicate likely memory unsafety.

The two main proposals are:
1. To change the meaning of the `unsafe` keyword in the language, better aligning with how it is used in languages such as Swift or Rust, where having it on a signature indicates "[RequiresUnsafe]"
2. To not change the existing semantics of `unsafe` and instead have users directly use the attributes

There are pros and cons to each approach. The first proposal is already covered by a different design document, so is largely out of scope of this document. This document will focus on the pros and cons of the second proposal.

### Issues with the `unsafe` keyword today

Language and API design is full of small regrets that are found days, months, or sometimes even years after a feature ships. However, they are also a core and fundamental part of having a healthy, thriving, and successful language. Hindsight is 20/20 after all and no one can reliably predict the future. 

The .NET team has decades of experience in this space, with many of the core API review and C# language design members having worked on multiple languages and frameworks across the industry. We know from this experience that taking major breaks, even if opt-in, comes with huge risks and for large codebases can make adoption of a feature fully prohibitive. While we have made significant advancements in toolings support, particularly due to modern AI models, there is still pain and divergence that will always exist for such breaks and so keeping these to calculated minimums is often a core goal whenever such breaks are considered. It is believed this careful consideration is a large part of what helps C# be considered successful, why C# continues seeing growth and being in the list of most beloved languages/ecosystems year over year, and what has with the success of modern .NET in spite of the complexities around multi-targeting and supporting downlevel frameworks/runtimes.

The main issue/regret with the `unsafe` keyword today is that many users intuitively think it means "caller must be unsafe" and while this is somewhat true, as mentioned above, for signatures containing pointers; it isn't actually how it works at the language level. Newer languages such as Rust which adopted a similar keyword applied the relevant hindsight and made that the meaning in their language. It is also common practice among unsafe authors to place the `unsafe` keyword at the coarsest scoping possible, often at the type level, to avoid needing to annotate each individual member in a type as allowing the use of `unsafe` concepts and to avoid needing to increase indentation. However, this can make it difficult to audit what code is actually dangerous vs what is safe. Such coarse annotations can also be impactful if a user were to see a signature such as `[RequiresUnsafe(false)] unsafe void M() { }` in source or metadata (such as via `IntelliSense`), as it looks to be simultaneously saying it is safe and unsafe.

It is the belief of this proposal that we can push users towards a desired path of success and clarity without forcing such a foundational break in the meaning of the `unsafe` keyword. This is achieved by a clear incremental adoption process that is backed by both static and AI tooling to get code following our recommended design and formatting guidance.

### Enablement Experience - Part 1

Developers opt-into the feature by setting the relevant project setting, we'll call this `<UnsafeEvolution>enable</UnsafeEvolution>` for simplicity (although a better name will likely be chosen).

This will cause new errors to be surfaced in locations that are invoking `extern` methods without an `unsafe { }` context and for any APIs from dependencies that have already enabled the feature and been annotated as `[RequiresUnsafe]`. Such errors can be trivially handled by a fixer which wraps the whole method body with `unsafe { }`. In other words, `void M() { ...; SomeExternCall(); ...; }` becomes `void M() { unsafe { ...; SomeExternCall(); ...; } }` and not `void M() { ...; unsafe { SomeExternCall(); } ...; }`, although the granularity could perhaps be handled by an `.editorconfig` option or similar. The reason for this is that new `unsafe { }` contexts introduced inline with existing code produce new scopes and that can introduce enablement risk. Surrounding the entire method body however is mechanical in nature and trivial to review without concerns over potential changes in behavior or semantics.

This allows trivial and largely automated enablement of the feature without complex code changes or an in depth review process. It maintains the historical status quo while also surfacing new locations of unsafe code usage, thus helping achieve the goal of raising awareness of what code is potentially dangerous (purely a positive). It does not, however, finish the job and there is still more work to be done. It is simply the first incremental step towards success and allowing users to start their migration process, particularly for large code bases.

### Enablement Experience - Part 2

Developers can then begin reviewing their own APIs and annotating the outliers that deviate from the norm. This includes annotating APIs that appear safe but are actually dangerous, such as `short System.Runtime.InteropServices.Marshal.ReadInt16(nint ptr)` which takes a native integer and internally reinterprets it as a pointer and dereferences it. This also includes the inverse case of annotating APIs that appear unsafe but are actually safe, such as `void* System.Runtime.InteropServices.NativeMemory.Alloc(nuint byteCount)` which is guaranteed to map to the C runtime `malloc` function and will only ever return `null` or a valid pointer that has at least `byteCount` of bytes accessible.

This is an important part of the process and is part of ensuring feature success and participation in the post-enablement world. These APIs are expected to be a minority case and will require careful auditing in either proposal as there will always be some that fall outside the presumptive defaults. Some of this work can be assisted by AI or static analysis as well; for example, any method which is indirecting a pointer produced from a parameter is fundamentally pushing a safety responsibility up to the caller as there is no way for it to prove the memory safety of the pointer itself. Likewise, a method which takes a pointer but does not otherwise indirect that pointer or pass it onto something which may indirect that pointer is potentially safe. There will be some false positives no matter what and so users will need to be mindful of this in code review.

### Code Fixer - Tightening of unsafe scopes

While not strictly part of the language, a core consideration of enabling the `Unsafe Evolution` feature is encouraging users to follow our recommended guidance around the use of `unsafe` to help break the points of confusion listed above. This is relevant to both proposals, but notably differs a bit in where you might apply it depending on which we're talking about. In the case of this proposal, the fixer can be applied at any time, either before or after enabling the feature; with a small nuance that you can potentially move the unsafe scope down slightly further after enabling the feature (not having it on method bodies at all).

At a basic level, this fixer would be responsible for identifying all the places that use `unsafe` at the type level and moving it down to the member level instead. This tightens the scope and effectively soft obsoletes the usage of `unsafe` at the type level. This allows code to work in both the pre and post `Unsafe Evolution` worlds and helps avoid some of the ambiguity of what the `unsafe` keyword means.

There are then a few additional scenarios that such a fixer could also handle, such as:
* suggest removing the `unsafe` keyword where it appears to be unnecessary
  * no use of pointers in the pre Unsafe Evolution world
  * no use of [RequiresUnsafe] members in the post Unsafe Evolution world
* suggest removing the `unsafe` keyword at the method level if it is annotated as `[RequiresUnsafe(false)]`
  * in the post-world, it would even be possible to recommend never using `unsafe` at the member level and only use it within a method body, but this could be partially conflicting with multi-targeting scenarios

### Additional Notes

One of the added benefits of this proposal is that most of this feature does not require language support and could almost entirely be handled by an analyzer plus set of code fixers. This means that it could also be enabled for downlevel projects fairly easily, which can help increase adoption and eventual robustness of the ecosystem. The notable exception to this is in the ability for methods with pointers in their signature to no longer require being `unsafe` and to have an opt-out (via `[RequiresUnsafe(false)]`), as this does require language changes; however this is likely not a hugely impacting factor given the presumptive majority case. This may also make it easier for other languages to adopt and follow suit, as they can provide a nearly identical experience as C# if desired.

There are several potential future expansions that touch on concepts enumerated by the working group or in alternative proposals. This includes things like support for `unsafe(expr)` to reduce the impact of nesting increase from `unsafe { }` contexts within method bodies, providing eventual enforcement of not using `unsafe` at the type level after we believe enough of the ecosystem has migrated that the break is no longer harmful for a majority of existing code, easier iteration and enablement of functionality and fixes for what support ends up analyzer based, etc.

In the post-enablement world where users are also using the `tightening of unsafe scopes` fixer (i.e. following the official recommendations), users can trivially identify the difference between something that is simply using unsafe constructs vs something that is itself unsafe. The former (use of unsafe constructs) is done by looking for the `unsafe` keyword, matching how users have historically identified such code. While users trying to identify the latter can do so by searching for the `[RequiresUnsafe]` annotation, `extern` keyword, and signatures containing pointers. This avoids some of the the problem where `unsafe` would otherwise serve a dual purpose. You still don't have a single search mechanism to identify all APIs that are themselves unsafe, due to the implicitness of extern functions and APIs with pointers in their signature; but this is likely impossible to rectify due to potential for pre-enablement dependencies to exist. 

IDE tooling could be introduced to help with searching, filtering, and even showing potential unsafety graphs to help with things such as threat modeling and identification of potential "danger zones" of code.

Nothing about this proposal prevents users from being more explicit with their annotations if they desire. A codebase could require that all `extern` methods in their own project be explicitly annotated as `[RequiresUnsafe]` or `[RequiresUnsafe(false)]` and this could even be a built-in feature if desired.

