Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3435,11 +3435,12 @@ When a *stackalloc_initializer* is present:

Each *stackalloc_element_initializer* shall have an implicit conversion to *unmanaged_type* ([§10.2](conversions.md#102-implicit-conversions)). The *stackalloc_element_initializer*s initialize elements in the allocated memory in increasing order, starting with the element at index zero. In the absence of a *stackalloc_initializer*, the content of the newly allocated memory is undefined.

If a *stackalloc_expression* occurs directly as the initializing expression of a *local_variable_declaration* ([§13.6.2](statements.md#1362-local-variable-declarations)), where the *local_variable_type* is either a pointer type ([§24.3](unsafe-code.md#243-pointer-types)) or inferred (`var`), then the result of the *stackalloc_expression* is a pointer of type `T*` ([§24.9](unsafe-code.md#249-stack-allocation)). In this case the *stackalloc_expression* must appear in unsafe code. Otherwise the result of a *stackalloc_expression* is an instance of type `Span<T>`, where `T` is the *unmanaged_type*:
If a *stackalloc_expression* occurs directly as the initializing expression of a *local_variable_declaration* ([§13.6.2](statements.md#1362-local-variable-declarations)), where the *local_variable_type* is either a pointer type ([§24.3](unsafe-code.md#243-pointer-types)) or inferred (`var`), then the result of the *stackalloc_expression* is a pointer of type `T*` ([§24.9](unsafe-code.md#249-stack-allocation)). In this case the *stackalloc_expression* must appear in unsafe code. Otherwise the result of a *stackalloc_expression* is an instance of type `Span<T>` or `ReadOnlySpan<T>`, where `T` is the *unmanaged_type*:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this change is right.

  1. Consider the following (SharpLab):

    using System;
    public class C {
        public void M() {
            C c = stackalloc int[1];
        }
        public static implicit operator C(Span<int> x) => new C();
    }

    If ReadOnlySpan<T> has a similar implicit conversion operator from Span<T>, then that can be used for the initialization, and the type of the stackalloc expression need not be ReadOnlySpan<T> directly.

  2. If the type of stackalloc expression could be either Span<T> or ReadOnlySpan<T>, then you'd likely need a disambiguation rule for when both types are feasible, e.g. when calling an overloaded or generic method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @KalleOlaviNiemitalo is correct here, it looks like Span<T> has an implicit conversion to ReadOnlySpan<T>. If Span is changed to ReadOnlySpan in the example it fails as it requires two implicit conversions to be inserted, but use an intermediate variable and it is valid as each line only requires the insertion of one conversion:

    public void M()
    {
        ReadOnlySpan<int> x = stackalloc int[1];
        C c = x;
    }

Copy link
Contributor

@KalleOlaviNiemitalo KalleOlaviNiemitalo Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implicit conversion is required by C.3 Standard Library Types not defined in ISO/IEC 23271:

public readonly ref struct Span<T>
{
public int Length { get; }
public ref T this[int index] { get; }
public static implicit operator ReadOnlySpan<T>(Span<T> span);
}

So I don't think any normative text for stackalloc should be changed for this. A note could be added though.

C.3 seems to be the only mention of ReadOnlySpan<> in the spec. AFAICT the semantics of the implicit conversion are unspecified and a conforming implementation could make it always return default(ReadOnlySpan<T>). Users would be unlikely to choose such an implementation though. But any note added in the stackalloc description should be carefully written not to claim any semantics that the standard does not actually require.


- `Span<T>` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)) is a ref struct type ([§16.2.3](structs.md#1623-ref-modifier)), which presents a block of memory, here the block allocated by the *stackalloc_expression*, as an indexable collection of typed (`T`) items.
- `Span<T>` and `ReadOnlySpan<T>` ([§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271)) are ref struct types ([§16.2.3](structs.md#1623-ref-modifier)), which presents a block of memory, here the block allocated by the *stackalloc_expression*, as an indexable collection of typed (`T`) items.
- The result’s `Length` property returns the number of items allocated.
- The result’s indexer ([§15.9](classes.md#159-indexers)) returns a *variable_reference* ([§9.5](variables.md#95-variable-references)) to an item of the allocated block and is range checked.
- The result’s indexer ([§15.9](classes.md#159-indexers)) is range checked.
- For `Span<T>`, the result's indexer returns a *variable_reference* ([§9.5](variables.md#95-variable-references)) to an item of the allocated block. For `ReadOnlySpan<T>`, the result's indexer returns the value of an item of the allocated block.

Stack allocation initializers are not permitted in `catch` or `finally` blocks ([§13.11](statements.md#1311-the-try-statement)).

Expand Down