-
Notifications
You must be signed in to change notification settings - Fork 1k
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
[Proposal]: First-Class Span Types #7905
Comments
I'm probably missing something obvious, but why doesn't just adding |
Implicit operators already exist. They do not suffice because they do not participate in the pieces that I outlined in the proposal. |
Oh I see what you mean, it's because of generics. |
These changes require a deep understanding of the relationship between the array type and a type parameter; I don't really see a generalizable mechanism that wouldn't be massively overcomplicated for the class of problem that we're trying to fix here. |
Would you allow an extension method invocation to combine an implicit span conversion with an identity conversion? using System;
class C {
void M() {
object[] objs = {};
S.X(objs); // OK
objs.X(); // ?
(int a, int b)[] tuples = {};
S.Y(tuples); // OK
tuples.Y(); // ?
}
}
static class S {
internal static void X(this Span<dynamic> span) {}
internal static void Y(this Span<(int c, int d)> span) {}
} Or alternatively, allow this identity conversion as part of the implicit span conversion:
|
The conversion to Span<T> throws in the covariant case using System;
class C {
static void Main() {
object[] objs = new string[1];
S.X(objs); // implicit conversion to Span<object> throws ArrayTypeMismatchException
objs.X(); // implicit span conversion presumably likewise
}
}
static class S {
internal static void X(this Span<object> span) {}
} The proposal defines the implicit span conversion to be a standard implicit conversion. It's then also a pre-defined conversion according to §10.4.1:
This paragraph in §10.2.1 then has to be changed:
|
No, that is not intended by this proposal. |
Does that mean: using System;
class C {
void M() {
(int a, int b)[]? array1 = null;
(int c, int d)[]? array2 = null;
array1.N(); // calls X.N(array1)
array2.N(); // calls Y.N(array2)
array1.AsSpan().N(); // error CS0121, ambiguous
}
}
static class X {
public static void N(this Span<(int a, int b)> span) {}
}
static class Y {
public static void N(this Span<(int c, int d)> span) {}
} I think this would be the first time that tuple element names affect which method is called. |
I'm firmly against the idea that the language would pick a span overload over an array or string overload and I think it would be really unfortunate. There are issues with that, as I mentioned here:
An array is a strictly derived type from a span. It has more featueres, it is more usable, and it can be put on the heap. If all you get is a span, you cannot put it on the heap or reuse it as a field, and you might have to allocate and copy the data to get something more usable. String, on the other hand, can be reused. If what you already have is an array or a string, you should call those corresponding overloads. Preferring span is only useful in cases like params span when you don't already have an array you could pass in, and it will implicitly allocate one. But that's an issue with the language implicitly allocating. I'd be fine with the language understanding span types, but only if it still considered them base types of arrays or strings - not the other way around. It would be nice to avoid the duplication of overloads on |
We've already decided, based on empirical need, that we will pick the span overload.
These issues do not apply in aggregate. And it would be bad for the ecosystem as a whole if we punished the majority of cases for a tiny subset of cases where there might be a problem. Those tiny subset should be designed to not have an issue here, versus requiring the rest of the ecosystem to try to get the perf that is sensible by default. |
That's a negative. it cannot be put on the stack. The span approach means you can operate on both uniformly. And, in the normal common case, you get the stack benefits across the ecosystem. It is strictly the type that should be preferred. And that is borne out by the teams looking at this and specifically wnating both the most flexibility and the best perf. |
But why would picking |
@Neme12 the method knows how it's going to use the parameter, so it (in the overwhelming majority of cases) knows if it needs to allocate from a span (thus negating the performance gains of spans in the first place or even making it worse in some cases). Why don't we use the overload resolution attribute to solve this? Basically, if the span overload knows it's better, it has an attribute with a higher value, and if not, the array/string one will have a higher value. |
So that the caller doesn't need to heap allocate. Which will often be the case the majority of the time. |
To clarify, I think she is asking why we would call the span overload if you already have an array. |
But as I said, it only allocates implicitly for |
The vast vast vast majority of cases do not do this. And they would not do this either if they got an array (as the data could change out from underneath them). So a copy needs to happen in the array case as well here. So spans are no worse. |
Which is exactly what the lang/compiler would do as that would be an identity conversion. |
Basically, the span overload would only be preferred if it's a collection expression and we target type it to span? |
So why would picking a |
No. It would also be preferred for things like params. |
Right, I didn't mention it because @Neme12 already said it. |
Ok. So, as i've been saying. We prefer span because it is better when a non-identity conversion is involved. If you have an array, and it's an identity conversion, then identity conversion rules continue to apply. :) |
Oh sorry, I misunderstood the proposal then. I should have read all of the detailed design before commenting. |
i thnk you guys may be confusing what an identity conversion is versus the 'implicit span conversion' that fred is talking about adding. That's a implicit widening conversion. not an identity conversion. An identity conversion si still first priority in the conversion list. |
no worries :) |
I agree it definitely makes sense to prefer |
I'm still not understanding this part of the motivation though. When I look at the linked API proposal, I don't see any duplication. And why would a developer have to convert to |
Because they're counting on this proposal to come in and not force them to duplicate 🙂.
Because the implicit conversion is a user-defined conversion, which doesn't carry through in a number of places, including type inference for these highly-generic cases. |
Ohh, I see what the issue is now (the My concerns were about making spans to be considered more specific than e.g. arrays with respect to conversions, as opposed to the opposite, like today. Although now than I'm thinking about it, if a type has implicit conversions to both arrays and spans, the span ones could indeed be better as they should be non-allocating (at least that's the assumption for implicit conversions to spans), and could return a span directly over a struct field. But I'd still definitely want to avoid a scenario where given an existing array and method overloads for both arrays and spans, the span one is picked anyway even though I have an exact type. |
First-Class Span Types
Summary
We introduce first-class support for
Span<T>
andReadOnlySpan<T>
in the language, including new implicit conversion types and consider them in more places,allowing more natural programming with these integral types.
Motivation
Since their introduction in C# 7.2,
Span<T>
andReadOnlySpan<T>
have worked their way into the language and base class library (BCL) in many key ways. This is great fordevelopers, as their introduction improves performance without costing developer safety. However, the language has held these types at arm's length in a few key ways,
which makes it hard to express the intent of APIs and leads to a significant amount of surface area duplication for new APIs. For example, the BCL has added a number of new
tensor primitive APIs in .NET 9, but these APIs are all offered on
ReadOnlySpan<T>
. Because C# doesn't recognize therelationship between
ReadOnlySpan<T>
,Span<T>
, andT[]
, it means that any developers looking to use those APIs with anything other than aReadOnlySpan<T>
have to explicitlyconvert to a
ReadOnlySpan<T>
. Further, it also means that they don't have IDE tooling guiding them to use these APIs, since nothing will indicate to the IDE that it is validto pass them after conversion. There are also issues with generic inference in these scenarios. In order to provide maximum usability for this style of API, the BCL will have to
define an entire set of
Span<T>
andT[]
overloads, which is a lot of duplicate surface area to maintain for no real gain. This proposal seeks to address the problem byhaving the language more directly recognize these types and conversions.
Detailed Design
https://github.com/dotnet/csharplang/blob/main/proposals/first-class-span-types.md
Design meetings
The text was updated successfully, but these errors were encountered: