Skip to content

Commit

Permalink
ref fields spec updates (#6539)
Browse files Browse the repository at this point in the history
* ref fields spec updates

Couple of items:

- Clarifying the rules around ref reassignment.
- Affirming the *safe-to-escape* of non `ref struct`. This needed
  clarifying after the introduction of *return only*
- Affirming the cut line for features in C# 11

* formatting

* feedback

* typo

* more

* more
  • Loading branch information
jaredpar committed Oct 12, 2022
1 parent 952ba44 commit b117e71
Showing 1 changed file with 42 additions and 4 deletions.
46 changes: 42 additions & 4 deletions proposals/csharp-11.0/low-level-struct-improvements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` constructor works without any extra annotation:

Expand Down Expand Up @@ -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*.

<a name="rules-method-invocation"></a>

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`:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -1307,6 +1309,42 @@ ref struct RS
}
```

#### Ref reassignment and unsafe escapes
<a name="examples-ref-reassignment-safety"></a>

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<int> p)
{
Span<int> local = stackalloc int[42];
ref Span<int> 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
<a name="examples-scoped-locals"></a>

Expand Down

0 comments on commit b117e71

Please sign in to comment.