Replies: 21 comments
-
Another unclear case: static void Main(string[] args)
{
Span<int> span = ValueList.List(1, 2, 3);
// still safe to use span?
} |
Beta Was this translation helpful? Give feedback.
-
I don't believe this is safe. Consider the following code: static void Main()
{
Span<int> span1 = ValueList.List(1, 2, 3);
Span<int> span2 = ValueList.List(4, 5, 6);
Console.WriteLine(span1[0]);
} When I run it, it prints I think this hinges on the lifetime of the temporary variable that is introduced by the compiler when passing a value to an |
Beta Was this translation helpful? Give feedback.
-
Yes, the first value list gets overwritten by the second. It is a bit weird though, because initially the lists are actually in different memory locations, but the JIT decides to copy each into the same one. See on Sharplab. I guess it is not a bug since we are using |
Beta Was this translation helpful? Give feedback.
-
@SingleAccretion I think the JIT is required to do that overwrite, because the C# compiler decides to reuse the same local variable for both |
Beta Was this translation helpful? Give feedback.
-
I am more concerned about passing class Test
{
static void Foo(Span<int> span1, Span<int> span2)
{
Console.WriteLine(span1[0]); // print 1
Console.WriteLine(span2[0]); // print 4
}
static void Main(string[] args)
{
Foo(ValueList.List(1, 2, 3), ValueList.List(4, 5, 6));
}
} |
Beta Was this translation helpful? Give feedback.
-
I think the consensus among people here would be that you shouldn't use this code. This simple and innocent example breaks: static void Main(string[] args)
{
var a = PreserveReference(ValueList.List(1, 2, 3));
var b = PreserveReference(ValueList.List(4, 5, 6));
Console.WriteLine(a[0]); //Prints 4
}
static Span<int> PreserveReference(Span<int> span) => span.Slice(0); |
Beta Was this translation helpful? Give feedback.
-
@SingleAccretion That's true, but my motivation is to pass length-variadic tuples as arguments without allocations, and returning references to the arguments is, IMO, pointless in most situations. |
Beta Was this translation helpful? Give feedback.
-
I am sorry, that's just me being wrong. The example was indeed OK. |
Beta Was this translation helpful? Give feedback.
-
In this documentation, there are 2 sentences about the life time of temporary variables:
Does the first sentence mean: static Span<int> ToSpan(in ValueList v) => v;
static void Main(string[] args)
{
if (true)
{
var span = ToSpan(ValueList.List(1, 2, 3));
// ValueList.List(1, 2, 3) still lives
}
// ValueList.List(1, 2, 3) is destroyed
} And does it also appliy to implicit conversions? |
Beta Was this translation helpful? Give feedback.
-
I think the C# language spec states that the As for whether your code works, the spec would say, |
Beta Was this translation helpful? Give feedback.
-
@agocke I think this code didn't introduce a temporary variable. public static implicit operator Span<int>(in ValueList v) =>
MemoryMarshal.CreateSpan(ref Unsafe.AsRef(v.i0), v.length); |
Beta Was this translation helpful? Give feedback.
-
I think I was working on a wrong direction. ref struct ValueList
{
Span<int> span;
public static implicit operator Span<int>(in ValueList v) => span;
public static ValueList List(params Span<int> args) =>
new ValueList { span = args };
}
class Test
{
static void Foo(in ValueList list) { }
static void Main(string[] args)
{
Foo(ValueList.List(1, 2, 3)); // prevent zero-alloc optimization?
}
} I am not sure what will happen. |
Beta Was this translation helpful? Give feedback.
-
Though not related to the title, there's a solution looks pretty: [StructLayout(LayoutKind.Sequential)]
struct ValueList
{
int length;
int i0,i1,i2,i3;
public static implicit operator Span<int>(in ValueList v) =>
MemoryMarshal.CreateSpan(ref Unsafe.AsRef(v.i0), v.length);
// no syntax for one-element tuple
public static implicit operator ValueList(int value) =>
new ValueList { length = 1, i0 = value };
public static implicit operator ValueList(in (int, int) tuple) =>
new ValueList { length = 2, i0 = tuple.Item1, i1 = tuple.Item2 };
public static implicit operator ValueList(in (int, int, int) tuple) =>
new ValueList { length = 3, i0 = tuple.Item1, i1 = tuple.Item2, i2 = tuple.Item3 };
public static implicit operator ValueList(in (int, int, int, int) tuple) =>
new ValueList { length = 4, i0 = tuple.Item1, i1 = tuple.Item2, i2 = tuple.Item3, i3 = tuple.Item4 };
}
class Test
{
static void Foo(in ValueList list1, in ValueList list2) { }
static void Main(string[] args)
{
Foo((1, 2, 3), (4, 5, 6));
}
} |
Beta Was this translation helpful? Give feedback.
-
However, I still love the idea of using |
Beta Was this translation helpful? Give feedback.
-
Now I understand what you meant. You meant that a static Span<int> Foo()
{
int i = 0;
return MemoryMarshal.CreateSpan(ref i, 1);
} The above compiles, but I don't believe |
Beta Was this translation helpful? Give feedback.
-
Yes, but I believe that's what the ref-return lifetime of |
Beta Was this translation helpful? Give feedback.
-
I am not quite understand what you mean. dotnet/runtime/issues/27295 seems related, but no one talked about temporary struct. There is a guarantee in C++:
https://en.cppreference.com/w/cpp/language/reference_initialization To be clear, that's what I am mainly concerned about. |
Beta Was this translation helpful? Give feedback.
-
I'm saying that you're currently out of the bounds of the language. The Unsafe and MemoryMarshal APIs violate the Span safety rules, so the language behavior for your program is undefined. |
Beta Was this translation helpful? Give feedback.
-
@agocke I agree with you. Those API should be used very carefully, in some performance-sensitive code. |
Beta Was this translation helpful? Give feedback.
-
So then the answer to your original question,
is yes, but you're out of bounds of the specification. Under the specification there's no way to observe the lifetime of an |
Beta Was this translation helpful? Give feedback.
-
@agocke Thanks. Perhaps the best choice is to change parameter type to |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I am writing a value-type list implicitly convertible to
Span<T>
, as a temporary solution for "params Span".As a first try:
And the typical use case:
The codes compile, but I don't know whether or not the temporary struct will be destroyed before calling
Foo
.Is there any spec about this?
Beta Was this translation helpful? Give feedback.
All reactions