Function parameter

Edit: makes original post as a question.

I want to know when a method needs to take the outer type as first parameter? Take the common Iterator as example: A next() method of type Iterator usually takes the type it lives in as first parameter and you don’t have to fill in as argument at call site; an init() method of type Iterator doesn’t care about that. E.g., next(it: *Iterator) ?u8 and init(vector: []const u8) Iteratior

You’ve only described a feature, what about it do you want explained.

I’ve seen lots of methods take the outer type as its first parameter. I don’t know when to do that.

It’s not centralized in the langref, but maybe you’re looking for the dot syntax? More precisely in the the context of struct.

This is a syntactic sugar, there are only function and function call, it’s just presented differently.


The pattern of the iterator is closely related to while loop with optionals, so maybe your question is about that.

This is kind of emerging from the interaction of while, captures and optionals

1 Like

So it’s syntax sugar like Iterator.next(*Iterator) V.S. iter.next(). I got it.

Is there some Zig idiomatic way for naming the first argument? In Python self is the convention in methods, but what is good practice in Zig?

I found there are two patterns: 1) doThing(self: @This()) 2) doAnotherThing(self: Self) with const Self = @This(); As for return type, maybe Self, maybe MyType though self: Self or self: @This() used.

Some people just use self. I’ve seen it advocated, and I agree, to name it something meaningful instead. For example if i’m in a function pub fn explode(self: Player) self isn’t very descriptive and I find it more useful to provide the context I'm acting on a player, than I'm acting on whatever the current self object is

So I’d prefer pub fn explode(player: Player) or something that provides more information

8 Likes

Interesting, that’s also what I’ve been doing in my project. Precisely for the reason you brought up. Glad to see I’m not alone :slight_smile:

personally I’ve come back from the self: Self pattern.
First it’s uninformative, if you jump in the middle of a big file and you just see self: Self it’s not helpful. Am I looking at the manager or unmanaged version of the hashmap ?

Second it doesn’t play well with nesting, if you nest your struct the nested one can’t use Self again

5 Likes

Agree, I personally prefer to name things like

pub const Sensor = struct {
    pub fn doStuff(camera: *Sensor) void {
        _ = camera;
    }
};

and if i don’t make something an implicit struct type, I love to do this

pub const Sensor = struct {
    pub fn doStuff(camera: *hardware.Sensor) void {
        _ = camera;
    }
};

basically even in the same file use a 2 level namespace, this way it’s really easy to find where something is coming from

4 Likes

Agreed, I only fall back to the name ‘self’ when it’s paired with a type ‘@This()’ and even then there is often a better name.

I tend to spell out types using their fully qualified name w.r.t to the outermost scope, especially when the type name is something rather uninformative on its own, and only makes sense when coupled to the owning type, eg

const Node = struct {
    fn do_something(node: Node) void { 
        // ...
    }
    const Index = enum(u16) {
        _,
    
        fn get(index: Node.Index, list: []const Node) Node {
            // ...
        }
    };
};

I reserve the use of const Self = @This() for templated types, because due to Zig’s ability to nest types and methods, seeing a wild Self or @This() is absolutely unhelpful, and I don’t like to strain my eyes counting levels of indentations. Generic types are the only exception, because I often give the type function the name I want to give the type, and you can’t shadow names.

Incidentally, as this is a problem I rarely encounter in other languages, this has forced to think a lot more about the names I give types.

2 Likes