csharplang icon indicating copy to clipboard operation
csharplang copied to clipboard

Consider changing the inferred type of stackalloc to a Span<T> instead of a T* if using a safe context

Open Dewera opened this issue 5 years ago • 15 comments
trafficstars

Currently, if you want to use stackalloc to allocate a Span<T> you need to do one of the following because the inferred type when using the var keyword is a T* (which you cannot access in a safe context.

Span<T> span = stackalloc T[size]

or

var span = (Span<T>) stackalloc T[size]

It would be nice if this so that you could do something like the following and get a Span<T> as the inferred type if you were not using an unsafe context

var span = stackalloc T[size]

Dewera avatar Apr 26 '20 10:04 Dewera

This is a language proposal, not a framework/runtime proposal. Language changes should be proposed at dotnet/csharplang.

But on top of that, I don't see this being considered because it changes meaning of the code depending on whether the current block is unsafe or not.

Joe4evr avatar Apr 26 '20 14:04 Joe4evr

As @Joe4evr said, we likely can't do this. If we had Span when stackalloc was added to the language, it probably would never have returned a pointer array. But it did, and having the result type of an expression change based on the unsafe context does not sound like a good idea to me.

333fred avatar Apr 26 '20 17:04 333fred

I'm personally intrigued by this idea. Namely, while it would technically be a breaking change , I wonder if we could justsay that the type of stackalloc was Span<T>? We could also say there was an implicit conversion (in unsafe contexts only of course) from Span<T> to T*. This could technically break overload resolution, but i wonder if that would actually happen/matter in practice...

CyrusNajmabadi avatar Apr 26 '20 18:04 CyrusNajmabadi

The above comments make sense and I agree that returning different types between unsafe and safe probably isn't the best idea, however, as mentioned by @CyrusNajmabadi, maybe changing stackalloc to always return a Span<T> with an implicit cast to a T*. This would still provide the ability to work with pointers if wanted but encourage the use of the safer Span<T> whilst also solving the original problem of returning a T* in a safe context.

Dewera avatar Apr 26 '20 22:04 Dewera

Converting a Span<T> to a T* is not a safe operation. Span<T> has different layouts on different platforms, may be a GC tracked reference, and wouldn't be free from a codegen perspective.

stackalloc is often used in perf critical paths and I dont expect users would find it acceptable if that regresses.

I also think it adds more confusion for relatively little benefit. Checked/unchecked operations are bad enough and I wouldn't want to remember that var x = stackalloc might differ due to a non-visible unsafe keyword that is on the type 50 lines up.

tannergooding avatar Apr 27 '20 14:04 tannergooding

Currently, if you want to use stackalloc to allocate a Span you need to do one of the following

or this

var span = (stackalloc T[size]);

var span = _ = stackalloc T[size]; should work too but it doesn't. looks like a bug to me.

alrz avatar Apr 27 '20 16:04 alrz

@tannergooding I agree with what you are saying, however, I still don't find it great to have a type infer to a pointer in a safe context. From my understanding, unsafe code was always supposed to be an explicit opt-in feature and whilst I understand that stackalloc was an unsafe only operation before the introduction of Span<T> it just feels like sort of goes against this philosophy.

Dewera avatar Apr 28 '20 06:04 Dewera

Why would this be a breaking change?

int Foo() { var s = stackalloc int[5]; }

does not currently compile.

int Foo() { Span<int> s = stackalloc int[5]; }

does.

unsafe int Foo() { var s = stackalloc int[5]; }

Compiles of course, and would still (if this is accepted) infere int*. So the meaning of existing code would not change.

I agree it's kind of weird that the return type changes based on unsafe context, but why is it a "breaking" change? All it does it allow code that currently does not compile to do so, no?

chrisaut avatar Apr 29 '20 09:04 chrisaut

I don't think it's technically a breaking change. It would be a very odd language semantic, but it's not technically a break.

333fred avatar Apr 29 '20 15:04 333fred

@chrisaut It's not about it being a breaking change to allow this; it's that doing this makes the simple act of changing a block to unsafe in the future breaking. Consider the following if this was done:

struct S
{
    // 50 lines down....
    int M()
    {
        var span = stackalloc int[8]; // not unsafe, inferred as Span<int>
        return span.Length; // valid
    }
}

And then you add an unsafe modifier somewhere:

unsafe struct S
{
    // 50 lines down....
    int M()
    {
        var span = stackalloc int[8]; // unsafe context, inferred as int*
        return span.Length; // oops! cant do .Length on int*
    }
}

Joe4evr avatar Apr 29 '20 15:04 Joe4evr

@Joe4evr that's why I don't think we'd do this, but that doesn't make this a breaking change. A breaking change changes the meaning of existing, compiling code.

333fred avatar Apr 29 '20 15:04 333fred

Right. And I probably agree that the gain here would be small compared to the weird semantics. I was just trying to understand why this would be breaking.

It would still be nice to just use var in safe contexts, but yeah....

chrisaut avatar Apr 29 '20 15:04 chrisaut

@Joe4evr does it not make more sense then to return a Span<T> for both cases as you can easily get a pointer to the first element in a Span<T> if you wanted to work with pointers.

This is obviously a breaking change though

Dewera avatar Apr 30 '20 11:04 Dewera

for both cases as you can easily get a pointer to the first element in a Span<T> if you wanted to work with pointers.

stackalloc is often used in perf critical paths and I dont expect users would find it acceptable if that regresses. Getting a pointer outside a span also requires using a fixed statement which has its own minimal overhead and while MemoryMarshal has some APIs that can help, they aren't available outside of .NET Core.

I don't think it is worth a breaking change for an edge case scenario around saving 5 characters when typing 😄. -- It's also worth noting that you can't return a Span in all cases. You can't use a pointer as a generic type parameter so Span<int*> x = stackalloc int*[10] is illegal while int** x = stackalloc int*[10] is not.

tannergooding avatar Apr 30 '20 14:04 tannergooding

Can we legalize Function((T*) stackalloc T[] { ... }) ?

SupinePandora43 avatar May 31 '22 04:05 SupinePandora43