csharplang/proposals/csharp-7.3/blittable.md

136 lines
5.8 KiB
Markdown
Raw Normal View History

# Unmanaged type constraint
2017-02-28 05:19:55 +01:00
## Summary
[summary]: #summary
The unmanaged constraint feature will give language enforcement to the class of types known as "unmanaged types" in the C# language spec. This is defined in section 18.2 as a type which is not a reference type and doesn't contain reference type fields at any level of nesting.
2017-02-28 05:19:55 +01:00
## Motivation
[motivation]: #motivation
The primary motivation is to make it easier to author low level interop code in C#. Unmanaged types are one of the core building blocks for interop code, yet the lack of support in generics makes it impossible to create re-usable routines across all unmanaged types. Instead developers are forced to author the same boiler plate code for every unmanaged type in their library:
2017-02-28 05:19:55 +01:00
```csharp
int Hash(Point point) { ... }
int Hash(TimeSpan timeSpan) { ... }
2017-02-28 05:19:55 +01:00
```
To enable this type of scenario the language will be introducing a new constraint: unmanaged:
2017-02-28 05:19:55 +01:00
```csharp
void Hash<T>(T value) where T : unmanaged
{
2017-02-28 05:19:55 +01:00
...
}
```
2021-02-13 00:59:31 +01:00
This constraint can only be met by types which fit into the unmanaged type definition in the C# language spec. Another way of looking at it is that a type satisfies the unmanaged constraint if it can also be used as a pointer.
2017-02-28 05:19:55 +01:00
```csharp
Hash(new Point()); // Okay
Hash(42); // Okay
Hash("hello") // Error: Type string does not satisfy the unmanaged constraint
2017-02-28 05:19:55 +01:00
```
Type parameters with the unmanaged constraint can use all the features available to unmanaged types: pointers, fixed, etc ...
2017-02-28 05:19:55 +01:00
```csharp
void Hash<T>(T value) where T : unmanaged
2017-02-28 05:19:55 +01:00
{
// Okay
fixed (T* p = &value)
{
...
}
2017-02-28 05:19:55 +01:00
}
```
This constraint will also make it possible to have efficient conversions between structured data and streams of bytes. This is an operation that is common in networking stacks and serialization layers:
2017-02-28 05:19:55 +01:00
```csharp
Span<byte> Convert<T>(ref T value) where T : unmanaged
{
...
2017-02-28 05:19:55 +01:00
}
```
Such routines are advantageous because they are provably safe at compile time and allocation free. Interop authors today can not do this (even though it's at a layer where perf is critical). Instead they need to rely on allocating routines that have expensive runtime checks to verify values are correctly unmanaged.
2017-02-28 05:19:55 +01:00
## Detailed design
[design]: #detailed-design
2017-02-28 05:19:55 +01:00
The language will introduce a new constraint named `unmanaged`. In order to satisfy this constraint a type must be a struct and all the fields of the type must fall into one of the following categories:
2017-02-28 05:19:55 +01:00
2018-01-25 03:20:46 +01:00
- Have the type `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool`, `IntPtr` or `UIntPtr`.
- Be any `enum` type.
- Be a pointer type.
2020-08-12 17:03:52 +02:00
- Be a user defined struct that satisfies the `unmanaged` constraint.
2017-02-28 05:19:55 +01:00
Compiler generated instance fields, such as those backing auto-implemented properties, must also meet these constraints.
For example:
```csharp
// Unmanaged type
struct Point
{
int X;
int Y {get; set;}
2017-02-28 05:19:55 +01:00
}
// Not an unmanaged type
struct Student
{
string FirstName;
string LastName;
}
```
2017-02-28 05:19:55 +01:00
2018-01-25 17:20:38 +01:00
The `unmanaged` constraint cannot be combined with `struct`, `class` or `new()`. This restriction derives from the fact that `unmanaged` implies `struct` hence the other constraints do not make sense.
The `unmanaged` constraint is not enforced by CLR, only by the language. To prevent mis-use by other languages, methods which have this constraint will be protected by a mod-req. This will
prevent other languages from using type arguments which are not unmanaged types.
The token `unmanaged` in the constraint is not a keyword, nor a contextual keyword. Instead it is like `var` in that it is evaluated at that location and will either:
2017-02-28 05:19:55 +01:00
- Bind to user defined or referenced type named `unmanaged`: This will be treated just as any other named type constraint is treated.
- Bind to no type: This will be interpreted as the `unmanaged` constraint.
2017-02-28 05:19:55 +01:00
In the case there is a type named `unmanaged` and it is available without qualification in the current context, then there will be no way to use the `unmanaged` constraint. This parallels the rules surrounding the feature `var` and user defined types of the same name.
2017-02-28 05:19:55 +01:00
## Drawbacks
[drawbacks]: #drawbacks
The primary drawback of this feature is that it serves a small number of developers: typically low level library authors or frameworks. Hence it's spending precious language time for a small number of developers.
2018-01-25 22:32:23 +01:00
Yet these frameworks are often the basis for the majority of .NET applications out there. Hence performance / correctness wins at this level can have a ripple effect on the .NET ecosystem. This makes the feature worth considering even with the limited audience.
2017-02-28 05:19:55 +01:00
## Alternatives
[alternatives]: #alternatives
There are a couple of alternatives to consider:
- The status quo: The feature is not justified on its own merits and developers continue to use the implicit opt in behavior.
2018-01-25 17:20:38 +01:00
## Questions
[quesions]: #questions
### Metadata Representation
The F# language encodes the constraint in the signature file which means C# cannot re-use their representation. A new attribute will need to be chosen for this constraint. Additionally a method which has this constraint must be protected by a mod-req.
### Blittable vs. Unmanaged
The F# language has a very [similar feature](https://docs.microsoft.com/dotnet/articles/fsharp/language-reference/generics/constraints) which uses the keyword unmanaged. The blittable name comes from the use in Midori. May want to look to precedence here and use unmanaged instead.
**Resolution** The language decide to use unmanaged
### Verifier
2017-02-28 05:19:55 +01:00
2018-01-25 22:32:23 +01:00
Does the verifier / runtime need to be updated to understand the use of pointers to generic type parameters? Or can it simply work as is without changes?
2017-02-28 05:19:55 +01:00
2018-01-25 03:20:46 +01:00
**Resolution** No changes needed. All pointer types are simply unverifiable.
2017-02-28 05:19:55 +01:00
## Design meetings
n/a