diff --git a/proposals/csharp-11.0/low-level-struct-improvements.md b/proposals/csharp-11.0/low-level-struct-improvements.md index e9afae9149..f71bdc4e4a 100644 --- a/proposals/csharp-11.0/low-level-struct-improvements.md +++ b/proposals/csharp-11.0/low-level-struct-improvements.md @@ -156,9 +156,9 @@ Next the rules for ref reassignment need to be adjusted for the presence of `ref The left operand of the `= ref` operator must be an expression that binds to a ref local variable, a ref parameter (other than `this`), an out parameter, **or a ref field**. -> For a ref reassignment in the form ... -> 1. `x.e1 = ref e2`: where `x` is *safe-to-escape* at least *return only* then `e2` must have *ref-safe-to-escape* at least as large as `x` -> 2. `e1 = ref e2`: where `e1` is a `ref` local or `ref` parameter then `e2` must have a *safe-to-escape* equal to *safe-to-escape* for `e1` and `e2` must have *ref-safe-to-escape* at least as large as *ref-safe-to-escape* of the *ref-safe-to-escape* of `e1` +> For a ref reassignment in the form `e1 = ref e2` both of the following must be true: +> 1. `e2` must have *ref-safe-to-escape* at least as large as the *ref-safe-to-escape* of `e1` +> 2. `e1` must have the same *safe-to-escape* as `e2` [Note](#examples-ref-reassignment-safety) That means the desired `Span` constructor works without any extra annotation: @@ -322,6 +322,8 @@ Any expression or statement which explicitly returns a value from a method or la Likewise any assignment to an `out` must have a *safe-to-escape* of at least *return only*. This is not a special case though, this just follows from the existing assignment rules. +Note: An expression whose type is not a `ref struct` type always has a *safe-to-return* of *calling method*. + The span safety rules for method invocation will be updated in several ways. The first is by recognizing the impact that `scoped` has on arguments. For a given argument `a` that is passed to parameter `p`: @@ -1096,7 +1098,7 @@ The features outlined in this document don't need to be implemented in a single What gets implemented in which release is merely a scoping exercise. -**Decision** Only (1) and (2) will make C# 11.0. The expectation is (3) and (4) are enabled very early in C# 12.0 to enable dogfooding by runtime throughout the .NET 8 cycle +**Decision** Only (1) and (2) made C# 11.0. The rest will be considered in future versions of C#. ## Future Considerations @@ -1307,6 +1309,42 @@ ref struct RS } ``` +#### Ref reassignment and unsafe escapes + + +The reason for the following line in the [ref reassignment rules](#rules-ref-reassignment) may not be obvious at first glance: + +> `e1` must have the same *safe-to-escape* as `e2` + +This is because the lifetime of the values pointed to by `ref` locations are invariant. The indirection prevents us from allowing any kind of variance here, even to narrower lifetimes. If narrowing is allowed then it opens up the following unsafe code: + +```csharp +ref struct RS { } +void Example(ref Span p) +{ + Span local = stackalloc int[42]; + ref Span refLocal = ref local; + + // The safe-to-escape of refLocal is narrower than p. For a non-ref reassignment + // this would be allowed as its safe to assign wider lifetimes to narrower ones. + // In the case of ref reassignment though this rule prevents it as the + // safe-to-escape values are different. + refLocal = ref p; + + // If it were allowed this would be legal as the safe-to-escape of refLocal + // is *containing method* and that is satisfied by stackalloc. At the same time + // it would be assigning through p and escaping the stackalloc to the calling + // method + // + // This is equivalent of saying p = stackalloc int[13]!!! + refLocal = stackalloc int[13]; +} +``` + +For a `ref` to non `ref struct` this rule is trivially satisfied as the values all have the same *safe-to-escape* scope. This rule really only comes into play when the value is a `ref struct`. + +This behavior of `ref` will also be important in a future where we allow `ref` fields to `ref struct`. + #### scoped locals