When a structure calls a method that requires `self` to be a pointer, the parameter is automatically and implicitly converted to a pointer. Are there any irreplaceable uses for this?

const Counter = struct {
    v: u8,
    fn increment(self: *Counter) void {
        self.v += 1;
    }
};

test Counter {
    var c: Counter = .{ .v = 0 };
    (&c).increment();
    try std.testing.expectEqual(1, c.v);
    c.increment();
    try std.testing.expectEqual(2, c.v);
}

A key feature of zig is that when self is a struct and a method requires self to be a pointer, self.method can be called directly, rather than (&self).method.
This approach may be convenient for a newly initialized struct. However, it can lead to misuse when copying content. It can cause users to forget that the API they are copying is based on pointers, not entire structs. Users should obtain a pointer to the copied object, but instead directly copy the struct, causing subsequent use to deviate from their intended purpose without notice.

For example, std.Io.Writer is an example. After directly copying an interface, users can directly call interface.method(), forgetting that the copied object is not a pointer, and that the structure it uses requires a pointer, potentially leading to misuse.

var interface = writer.interface;
interface.print("hello, world", .{});
interface.flush();

If users were forced to use this here, they would likely reflect and realize their mistake:

var interface = writer.interface;
(&interface).print("hello, world", .{});
(&interface).flush();

I originally thought that a key reason for this feature was generic functions. Sometimes generic functions are passed values, and sometimes pointers. This feature bridges the gap between pointers and values, allowing their methods to be executed consistently.

But after some reflection, I feel this isn’t entirely tenable. If the API executed within a generic function requires some parameters to be pointers and some to be values, then this distinction should be reflected in the generic function’s parameters. Generic function parameters shouldn’t be passed values ​​and then converted to pointers internally for execution, which could easily lead to errors.
Is there something I’m missing where strong support for this feature would make coding extremely difficult if it didn’t exist?

some related links:

i dont think

var interface = writer.interface;
(&interface).print("hello, world", .{});
(&interface).flush();

would cause people to realise their mistake, that requires knowing the interface uses @fieldParentPtr. Even in the case where someone who does know makes this mistake, I dont think this would cause them to realise.

Simply because the functions taking a self pointer does not indicate @fieldParentPtr is being used.

1 Like

This would also be taken care of by Proposal: Pinned Structs · Issue #7769 · ziglang/zig · GitHub, which would basically mark a struct type as ‘not copyable’, so you’d be forced to take a pointer.

IMHO this is also a grey area where Zig doesn’t quite know if it wants to be more like C or more like C++ :wink:

E.g. in C your only choice to enforce correct usage is to wrap everything in a function API and keep any struct types which might be ‘misused’ hidden behind this function API (e.g. in C the primary abstraction mechanism is a library’s function API, not the type system).

C++ OTH gives you some extra syntax to avoid misuse (like private struct members).

Tbh I’m not sure which course Zig should take. Many stdlib APIs seem too high level for what the language syntax provides (they are too “C++/C#/Rust-like” in some areas), and now the question is: should the stdlib APIs become lower level, or should more semantics be added to the type system?

C answers this dilemma by basically not providing a useful stdlib at all and delegating all those decisions to the user, which in my book is ok, but maybe different from the expectations of most other programmers coming to Zig…

2 Likes

This may need to be discussed in conjunction with another point of mine, namely that “when an API related to a structure requires the use of a pointer rather than the entity itself, it indicates that the structure was not designed for copying, and thus copying should be done with caution.” It’s not just the structures that use @fieldParentPtr that are unsuitable for copying.

What I am more concerned about is whether “implicit pointer conversion” has an irreplaceable role, as this is, after all, an implicit matter.

This proposal gives me the feeling of being another direction of “overly strong”.
Some contents cannot be copied individually, but sometimes it is acceptable to copy them together with the entire parent structure, including those structures that use @fieldParentPtr internally. However, some structures contain self-references, and as a whole, they should not be copied, but copying part of them is not an issue.
My current attitude towards this proposal is still reserved (as discussed recently within this proposal). I am currently more inclined to let users notice the differences in design between different interfaces, allowing users to realize through the use methods of the interfaces that “the usage of this interface is different from that of Allocator, and this interface is not suitable for direct copying,” which should help reduce some simple usage errors, while still allowing the possibility of partial copying if users insist.

1 Like

I think it’s simply too small of a problem to cater to. Even if the status quo wasn’t what it is.

I wouldn’t be surprised if zig used to do what you are suggesting.