Confusion about comptime structs

I am a bit confused about how to create a struct with comptime fields.

I want to make some iterator for my board game.
But cannot get the function working

const Board = struct
{
   fn iterator(comptime orientation, comptime direction) Iterator 
   {
        return Iterator {.orientation = orientatatation,  ...etc. }
        // not compiling
    }
}

const Iterator = struct
{
    comptime orientation: Orientation, // or const, or how?
    comptime direction: Direction // or const or how?
    board: *Board,
    // and some more
}

Edit:
I get:
error: value stored in comptime field does not match the default value of the field
or:
cannot evaluate comptime expression.

That isn’t the real code you are running, orientation and direction don’t even have declared types.

Why do you want to use comptime fields, what is your goal?

I would expect the Iterator to be a function that takes orientation and direction as comptime parameters and returns a struct type, then those fields can be regular fields orientation: Orientation.

const Orientation = enum { horizontal, vertical };
const Direction = enum { up, down, left, right };
const Value = u32;
const Board = struct {
    pub fn iterator(self: *Board, comptime orientation: Orientation, comptime direction: Direction) Iterator(orientation, direction) {
        return Iterator{ .board = self };
    }

    fn Iterator(comptime orientation: Orientation, comptime direction: Direction) type {
        _ = orientation; // autofix
        _ = direction; // autofix
        return struct {
            const It = @This();
            board: *Board,
            pub fn next(it: *It) ?Value {
                _ = it; // autofix
                return 5; // ...
            }
        };
    }
};

If I remember correctly comptime fields only exist for anonymous structs and tuples. You generally shouldn’t use them yourself.
You can achieve the same thing here with a comptime Iterator type:

fn iterator(comptime orientation: Orientation, comptime direction: Direction) Iterator(oerientation, direction) {
    return .{...etc.};
}

fn Iterator(comptime orientation: Orientation, comptime direction: Direction) type {
    return struct {
        board: *Board,
        // and some more
    };
}

This is some more code.
The funny thing is that the Board.letter_iterator() function works when I call it with the default values.
Otherwise I get:
error: value stored in comptime field does not match the default value of the field

So what is the big trick (if any) to initialize this BoardLetter iterator with other than the default values?

pub const Orientation = enum
{
    Horizontal,
    Vertical,
};

pub const Direction = enum
{
    Forwards,
    Backwards,
};

const Board = struct
{
    pub fn letter_iterator(self: *Board, square: Square, comptime orientation_: Orientation, comptime direction_: Direction) BoardLetterIterator
    {
        return BoardLetterIterator.init(self, square, orientation_, direction_);
    }
}

const BoardLetterIterator = struct
{
    const Self = @This();

    comptime orientation: Orientation, // ???
    comptime direction: Direction, // ???
    board: *const Board,
    startsquare: Square,
    square: Square,

    pub fn init(board: *const Board, square: Square, comptime orientation_: Orientation, comptime direction_: Direction) Self
    {
        return Self
        {
            .orientation = orientation_,
            .direction = direction_,
            .board = board,
            .startsquare = square,
            .square = square,
        };
    }
};

I also tried that one. That only works if all parameters are comptime.

It should work fine with non-comptime parameters:

fn iterator(comptime orientation: Orientation, comptime direction: Direction, nonComptime: i32) Iterator(oerientation, direction) {
    return .{.nonComptime = nonComptime, ...etc.};
}

fn Iterator(comptime orientation: Orientation, comptime direction: Direction) type {
    return struct {
        board: *Board,
        nonComptime: i32,
        // and some more
    };
}

I think it is best to approach generic types like this in reverse until you are familiar enough with them, to write the generic version right away.

So my suggestion would be to first write a non generic version that hardcodes the comptime parameters as two constants.
Then see how this differs to a second one with the constants changed.

Then do the step of turning it into a generic type function.

It also seems like there is a misconception somewhere.
When you have a function that has comptime types and returns a type (in this case a struct that implements an iterator) this struct type already has access to those comptime parameters, there is no need to store those as fields.

1 Like

Think what it means for a type to have comptime and non-comptime fields. For instance, comptime_int doesn’t have a well-defined size, how can a struct contain one? comptime fields are just syntactic sugar that allows you to access a comptime value using the . syntax. The actual value can’t coexist with normal runtime data. Therefore, comptime fields are read-only. When assigning to them, you must guarantee that you are assigning the same value that they already contain. All of this dance just exists for the purposes of generic programming. You could achieve the same thing using a const declaration in the type, but then, when doing generic functions you’d have to create two versions, one that expects the data to be a field, and one that expects it to be a declaration.
What you want is a generic type:

fn BoardLetterIterator(comptime orientation: Orientation, comptime direction: Direction) type {
    return struct{
        board: *const Board,
        startsquare: Square,
        square: Square,

        pub fn init(board: *const Board, square: Square) @This() {
            return .{
                .board = board,
                .startsquare = square,
                .square = square,
            };
        }
    };
}
3 Likes

Yeah that is all working

What you want is a generic type:

I tried that also.

    pub fn letter_iterator(self: *const Board, square: Square, comptime orientation: Orientation, comptime direction: Direction) type
    {
        return BoardLetterIterator(orientation, direction).init(self, square);
    }
// still does not compile
var it = board.letter_iterator(112, .Horizontal, .Forwards);
src\main.zig:87:19: error: unable to resolve comptime value
    var it = board.letter_iterator(112, .Horizontal, .Forwards);
             ~~~~~^~~~~~~~~~~~~~~~
src\scrabble.zig:382:130: note: expression is evaluated at comptime because the function returns a comptime-only type 'type'
    pub fn letter_iterator(self: *const Board, square: Square, comptime orientation: Orientation, comptime direction: Direction) type

Somehow I totally miss the point…

You can’t use type there instead use BoardLetterIterator(orientation, direction).

Why doesn’t this work?
Because Zig only has limited support for type inference / you can use type when you are returning a type, but here you are actually returning a value of a specific type.

1 Like

pub fn BoardLetterIterator(comptime orientation: Orientation, comptime direction: Direction) type

AHAAA the function as standalone IS working.

but when it is a “method” of Board it does NOT work.

var it = BoardLetterIterator(.Horizontal, .Forwards).init(&board, 112);
var tt = board.letter_iterator(112, .Horizontal, .Forwards);

These two do exactly the same but only the first is working.
Peculiar…

Use this:

pub fn letter_iterator(self: *const Board, square: Square, comptime orientation: Orientation, comptime direction: Direction) BoardLetterIterator(orientation, direction)
{
    return BoardLetterIterator(orientation, direction).init(self, square);
}

I think you aren’t reading carefully enough, you can’t use type as the return type and then return a value that isn’t a type.

The code above declares the return type to be the type of the actual iterator instance that is returned by the init function.

2 Likes

O my God… You are right again.

Now for my monomorphization of a scrabble board with comptime different sizes :slight_smile: Good exercise.

1 Like