"pub fn main" AFTER constant declarations

Hi friends. Today I learned, by trial and error, that just by changing pub fn main placement I can get this code to compile and run:

// "MAIN" AT THE BEGINNING: WRONG!
// pub fn main() void {
const std = @import("std");

const Dog = struct {
    age: u8 = 1,
    legs: u8 = 4,
    name: []const u8 = "Cococha",
    fn bark(self: *Dog) void {
        std.debug.print("Woof, woof, I'm {s}!\n", .{self.name});
    }
};

// "MAIN" AFTER CONST DECLARATIONS: WORKS!
pub fn main() void {
    var doggie = Dog {
        // .name = "Bobby",
    };

    std.debug.print("\n1. Doggie's age is {d} years.\n\n", .{doggie.age});
    std.debug.print("2. Doggie has {d} legs.\n\n", .{doggie.legs});
    doggie.age = doggie.age + 2;
    std.debug.print("3. Doggie's age is now {d} years.\n\n", .{doggie.age});
    std.debug.print("4. Doggie's name is {s}.\n\n", .{doggie.name});
    doggie.name = "Elsa";
    doggie.bark();
}

If I reverse my changes (by commenting out the second line with pub fn main and uncommenting the first line with pub fn main) then the code won’t compile or run.

It must be a very 101 discovery, I know, but can you point me to some documentation where the right placement for const declarations and main function in zig source files is discussed?

The version that works is using Container Level Variables while the one that does not work uses Local Variables, one of the differences between the two is that the former are “order-independent and lazily analyzed” while the latter are not.

Simplified you can say a function gets executed sequentially while containers (namespaces) are just a collection of declarations and thus can be treated differently to make it more convenient to use for the programmer (no forward declarations required).

If you change fn bark(self: *Dog) void { to fn bark(self: *@This()) void { then both work, because then that line no longer depends on the name Dog being defined (while we are currently trying to define it), instead we just say “use the type we are currently defining”. But a better way is to move the whole definition of Dog out of the main function, or into some other container level scope, because referring to it via its name is idiomatic, but it is only possible with container level variables (because of their lazy nature/resolution which allows us to use a name before it is defined, this isn’t allowed in the other case because there we evaluate more eagerly/sequentially).

3 Likes

Both Dog and std are not just simple constants (i.e. 5, “hello”), they are types (structs).
Apparently, Zig does not allow to declare types inside functions.

1 Like

It is allowed they just aren’t able to refer to their own identifier name, because they aren’t evaluated lazily within a function context. You basically have to treat them as if they were generics and use @This() instead of the named variable which is only in scope after the type is declared (inside functions).

Or you can declare a struct within the function and define types within that, here are some variations:

const std = @import("std");

pub fn main() void {
    const LocalDog = struct {
        age: u8 = 1,
        legs: u8 = 4,
        name: []const u8 = "Cococha",
        fn bark(self: *const @This()) void {
            std.debug.print("Woof, woof, I'm {s}!\n", .{self.name});
        }
    };

    const animals = struct {
        const InnerDog = struct {
            age: u8 = 1,
            legs: u8 = 4,
            name: []const u8 = "Cococha",
            fn bark(self: *const InnerDog) void {
                std.debug.print("Woof, woof, I'm {s}!\n", .{self.name});
            }
        };
    };

    const outer = Dog{ .name = "outer" };
    outer.bark();

    const local = LocalDog{ .name = "local" };
    local.bark();

    const inner = animals.InnerDog{ .name = "inner" };
    inner.bark();

    var doggie = Dog{
        .name = "Bobby",
    };
    std.debug.print("\n1. Doggie's age is {d} years.\n\n", .{doggie.age});
    std.debug.print("2. Doggie has {d} legs.\n\n", .{doggie.legs});
    doggie.age = doggie.age + 2;
    std.debug.print("3. Doggie's age is now {d} years.\n\n", .{doggie.age});
    std.debug.print("4. Doggie's name is {s}.\n\n", .{doggie.name});
    doggie.name = "Elsa";
    doggie.bark();
}

const Dog = struct {
    age: u8 = 1,
    legs: u8 = 4,
    name: []const u8 = "Cococha",
    fn bark(self: *const Dog) void {
        std.debug.print("Woof, woof, I'm {s}!\n", .{self.name});
    }
};
2 Likes

How variables work is documented in Documentation - The Zig Programming Language.

And, by the way, this program works correctly:

pub fn main() void {
    var doggie = Dog{
        .name = "Bobby",
    };

    std.debug.print("\n1. Doggie's age is {d} years.\n\n", .{doggie.age});
    std.debug.print("2. Doggie has {d} legs.\n\n", .{doggie.legs});
    doggie.age = doggie.age + 2;
    std.debug.print("3. Doggie's age is now {d} years.\n\n", .{doggie.age});
    std.debug.print("4. Doggie's name is {s}.\n\n", .{doggie.name});
    doggie.name = "Elsa";
    doggie.bark();
}

const std = @import("std");

const Dog = struct {
    age: u8 = 1,
    legs: u8 = 4,
    name: []const u8 = "Cococha",
    fn bark(self: *Dog) void {
        std.debug.print("Woof, woof, I'm {s}!\n", .{self.name});
    }
};
1 Like

Yes, I was wrong, thanks for correcting me.
The problem with the original code is in @This(), yes.
This works as exprected:

pub fn main() void {
    const std = @import("std");

    const Dog = struct {
        age: u8 = 1,
        legs: u8 = 4,
        name: []const u8 = "Cococha",
        fn bark(self: @This()) void {
            std.debug.print("Woof, woof, I'm {s}!\n", .{self.name});
        }
    };

    var doggie = Dog {
        // .name = "Bobby",
    };

    std.debug.print("\n1. Doggie's age is {d} years.\n\n", .{doggie.age});
    std.debug.print("2. Doggie has {d} legs.\n\n", .{doggie.legs});
    doggie.age = doggie.age + 2;
    std.debug.print("3. Doggie's age is now {d} years.\n\n", .{doggie.age});
    std.debug.print("4. Doggie's name is {s}.\n\n", .{doggie.name});
    doggie.name = "Elsa";
    doggie.bark();
}
1 Like

Sorry for replying to closed topic, just wanted to add a link to related topic
A citation from there:

2 Likes