How to initialize a static array literal of struct elements

Hello,

I am trying to figure out how to initialize a static array of struct elements using the literal syntax. I didn’t find examples of this in the language reference, so I wonder if what I am trying even makes sense, or maybe isn’t recommended or something.
Anyway, there is clearly something I am missing, can you help?

Here is what my code looks like:

const std = @import("std");

const NoteEvent = struct {
    pitch: u8,
    velocity: u8,
    note_position: f32,
    duration: f32,

    pub fn debugPrint(self: @This()) void {
        std.debug.print("NoteEvent{{ .pitch = {[pitch]}, .velocity: {[velocity]}, .note_position: {[note_position]}, .duration: {[duration]} }}\n", self);
    }
};

fn printNotes(notes: []NoteEvent) void {
    for (notes) |note| {
        note.debugPrint();
    }
}

pub fn main() !void {
    // this was my first naive attempt, with this I get an error:
    // error: type '[]main.NoteEvent' does not support array initialization syntax
    const notes: []NoteEvent = []NoteEvent{
        NoteEvent{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        NoteEvent{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
    };

    printNotes(notes);
}

After the above attempt, I searched on the internet and realized I probably needed to create an array, so tried the following things:

1:

    // make an array, with an inferred size. I get this error:
    // error: array literal requires address-of operator (&) to coerce to slice type '[]main.NoteEvent'
    const notes: []NoteEvent = [_]NoteEvent{
        NoteEvent{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        NoteEvent{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
    };

2:

    // then I add the & operator, I get:
    // error: unable to infer array size
    const notes: []NoteEvent = [_]NoteEvent&.{
        NoteEvent{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        NoteEvent{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
    };

3:

    // so I try to add the size manually, and then I get:
    // error: incompatible types: 'type' and 'struct{comptime main.NoteEvent = .{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 }, comptime main.NoteEvent = .{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 }}'
    const notes: []NoteEvent = [2]NoteEvent & .{
        NoteEvent{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        NoteEvent{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
    };
  1. ask for help: this post :grinning:

So, what am I missing?
Thanks!

4:

const notes: []const NoteEvent = &.{
        .{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        .{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
};

In zig 0.14.0-dev.6+0ba64e9ce

2 Likes

Thank you, that did it!

I subsequentely needed to @constCast when passing it to the function later, as per the message to discard the const qualifier.

Working code (Zig 0.13):

const std = @import("std");

const NoteEvent = struct {
    pitch: u8,
    velocity: u8,
    note_position: f32,
    duration: f32,

    pub fn debugPrint(self: @This()) void {
        std.debug.print("NoteEvent{{ .pitch = {[pitch]}, .velocity: {[velocity]}, .note_position: {[note_position]}, .duration: {[duration]} }}\n", self);
    }
};

fn printNotes(notes: []NoteEvent) void {
    for (notes) |note| {
        note.debugPrint();
    }
}

pub fn main() !void {
    const notes: []const NoteEvent = &.{
        .{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        .{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
    };
    printNotes(@constCast(notes));
}

I believe I’ve seen more code doing this:

// Create array
const notes = [_]NoteEvent{ //... };

// Then pass a slice, either:
printNotes(&notes); // pointer to array coerced to slice
// or
printNotes(notes[0..]); // actual slice

And if you’re not modifying the data in printNotes you can change the parameter type to []const NoteEvent (avoids the const cast).

4 Likes

@dude_the_builder Hi! Yeah, I was trying to do that, but I kept getting errors… Could you point me to some working example code that does that?

About the []const NoteEvent in the function: I thought about doing that, but I didn’t because I was assuming if I did that, then I wouldn’t be able to call it later with a slice from an ArrayList later (dynamically allocated), is my assumption wrong ? :thinking:

I want to keep the types of my functions very generic, so that later I can pass dynamically allocated stuff – for now I’m just test stuff in main() until I get the logic to work, that’s why I went with the @constCast. Is this the way to go or maybe I’m missing something?

Btw, are there any examples of doing similar stuff with nested structures?

Here is what I am trying to get to work:

const std = @import("std");

const NoteEvent = struct {
    pitch: u8,
    velocity: u8,
    note_position: f32,
    duration: f32,

    pub fn debugPrint(self: @This()) void {
        std.debug.print("NoteEvent{{ .pitch = {[pitch]}, .velocity: {[velocity]}, .note_position: {[note_position]}, .duration: {[duration]} }}\n", self);
    }
};

const Measure = struct {
    notes: []NoteEvent,
};

const Track = struct {
    name: []u8,
    measures: []Measure,
};

const SongSection = struct {
    tempo: u32 = 120,
    time_signature: [2]u8 = .{ 4, 4 },
    tracks: []Track,

    pub fn getMeasureDurationMs(self: *SongSection) u32 {
        return self.time_signature[0] * self.getBeatDurationMs();
    }

    pub fn getBeatDurationMs(self: *SongSection) u32 {
        return 60000 / self.tempo;
    }
};

fn printNotes(notes: []NoteEvent) void {
    for (notes) |note| {
        note.debugPrint();
    }
}

fn printMeasure(measure: Measure) void {
    printNotes(measure.notes);
}

pub fn main() !void {
    const notes: []const NoteEvent = &.{
        .{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        .{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
    };
    printNotes(@constCast(notes));

    // this works:
    const measure = Measure{ .notes = @constCast(notes) };
    printMeasure(measure);

    // but this doesn't:
    const measure2 = Measure{ .notes = @constCast(NoteEvent & .{
        .{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        .{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
    }) };
    printMeasure(measure2);
}

As you can see, I have a bit of a deep tree of data structures, and I want to initialize them ideally in one big literal block (for a track, for example).
I haven’t found any example of this, so I am trying to work out the basics for only measures and notes, to be able to generalize for the rest.

If you could point me to some documentation or examples that do similar stuff, that would be much appreciated!
Thank you!

This code works (v0.13.0):

const std = @import("std");

const NoteEvent = struct {
    pitch: u8,
    velocity: u8,
    note_position: f32,
    duration: f32,

    pub fn debugPrint(self: @This()) void {
        std.debug.print("NoteEvent{{ .pitch = {[pitch]}, .velocity: {[velocity]}, .note_position: {[note_position]}, .duration: {[duration]} }}\n", self);
    }
};

const Measure = struct {
    notes: []const NoteEvent, // added const here
};

const Track = struct {
    name: []const u8, // added const here
    measures: []const Measure, // added const here
};

const SongSection = struct {
    tempo: u32 = 120,
    time_signature: [2]u8 = .{ 4, 4 },
    tracks: []const Track, // added const here

    pub fn getMeasureDurationMs(self: SongSection) u32 { // removed * here
        return self.time_signature[0] * self.getBeatDurationMs();
    }

    pub fn getBeatDurationMs(self: SongSection) u32 { // removed * here
        return 60000 / self.tempo;
    }
};

fn printNotes(notes: []const NoteEvent) void {
    for (notes) |note| note.debugPrint();
}

fn printMeasure(measure: Measure) void {
    printNotes(measure.notes);
}

pub fn main() !void {
    const notes = [_]NoteEvent{
        .{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
        .{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
    };
    printNotes(&notes);

    // this works:
    const measure = Measure{ .notes = &notes };
    printMeasure(measure);

    // this too :)
    const measure2 = Measure{
        .notes = &.{
            .{ .pitch = 60, .velocity = 127, .note_position = 0, .duration = 1 },
            .{ .pitch = 62, .velocity = 127, .note_position = 1, .duration = 3 },
        },
    };
    printMeasure(measure2);
}

I think the big lesson here (as I’ve seen recommended by many others) is to always start out with const and if you get an error because you need mutability, then make it non-const. As you can see, by just making all those slices and function parameters const, all the errors went away and you can create slices from literals (as in measure2) since literals are always const.

Always keep in mind, you can go from non-const to const safely with no problems, so you could pass in that slice from ArrayList as you mentioned. Going from const to non-const, however, is unsafe (that’s why it requries an explicit @constCast) and thus will make your functions and struct fields more restrictive instead of more general.

2 Likes

@dude_the_builder Thank you so much!

Yeah, indeed, it was not clear to me that it’d be easy to go from non-const to const whenever needed. I am not sure if I fully understood yet tbh, but I will trust your advice for now! :smile:

Need to do some more reading and experimenting to fully grasp it – thank you for the advice and explanations!

(Btw, i’ve been watching your Youtube videos and i’ve found them very helpful – thanks for those as well!)

2 Likes

The difference here is between a slice literal:

And an array literal:

The first one uses [] and &.{...}, the second uses [_] and {...}, no address operator, and no ., which is tuple syntax, also used for anonymous structs.

It’s easy to cast an array to a slice, so you probably want to use array literals.

2 Likes