# C# Language Design Meeting for January 22nd, 2024

## Agenda

- [Ref struct interfaces](#ref-struct-interfaces)
- [Interceptors](#interceptors)

## Discussion

### Ref struct interfaces

https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md

Ref structs are not currently allowed to implement interfaces. The reason is that we cannot allow them to be boxed, because they are not permitted in heap memory. 

However, this prevents ref structs from participating in the core abstraction mechanism we use to describe patterns and shapes of types in C#. For example, `Span<T>` cannot implement `IEnumerable<T>`, and methods that rely on that need to have dedicated span overloads with virtually the same implementation.

The proposal allows ref structs to implement interfaces, but does not allow them to be boxed to those interfaces. Default implementations cannot be called, because that involves boxing the receiver. 

The way you can take advantage of the interface is by using it to satisfy a generic constraint. Generic code today does allow type parameters to be converted to their constraints, which would again lead to boxing of ref structs used as type arguments. So the proposal introduces the idea of an "anti-constraint", whereby a type parameter explicitly permits ref structs at the cost of not being able to box it inside the generic code. Such a type parameter would also participate in the lifetime tracking that we apply to refs and ref structs.

The proposal anticipates other possible "anti-constraints" in the future - extra permissions on a type parameter allowing type arguments that wouldn't be admissible by default - and suggests a syntax for them that can grow to encompass these over time. Here's an example from the proposal:

``` c#
T Identity<T>(T p)
    allow T : ref struct
    => p;

// Okay
Span<int> local = Identity(new Span<int>(new int[10]));
```

We discussed where we would add the `allow T : ref struct` anti-constraint in our existing libraries. The answer is, probably nearly all our core abstractions. There might be breaking changes here, but we haven't thought of any serious ones, beyond the overload-resolution-with-lambdas ones that we usually accept. If we were to add anti-constraints to existing library types we would need to check for problems in VB and F# too.

The feature requires runtime support, and the runtime team has already done some work on this, but we need to make sure things line up.

Could we use this to make LINQ work with span types as source collections? That's complicated because many query methods store the source for later - deferred - query evaluation, and we can't store ref structs in objects! It may be disappointing to users that this feature doesn't enable LINQ scenarios.

On the syntax front, we have some skepticism: One problem is that "allow" and "where" aren't the same kind of word, grammatically, and so reading the proposed syntax may get confusing and inelegant. Another is that you now sometimes need two separate clauses that talk about the same type parameter; a `where T : ...` and an `allow T : ...` clause. Perhaps we could address both of those problems by folding the anti-constraint syntax into the constraint syntax as an optional suffix:

``` c#
where T : IEnumerable<T>, allows ref struct
```

Finally, a more nebulous concern is how this could interact with other things we might do with generics in the future - the "unknown unknowns".

#### Conclusion

There are several open questions around the specifics of the design, but we think the feature is valuable and want to keep working through it.

### Interceptors

https://github.com/dotnet/csharplang/issues/7009

We've had an early prototype of interceptors out with C# 12/.NET 8, and are now looking at making the feature stable.

The prototype relies on the use of file paths in a way that has turned out to be problematic, since it makes the source code not portable. To that end we want to allow relative paths - in practice you'd rarely have a situation where that won't suffice. 

Another current problem with using file paths is that files sometimes aren't emitted to disk (yet). We could address this by making such files identifiable using the path that they *would* be emitted to. 

An alternative to file paths would be to have a more opaque location token that doesn't have source-level "meaning". If we recognize that interceptors will always be generated, why does this need to be something that can be written and understood by a human? However, such an approach comes with its own challenges when you try to implement it in practice. We'll keep investigating it as a possible future addition, but for now the file-path-based approach is what we'll aim at shipping.

It's important that tools can identify that a given call is intercepted, so there needs to be good APIs for that which don't incur the cost of binding everything.

This feature relies on source generation, but where many other source generators are about enhancing the surface area for the developer, it's desirable that interceptors are *hidden* from being manually called, and only affect the call site that they are intercepting. This can also lower the cost of the source generation, and the frequency at which it runs.

#### Conclusion

This is not a feature area that is primarily driven from the language. There is no new proposed syntax, but there is some language-level behavior. We should keep the LDM in the loop as this feature area evolves, so that language-level concerns continue to be factored in.