Pointer to optional self in struct method

I was experimenting with some things and run into a problem.

const log = @import("std").debug.print;

const Thing: type = struct {

    data: u32,

    fn init(d: u32) Thing {
        return .{.data = d};
    }

    fn func(thing: *?Thing) void {
        //log("d = {}\n", .{thing.?.data});
        log("d = {}\n", .{thing.data});
    }
};

pub fn main() void {
    const t = Thing.init(11);
    t.func();
}

Compiler says:

$ zig build-exe test.zig 
test.zig:20:6: error: no field or member function named 'func' in 'test.Thing'
    t.func();
    ~^~~~~
test.zig:4:21: note: struct declared here
const Thing: type = struct {
                    ^~~~~~
test.zig:12:5: note: 'func' is not a member function
    fn func(thing: *?Thing) void {
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

I can not understand what is going on here. :frowning:

Shouldn’t it be ?*Thing

No, I wanted pointer to optional, not an optional pointer.

The method syntactic sugar only works for This, * This and *const This. Anything other than that, you have to do normal function call.

1 Like

If this worked, what would the method call do if the thing argument points to null?

something like this I suppose:

const log = @import("std").debug.print;

const Thing: type = struct {

    data: u32,

    fn init(d: u32) Thing {
        return .{.data = d};
    }

    fn func(thing: *?Thing) void {
        const t: Thing = thing.* orelse { 
            log("oops\n", .{});
            return;
        };
        log("d = {}\n", .{t.data});
    }
};

pub fn main() void {
    var t: ?Thing = Thing.init(11);
    Thing.func(&t);
    t = null;
    Thing.func(&t);
}

now it’s working but it’s not exactly what I wanted.
ok, nevermind.

Why do you need a pointer to optional anyways?

Good question.
I studied various ways of constructing interfaces with particular interest in method number 4 (old way, embed an interface struct into implementer struct and use @fieldParentPtr()). Obviously this method does not allow a struct to implement several interfaces since methods are passed a pointer to a particular one. Then I thought that we can construct a set of interfaces.

Failed attempt to do this
const log = @import("std").debug.print;

const TraitA: type = struct {

    impl: ?*const fn(*TraitSet) void = null,

    fn doit(t: *?TraitA) void {
//        if (*t and t.impl) |t, impl| {
        if (t.impl) |impl| {
            const ts: *TraitSet = @fieldParentPtr("a", t);
            impl(ts);
        } else {
            log("A default implementation\n", .{});
        }
    }
};

const TraitB: type = struct {

    impl: ?*const fn(*TraitSet) void = null,

    fn doit(t: *?TraitB) void {
        if (t.impl) |impl| {
            const ts: *TraitSet = @fieldParentPtr("b", t);
            impl(ts);
        } else {
            log("B default implementation\n", .{});
        }
    }
};

const TraitSet: type = struct {
    a: ?TraitA = null,
    b: ?TraitB = null,
};

/// implementations

const ThingOne: type = struct {
    data: u32,
    ts: TraitSet,

    fn init(d: u32) ThingOne {
        return .{
            .data = d,
            .ts = .{
                .a = .{.impl = myAImpl},
            },
        };
    }

    fn myAImpl(ts: *TraitSet) void {
        const me: *ThingOne = @fieldParentPtr("ts", ts);
        log("I can do A with {}\n", .{me.data});
    }
};

const ThingTwo: type = struct {
    data: u32,
    ts: TraitSet,

    fn init(d: u32) ThingTwo {
        return .{
            .data = d,
            .ts = .{
                .b = .{.impl = myBImpl},
            },
        };
    }

    fn myBImpl(ts: *TraitSet) void {
        const me: *ThingTwo = @fieldParentPtr("ts", ts);
        log("I can do B with {}\n", .{me.data});
    }
};

pub fn main() void {
    const t1 = ThingOne.init(11);
    var a = t1.ts.a; a.doit();

//    const t2 = ThingTwo.init(22);
//    t1.ts.a.doit();
//    t1.ts.b.doit();
//    t2.ts.a.doit();
//    var m = t2.ts.b.?; m.doit();
}
const TraitSet: type = struct {
    a: ?TraitA = null,
    b: ?TraitB = null,
};

These have to be optional otherwise adding more interfaces to the set would break existing implementers.

1 Like

I don’t really see the problem. Sure you can’t have two interfaces that point at the same function, but apart from that you can you just do it like this:

const SeveralInterfaceImpl: type = struct {
    data: u32,
    a: TraitA,
    b: TraitB,

    fn init(d: u32) SeveralInterfaceImpl {
        return .{
            .data = d,
            .a = .{.impl = &myAImpl},
            .b = .{.impl = &myBImpl},
        };
    }

    fn myAImpl(a: *TraitA) void {
        const me: *SeveralInterfaceImpl = @fieldParentPtr("a", a);
        ...
    }
    fn myBImpl(b: *TraitB) void {
        const me: *SeveralInterfaceImpl = @fieldParentPtr("b", b);
        ...
    }
};
1 Like

I wanted all interfaces to be collected in a struct and pass it to implementing functions instead of a pointer to concrete interface.
Ok, it’s another topic already.
I will mark @LucasSantos91 answer as solution.
As to my affair with interfaces - if I will be able to do what I wanted I will create new topic.

2 Likes

Yep, that was some kind of glitch in my head, sorry.
Have no idea now what made me think in a so weird way.