Skip to content

Allow ref struct to implement interfaces#7625

Merged
jaredpar merged 10 commits intodotnet:mainfrom
jaredpar:refint
Oct 30, 2023
Merged

Allow ref struct to implement interfaces#7625
jaredpar merged 10 commits intodotnet:mainfrom
jaredpar:refint

Conversation

@jaredpar
Copy link
Member

This proposal sketches out what it would look like if ref struct were allowed to implement interfaces and participate as generic parameters.  

Related #7608

@jaredpar jaredpar requested a review from a team as a code owner October 24, 2023 15:31
@jaredpar
Copy link
Member Author

Co-authored-by: Charles Stoner <10732005+cston@users.noreply.github.com>

1. Instances of it cannot be boxed
2. Instances participate in lifetime rules like a normal `ref struct`
3. The type parameter cannot be used in `static` fields, elements of an array, etc ...
Copy link
Member

Choose a reason for hiding this comment

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

In short, ref struct cannot appear on the heap, and the rules are meant to prevent that.

A type parameter bound by `allow T: ref struct` has all of the behaviors of a `ref struct` type:

1. Instances of it cannot be boxed
2. Instances participate in lifetime rules like a normal `ref struct`
Copy link
Member

Choose a reason for hiding this comment

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

This is perfectly fine, but I also think we could slip some of this support to a second release if necessary. If we don't allow scoped/unscoped/etc then I think the default ref-struct rules are very useful and the cases where you need different lifetimes are pretty narrow.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure what you want to slip here. Instances of the type parameter need to behave like ref struct from a lifetime perspective else it's easy to create memory safety issues. That support is fairly easy to add. Conceptually it's just applying lifetime rules to any type which could be a ref struct but really nothing else changes.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure what you want to slip here

Just the lifetime modifier syntax like scoped. Agreed it may not be worth it to skip.


### Runtime support
This feature requires several pieces of support from the runtime / libraries team:
- Preventing default interface methods from applying to `ref struct`
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this is an important one that will likely need runtime work.

void M()
{
// Error: both of these box if I1 is implemented by a ref struct
I1 local1 = this;
Copy link
Member

Choose a reason for hiding this comment

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

The runtime will demand a this pointer as a boxed interface even without any of these explicit usages. Simply calling this method will break the runtime. So you're right that this needs a runtime exception.


```csharp
T Identity<T>(T p)
allow T : ref struct
Copy link
Member

Choose a reason for hiding this comment

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

*standard handwaves around syntax*

@stephentoub
Copy link
Member

Thanks, @jaredpar. I read this with an eye towards dotnet/runtime#27229 (comment) and really wanting to get a solution there in for .NET 9. Nothing in this PR jumped out at me as being problematic for that, but wanted to double-check.

@jaredpar
Copy link
Member Author

@stephentoub Looked through the IMappingComparer<TMappedKey, TTarget> and that seems compatible with the design proposal here.

jaredpar and others added 2 commits October 24, 2023 14:31
Co-authored-by: Joe4evr <jii.geugten@gmail.com>
Co-authored-by: Charles Stoner <10732005+cston@users.noreply.github.com>
@RikkiGibson RikkiGibson self-assigned this Oct 24, 2023
Copy link
Member

@agocke agocke left a comment

Choose a reason for hiding this comment

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

LGTM, I think we can fill in details as needed

jaredpar and others added 2 commits October 30, 2023 14:53
Co-authored-by: Charles Stoner <10732005+cston@users.noreply.github.com>
@jaredpar jaredpar merged commit ce73feb into dotnet:main Oct 30, 2023
@jaredpar jaredpar deleted the refint branch October 30, 2023 21:55
}
}

ref struct S = I1 { }
Copy link
Member

Choose a reason for hiding this comment

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

Should be S : I1

```csharp
interface I1 { }
I1 M1<T>(T p)
allow T : ref struct, I1
Copy link
Member

Choose a reason for hiding this comment

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

It feels like the I1 constraint should only be permitted within a where clause not an allow clause

### Co and contra variance
To be maximally useful type parameters that are `allow T : ref struct` must be compatible with generic variance. Specifically it must be legal for a parameter to be both co/contravariant and also `allow T: ref struct`. Lacking that they would not be usable in many of the most popular `delegate` and `interface` types in .NET like `Func<T>`, `Action<T>`, `IEnumerable<T>`, etc ...

Given there is no actual variance when `struct` are involved these should be compatible. There is still some concern that I'm missing deeply generic variance cases. Need to sit down with @agocke to work out if this is truly safe or if there are deeply generic scenarios that need to be worked out.
Copy link
Member

Choose a reason for hiding this comment

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

I think it's good to double check. However, I doubt there are issues with nested generic conversion currently, because structs can only have invariant type parameters.

Given there is no actual variance when `struct` are involved these should be compatible. There is still some concern that I'm missing deeply generic variance cases. Need to sit down with @agocke to work out if this is truly safe or if there are deeply generic scenarios that need to be worked out.

### Auto-applying to delegate members
**Decision**: do not auto-apply
Copy link
Member

Choose a reason for hiding this comment

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

Agree that we should not auto-apply.

While that is true it can present a problem in multi-targeted scenarios. Code would compile in one target framework but fail in another. This could lead to confusion with customers and result in a desire for a more explicit opt-in.

### Binary breaking change
Adding `allow T: ref struct` to an existing API is not a source breaking change. It is purely expanding the set of allowed types for an API. Need to track down if this is a binary breaking change or not. Unclear if updating the attributes of a generic parameter constitute a binary breaking change.
Copy link
Member

Choose a reason for hiding this comment

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

A type parameter on an override inherits the base type parameter's constraints, so I would expect that adding allow T : ref struct would break something like the following: (SharpLab)

public interface I { }

public class C {
    public virtual void M<T>(T t) where T : I // allow T : ref struct added in vNext
    {
    }
}


public class D : C {
    public override void M<T>(T t)
    {
        I i = t; // used to be ok, but now is an error
    }
}

Depending on how the metadata for allow clause works, this case may or may not be a binary break, but it should probably be a source break as well as a runtime break.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants