Struct method reference to `self` by pointer or not?

Kinda a basic question but a little context:

I’m parsing a UDP packet as a struct called Packet that copies and allocates the bytes into buf like:

const Self = @This();

buf: []const u8 = undefined,
header: *Header = undefined,

pub fn initBuffer(allocator: Allocator, src: []const u8) !Self {
    const buf = try allocator.alloc(u8, src.len);
    errdefer allocator.free(buf);
    @memcpy(buf[0..], src);
    const packet: Self = .{
        .buf = buf,
        .header = @ptrCast(@alignCast(buf[0..min_packet_size])),
    };}

See full source: Github: Packet.zig

The header (first 36 bytes) goes into an internal packed struct called Header.

I have some methods on the struct to read/parse the header values.

I understand for primitive values I can use self:

/// Packet byte length (header + payload)
pub fn size(self: Self) u16 {
    return self.header.size;
}

and the compiler optimises whether or not self is copied, I don’t need to care, right?

My question is do I need to care when returning a pointer? For example:

/// Device MAC address
pub fn target(self: Self) *const [6]u8 {
    return std.mem.asBytes(&self.header.target)[0..6];
}

Do I use self: Self or self: *Self?

I think the first is correct because I’m not mutating self and self.header is already a pointer to allocated memory. I’m not returning a stack pointer, right?

Thanks!

in this case it doesnt matter since you are accessing a field which itself is a pointer and doing nothing else.

no, zig previously did that, i dont recall if it still does that, but if it does it will probably be removed soon. That optimisation is simply very troublesome to do since the compiler has no knowledge of the programmer’s intentions

6 Likes

As long as the returned pointer is valid outside the function (which a pointer to self is not but the pointer self.header is) this seems to be fine.

I personally always try to avoid passing self by value and use *const Self (or *Self) instead (at least if self is not something like an enum or similar), because this also helps to avoid problems like this.

5 Likes

thanks for the additional info, that’s good to know

The optimization is going to be removed, so follow the same rule as C: if a value is small enough (around 3 pointers), pass by value, otherwise by pointer.
That is assuming that a value is valid in the first place. If you’re returning a pointer to something inside the parameter, you need to pass a pointer.

4 Likes