Skip to content

Misleading error message about changing the value of data members inside a struct method #21034

Description

@pedropark99

Zig Version

0.13.0-dev.266+0b0625ccf

Steps to Reproduce and Observed Output

I made a quick search, and I did not found an open issue reporting this problem. Also, my interpretation might be wrong. The basic idea is: when I declare a struct method that changes the value of a data member, depending on how I declare the self argument of this method, I get a misleading error message from the compiler.

As an example, let's use the Vec3 struct exposed below. This struct have two methods in it, double() and distance().
Notice that distance() use the values from the data members of the struct, but it do not alter the values of these data members. In the other hand, the double() method use the values from the data members, but it also alters the values of these data members.

The key part in this code, is the self argument present in both methods. In double() method, self is a pointer *Vec3, while in the distance() method, it is just Vec3. So, in summary, if the method alters the value of any data member, you should pass the self object by reference. But if this method only use the data members, and do not alter them in some way, you can pass self by value.

If I compile and execute this code example below, it runs fine with no errors:

const std = @import("std");
const math = std.math;
const Vec3 = struct {
    x: f64,
    y: f64,
    z: f64,

    pub fn distance(self: Vec3, other: Vec3) f64 {
        const xd = math.pow(f64, self.x - other.x, 2.0);
        const yd = math.pow(f64, self.y - other.y, 2.0);
        const zd = math.pow(f64, self.z - other.z, 2.0);
        return math.sqrt(xd + yd + zd);
    }

    pub fn double(self: *Vec3) void {
        self.x = self.x * 2.0;
        self.y = self.y * 2.0;
        self.z = self.z * 2.0;
    }
};

pub fn main() !void {
    var v3 = Vec3 {
        .x = 4.2, .y = 2.4, .z = 0.9
    };
    v3.double();
    std.debug.print("Doubled: {d}\n", .{v3.x});
}
Doubled: 8.4

However, if I change the function signature of the double() method, so that the self is marked as Vec3, instead of *Vec. Then I get a misleading error message from the compiler. Notice that the only thing that I've changed in this code example is the function signature of double().

const std = @import("std");
const math = std.math;
const Vec3 = struct {
    x: f64,
    y: f64,
    z: f64,

    pub fn distance(self: Vec3, other: Vec3) f64 {
        const xd = math.pow(f64, self.x - other.x, 2.0);
        const yd = math.pow(f64, self.y - other.y, 2.0);
        const zd = math.pow(f64, self.z - other.z, 2.0);
        return math.sqrt(xd + yd + zd);
    }

    pub fn double(self: Vec3) void {
        self.x = self.x * 2.0;
        self.y = self.y * 2.0;
        self.z = self.z * 2.0;
    }
};

pub fn main() !void {
    var v3 = Vec3 {
        .x = 4.2, .y = 2.4, .z = 0.9
    };
    v3.double();
    std.debug.print("Doubled: {d}\n", .{v3.x});
}
ZigExamples/zig-basics/vec3_struct.zig:16:13: error: cannot assign to constant
        self.x = self.x * 2.0;
        ~~~~^~

Notice that this error message is basically saying that the x data member belongs to an object that is constant, even though this x data member is inside the v3 object, which is marked as a variable object (var) in our code.

So this error message is misleading, and it do not help to identify the source of the problem, which is just the type annotation associated with the self argument. I have actually written a section in my book to explain this specific use case: https://pedropark99.github.io/zig-book/Chapters/03-structs.html#about-the-struct-state

Expected Output

I think it would be a good improvement for the compiler to do either one of these two things:

  • if an error is generated because the user is trying to alter a data member inside a struct method, print the same error message, but also prints an additional note message in the compiler with something like: "note: if you are altering the value of some data member, remember to use self: *x instead of self: x in the function signature of your method.".
  • if the compiler detects that the method is using self: x instead of self: *x, and, this method is assigning a new value to a data member, print an error message "error: to alter the value of some data member, you must use self: *x instead of self: x in the function signature of your method".

Metadata

Metadata

Assignees

No one assigned

    Labels

    error messageThis issue points out an error message that is unhelpful and should be improved.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions