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.
-
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