Tuple literals can be coerced into array and many-item pointers, but tuple variables can't. Intended design?

An example:

pub fn main() !void {
    var x: u8 = 1; _ = &x;
    
    var s1: []const u8 = &.{ x, x }; _ = &s1;
    var mp1: [*]const u8 = &.{ x, x }; _ = &mp1;
    var pa1: *const [2]u8 = &.{ x, x }; _ = &pa1;
    
    var a = .{ x, x }; _ = &a;
    
    var s2: []const u8 = &a; _ = &s2;
    //var mp2: [*]const u8 = &a; _ = &mp2; // error
    //var pa2: *const [2]u8 = &a; _ = &pa2; // error
}

I think this is intended as it’s the way how you create slices from arrays. For instance in build.zig you often see something like this:

obj.root_module.addCSourceFile(.{
    .file = b.path("my_source.c"),
    .flags = &.{ // <-- notice the `&`
        "-g",
        "-O2",
        "-D__TARGET_ARCH_x86",
        "-fno-asynchronous-unwind-tables",
    },
});

The weirdness here is not they can, it is they can't. Or precisely, literals can, vars can’t.

1 Like

Oh yeah, sorry.

Now that I know that I think it’s likely due to result location semantics. My mental model would be that the assignment basically “fixes/finalizes” the type. Though I will stop guessing and let the people who really know this answer :sweat_smile:

1 Like

.{ x, y } is not a tuple literal, rather it is composite type literal with an inferred type.

If there is no type to infer, then zig generates a new type that fits the literal, in your examples that would be a tuple, but that depends on the literal.

.{ .foo = x } would generate a non-tuple struct.

Tuples can coerce to arrays, but only by value not pointer.

    var a = .{ x, x }; _ = &a;
    var s2: [2]u8 = a; _ = &s2;
4 Likes

What is the difference between a tuple literal and composite type literal? I can’t find the latter term in official doc.

Regardless of the term name, why do the last two lines fail to compile?

sorry, the term zig uses is “anonymous struct literal”

The difference is that there is no distinction between anonymous (type inferred) array or tuple literals.

The only distinction would be between non-tuple struct and array/tuples by way of field names.

I still don’t understand why do the last two lines fail to compile. Do you mean a and .{x, x} are of two different types?

pub fn main() !void {
    var x: u8 = 1; _ = &x;
    var mp1: [*]const u8 = &.{ x, x }; _ = &mp1;
    var pa1: *const [2]u8 = &.{ x, x }; _ = &pa1;
    
    var a = .{ x, x }; _ = &a;
    var mp2: [*]const u8 = &a; _ = &mp2; // error
    var pa2: *const [2]u8 = &a; _ = &pa2; // error
}

Pointers to tuples don’t coerce to pointers to arrays, not to many item pointers.

Tuples only coerce to arrays by value, that is by copy, not pointer.

I don’t know how to explain this any better than I already have.

So do you mean &.{ x, x } is not a pointers to tuple? Because it coerces to pointers to arrays and many item pointers.

in the context of:

    var mp1: [*]const u8 = &.{ x, x }; _ = &mp1;
    var pa1: *const [2]u8 = &.{ x, x }; _ = &pa1;

Both give a result type that is not a tuple, so yes &.{ x, x } does not create a tuple in that context.

But if you did not provide types, then it would create a pointer to a tuple.

I generally agree with @vulpesx’s point, but I think part of this misunderstanding is due to the documentation not being thorough enough.

I think the current syntax for initializing array pointers and such isn’t based on tuple coercion, but on result type inference.

But the current document explains the relationship between the result type and .{ x } like this:

Expression
.{x}
Parent Result Type
T
Sub-expression Result Type
x is a @FieldType(T, "0")

Since @FieldType only applies to struct (tuple) types, it’s easy to misunderstand that .{ x } is just a tuple literal. Using it to initialize slices or multiple pointers works through type coercion rather than inferred result type.

I admit I still don’t understand what exactly you are talking about. :smiley:
It is just quite confusing to me.

At least, pointers to tuples can be coerced into slices, then it is quite easily to get array pointers and many-item pointers from the resulting slices. It looks meaningless to me that prevent pointers to tuples from being directly coerced into array pointers and many-item pointers.

pub fn main() !void {
    var x: u8 = 1; _ = &x;
    var a = .{ x, x }; _ = &a;
    
    var s2: []const u8 = &a; _ = &s2;
    //var mp2: [*]const u8 = &a; _ = &mp2; // error
    //var pa2: *const [2]u8 = &a; _ = &pa2; // error
    
    var mp3: [*]const u8 = @as([]const u8, &a).ptr; _ = &mp3; // okay
    var pa3: *const [2]u8 = @as([]const u8, &a)[0..2]; _ = &pa3; // okay
}

This is indeed a bit confusing. It’s possible that this inconsistency is caused by the missing error report in this particular compiler implementation. I agree that if array pointers aren’t allowed to be coerced like this, slices certainly shouldn’t be either.

2 Likes

@vulpesx, do you agree with @npc1054657282 on this point? If not, is there a rule in official doc to allow pointers to tuple variables -> slices coercion?

This is though only possible if the inner types are coercible into the type of target slice. And with that it is only really possible for tuples of primitive types and for tuples who’s element have all the same type, making them equal to an array.

var x: u8 = 1; _ = &x;
var y: u16 = 2; _ = &y;

const c = .{ x, y }; _ = &c;
var s4: []const u16 = &c; _ = &s4;
const b = .{ x, y }; _ = &b;
var s3: []const u8 = &b; _ = &s3; // error

And at least for me this makes sense. Tuples have a compile time known length and list of types. If those types all are the same they are basically equal to an array of the same size. So the argument for being coercible to slices boils down to arrays being coercible to slices. And we should of course be able to do this.