Skip to content

Latest commit

 

History

History
76 lines (55 loc) · 5.43 KB

LDM-2024-12-04.md

File metadata and controls

76 lines (55 loc) · 5.43 KB

C# Language Design Meeting for December 4th, 2024

Agenda

Quote of the Day

  • "Anything else for you?"

Discussion

First-class span open questions

Champion issue: #8714

Preferring ReadOnlySpan<T> over Span<T> conversions

Spec: https://github.com/dotnet/csharplang/blob/1754aaafffe270c19d8578af30a2eb768de3ee49/proposals/first-class-span-types.md#prefer-converting-from-t-to-readonlyspant-over-spant-in-overload-resolution

The first issue we looked at today was around how array covariance can impact conversions to Span<T>. Because .NET has array covariance, the conversion from an array to a Span<T> has to check to make sure that the array is a T[], not some subtype of T[]; if this is the case, then the conversion will throw. This is a footgun for usages of this implicit conversion, and one that was hit immediately on upgrade to C# 14 preview by Roslyn because of the triplet of IEnumerable<T>, Span<T>, and ReadOnlySpan<T> in test assert APIs. It is fairly common for some expected data to be an array of some specific subtype, while the actual data being converted is a supertype. Previously this would have gone through IEnumerable<T>, which was perfectly fine with covariant arrays. Span<T> is now the preferred overload, and that isn't fine with covariant arrays.

This issue, where an implicit conversion can throw, isn't entirely new to the language; after all, dynamic can be implicitly converted to any type, and that can throw. But it is likely more prominent than dynamic, as with that feature, the user usually intentionally started at dynamic and went to a specific type, rather than starting at array and then invisibly going to a Span<T> where IEnumerable<T> used to be fine. While we don't think that we have reason to entirely remove the array->Span conversion from the feature, we do think that this merits some work to make sure that when there is a safer conversion available that likely has the same intended meaning, we prefer that conversion instead.

The proposal here is to make ReadOnlySpan<T> preferred over Span<T> for first-class span. From an immediate type theory perspective, this is an exception how most-specific type normally works. Since Span<T> can be converted to ReadOnlySpan<T>, but not the other way around, our standard most-specific type logic would indicate that Span<T> should be preferrable. However, for these types of APIs that overloaded across Span<T> and ReadOnlySpan<T>, we think that the only reason they're overloaded is because we currently don't have first-class spans as a language feature. Both overloads were added to make the API usable, but the only thing the API needs is read access, and the Span<T> API usually just forwards to the ReadOnlySpan<T> version. The only time this reasoning doesn't hold up is when we consider extension methods across multiple different types; such overloads could indeed have different behavior, where one overload really does need mutable access, while the other only reads from the array. Despite this, we think the overall feature is better served by preferring ReadOnlySpan<T> over Span<T>.

We also considered the question of whether we should take a bigger step. Our proposed rules only impact when arrays are converted to span types. This is yet another element to a decoder ring, another special case that users need to know to understand how overload resolution will be applied to their APIs. Could we instead make a broader, simpler rule, where we just always prefer ReadOnlySpan<T> over Span<T>? We don't think we have enough data to make such a decision today. We don't know of any APIs that this would negatively affect, but more investigation needs to be done before making a call.

Finally on this topic, we considered how restrictive to make the preference on type parameters. It seems a little suspicious to us to prefer ReadOnlySpan<object> over Span<string>; can we really make the assertions we said earlier about the expected behavior of the API for this type of shape? We'll investigate this as well, and see whether there are examples of this pattern in the wild.

Conclusion

We adopt the rule preferring ReadOnlySpan<T> over Span<T> for array conversions. We will investigate broader rules as well, and their potential impact on the BCL.

Conversions in expression trees

Spec: https://github.com/dotnet/csharplang/blob/1754aaafffe270c19d8578af30a2eb768de3ee49/proposals/first-class-span-types.md#ignore-span-conversions-in-expression-trees

Our last topic of the day was considering whether we should try and handle the conversion specially in expression trees, as this can be a breaking change. We had a similar discussion with params ReadOnlySpan, and we continue to find our reasoning from that issue compelling. Therefore, we will not make any changes to how binding occurs in expression trees; they will potentially pick up new span-based overloads and users may have to insert explicit casts to get old behavior back.

Conclusion

No special cases for expression trees.