[Proposal]: First-Class Span Types #8713
Replies: 30 comments
-
I'm probably missing something obvious, but why doesn't just adding |
Beta Was this translation helpful? Give feedback.
-
Implicit operators already exist. They do not suffice because they do not participate in the pieces that I outlined in the proposal. |
Beta Was this translation helpful? Give feedback.
-
Oh I see what you mean, it's because of generics. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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:
|
Beta Was this translation helpful? Give feedback.
-
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:
|
Beta Was this translation helpful? Give feedback.
-
No, that is not intended by this proposal. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
But why would picking |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
So that the caller doesn't need to heap allocate. Which will often be the case the majority of the time. |
Beta Was this translation helpful? Give feedback.
-
To clarify, I think she is asking why we would call the span overload if you already have an array. |
Beta Was this translation helpful? Give feedback.
-
But as I said, it only allocates implicitly for |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Which is exactly what the lang/compiler would do as that would be an identity conversion. |
Beta Was this translation helpful? Give feedback.
-
Basically, the span overload would only be preferred if it's a collection expression and we target type it to span? |
Beta Was this translation helpful? Give feedback.
-
So why would picking a |
Beta Was this translation helpful? Give feedback.
-
No. It would also be preferred for things like params. |
Beta Was this translation helpful? Give feedback.
-
Right, I didn't mention it because @Neme12 already said it. |
Beta Was this translation helpful? Give feedback.
-
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. :) |
Beta Was this translation helpful? Give feedback.
-
Oh sorry, I misunderstood the proposal then. I should have read all of the detailed design before commenting. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
no worries :) |
Beta Was this translation helpful? Give feedback.
-
I agree it definitely makes sense to prefer |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
First-Class Span Types
Champion Issue: #8714
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
Beta Was this translation helpful? Give feedback.
All reactions