Compiler says 'no field or member' but it is there!

OK, here is the complete file, it’s only just been started and I have issues already, despite the fact I have created loads of other Structs in my code and other small Zig projects, I am still new to Zig.

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

const Editor = struct {
    //; State
    buffer: std.ArrayList(i64) = undefined,
    //; Behaviour
    pub fn init(allocator: std.mem.Allocator) !Editor {
        return Editor{ .buffer = try std.ArrayList(i64).initCapacity(allocator, 80) };
    }
    pub fn append(self: *Editor, unicodeChr: i64) !void {
        self.buffer.addOne(unicodeChr);
    }
    pub fn len(self: Editor) usize {
        return self.buffer.items.len;
    }
};

test "Test initial state" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    const e = Editor.init(allocator);
    expect(e.len() == 0);
}

and the compiler error:

src/editor_core.zig:34:13: error: no field or member function named 'len' in '@typeInfo(@typeInfo(@TypeOf(editor_core.Editor.init)).@"fn".return_type.?).error_union.error_set!editor_core.Editor'
    expect(e.len() == 0);
           ~^~~~
src/editor_core.zig:34:13: note: consider using 'try', 'catch', or 'if'

Clearly, the init() call works, declared as a public fn, but the len() it says is not a member… totally and utterly bemused and confused at this point, especially as I thought I was starting to “get it” :smiley: LMAO

The only significant difference I can see is init() has no Self, and len() has a Self which is immutable and append() also has a mutable Self.

Thanks for your patience.

Your Init function can return an error. Replace const e = Editor.init(allocator); with const e = try Editor.init(allocator); and try again.

5 Likes

Legend! The final test function is now:

test "Test initial state" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    const e = try Editor.init(allocator);
    try expect(e.len() == 0);
}

This level of frustration takes me back some 20 odd years when I started learning Haskell… the real secret to progress is not sometimes the language but learning to decipher the compiler errors to the point they make sense.

I really really went back and stared hard and as usual the answer is there, my brain just couldn’t get through the noise to read the signal!

Thank you very much indeed!

1 Like

within tests you can instead use:

const allocator = std.testing.allocator;

That is an automatically setup allocator for testing, it is an GPA or DebugAllocator (in more recent versions) that is used to check for memory leaks.

So your test could look like this:

test "Test initial state" {
    const e = try Editor.init(std.testing.allocator);
    try expect(e.len() == 0);
}
3 Likes

with anonymous struct literal and decl literal this can be written like this:

pub fn init(allocator: std.mem.Allocator) !Editor {
    return .{ .buffer = try .initCapacity(allocator, 80) };
}

Also I wouldn’t recommend:

and instead use:

buffer: std.ArrayList(i64),

If somebody ends up using an ArrayList that wasn’t initialized, because it was default-initialized to undefined (and usually you would expect to get an error if you didn’t initialize an ArrayList somewhere), then they will likely trigger a Segfault/crash (in the good case) or cause the program to do unexpected/buggy things (like corrupt its own memory, change and access different pointers) (in the bad case).

Default initializing a field to undefined is almost never a good idea. Most of the time it would be better to create explicit initial empty values, or initialization functions that return an instance instead.
(I think fields that default to undefined should be reserved to internal buffers that are automatically initialized lazily once the data-structure is used (based on other fields in the struct), or for situations like that, but even there you could avoid setting a default value that is undefined on the field itself and instead set it explicitly where the instance is constructed)

2 Likes

Excellent timing, I was just starting to wonder about ‘setup’ and ‘teardown’…is there a ‘Zig Way’ for that?

Good points, thanks! Notes taken, code will be changing v. soon! :slight_smile:

Well I sometimes use something like this:

var instance: ManagerWithComplexInternalState = try .init(allocator);
defer instance.deinit();

// function is used as a post-init/pre-use step to create internal
// links or pointers that require a stable memory address for example
// for self-referential structures
instance.setup();

But I am not entirely sure what specifically you mean with setup and teardown, I think those can mean many different things depending on the context. Can you clarify?

I was just asking what the “common Zig pattern” is if a bunch of tests all have a common setup, I guess the obvious answer is to define a function and just call it but then would that function be included in a build other than test?

So much to learn! :smiley:

I think I have “seen the Way” from here:

Line 1660 shows a bunch of tests sharing an allocator, each in its own scope via {} blocks.

Thanks.

I think that is just done there so that the different array lists can all use the name list, otherwise that isn’t really necessary.

You can just define a function and call it from the test, functions that aren’t used will be ignored by the compiler, because they are only lazily analyzed/compiled when they are actually used. So it wouldn’t be included in other builds, if those don’t use the function.

Top info once again from this forum, thanks Sze!!

So if we informally used test_ as a prefix for test helpers, that should work nicely, I’ll try that in a minute.

It’s the kind of details easily missed from RTFM time, especially when learning.

Thanks again.

1 Like