Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify Span<T> rules #1593

Merged
merged 2 commits into from
Jun 4, 2018
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions proposals/csharp-7.2/span-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,20 @@ A `new` expression that invokes a constructor obeys the same rules as a method i

In addition *safe-to-escape* is no wider than the smallest of the *safe-to-escape* of all arguments/operands of the object initializer expressions, recursively, if initializer is present.

## Span constructor
The language relies on `Span<T>` not having a constructor of the following form:

``` csharp
void Example(ref int x)
{
// Create a span of length one
var span = new Span<int>(ref x);
}
```

Such a constructor makes `Span<T>` which are used as fields indistinguishable from a `ref` field. The safety rules described in this document
depend on `ref` fields not being a valid construct in C#, or .NET.

## `default` expressions

A `default` expression is *safe-to-escape* from the entire enclosing method.
Expand Down Expand Up @@ -280,3 +294,37 @@ We wish to ensure that no `ref` local variable, and no variable of `ref struct`
> ``` c#
> Foo(new Span<int>(...), await e2);
> ```

# Future Considerations

## Length one Span<T> over ref values
Though legal today there are cases where creating a length one `Span<T>` instance over a value would be beneficial:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo? not legal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops :(


``` csharp
void RefExample()
{
int x = ...;

// Today creating a length one Span<int> requires a stackalloc and a new
// local
Span<int> span1 = stackalloc [] { x };
Use(span1);
x = span1[0];

// Simpler to just allow length one span
var span2 = new Span<int>(ref x);
Use(span2);
}
```

This feature gets more compelling if we lift the restrictions on (fixed sized buffers)[https://github.com/dotnet/csharplang/blob/master/proposals/fixed-sized-buffers.md] as it would
allow for `Span<T>` instances of even greater length.

If there is ever a need to go down this path then the language could accommodate this by ensuring such `Span<T>` instances
Copy link
Member

@gafter gafter Jun 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this would work. By the time we would want to make that change, there might already be a user-declared type

internal ref struct MySpan<T>
{
    public MySpan(ref T data) ...;
}

This API is perfectly safe today because it doesn't (has no safe way to) capture the data.

MySpan<int> M(int x)
{
    return new MySpan<int>(ref x);
}

Although this is safe today, but would become illegal under the rule changes we would need to permit a Span that can capture a reference. And so would be a breaking change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That constructor though couldn't be implemented in a way that captured ref T data. If it attempted to do so via Span<T> then it would be an error as the value wouldn't be escapable beyond the constructor body and hence couldn't be assigned to a field.

Copy link
Member

@VSadov VSadov Jun 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would work. Because:

  • we do not have such constructors on Span/ReadOnlySpan right now.
  • with suggested restrictions the resulting instances will never escape the scope that contains the constructor invocation, so they will not introduce any new escaping scenarios for the outer scopes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also add that in a case when the ref argument is "definitely referring to a heap object" - like a ref to an array element, for example, the resulting instance could be allowed to escape. It feels like it might be another common case.

were downward facing only. That is they were only ever *safe-to-escape* to the scope in which they were created. This ensure
the language never had to consider a `ref` value escaping a method via a `ref struct` return or field of `ref struct`. This
would likely also require further changes to recognize such constructors as capturing a `ref` parameter in this way though.