I have following Zig code : `fn decode() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer gpa.deinit();
const allocator = gpa.allocator();
var list = std.ArrayList(u8).init(allocator);
defer list.deinit();
try list.append('c');
std.debug.print("{c}\n", .{list.items[0]});
}` to my understanding, that should be the way to initilize an Array list, but i get the error “struct ‘array_list.Aligned(u8,null)’ has no member named ‘init’.” What is the cause of this? Thank you for your help
ArrayList used to be “managed” by default - “managed” indicates that it stores an allocator as a struct field, and uses that same allocator in methods that allocate and deallocate memory.
Now in 0.15.1, it’s “unmanaged” by default - so, each function that would allocate or deallocate memory takes an allocator as an argument, and the struct itself no longer stores an allocator as a parameter.
Because of this, it’s now unnecessary to even call a method at all to initialise an ArrayList - so, the correct way to initialise it now is to use the .empty constant.
All this is to say that your code from 0.15.1 onward should look like this:
What is the difference between these two? Is .empty better than .{}? I guess .empty can encapsulate specific non-zero initialization values for each field?
var list: std.ArrayList(u8) = .empty;
vs var list: std.ArrayList(u8) = .{};
AFAIK the = .xxx; form for initialization is the new convention since it allows to have different sets of default values, and each set has a name which makes the code more readable.
Downside (or arguably: upside, depending on situation) compared to struct-declaration-default-values is that you can’t inject your own overrides into the initialization (e.g. if you have 50 struct items with default values, but want to have 10 of those to have non-default values you can’t do that with the form = .xxxx (at least not without splitting the initialization into multiple assignments)).
To clarify the upside that @floooh glossed over, being able to set some fields but leave others default can, depending on the type e.g. ArrayList, break the invariance of fields e.g. ArrayList{.capacity = non_zero_number} which causes items to be defaulted to a zero length slice which afaik has a pointer that’s undefined, using this will result in unchecked illegal and undefined behaviour.
So for ArrayList, and other types, default field values are unsafe to use, so it is recommended that fields with invariance do not get default values, preferring a declaration as an alternative. The addition of decl literals made this practice much nicer to use.
Adding to @tholmes post as to why managed containers are being replaced by unmanaged ones:
Having an extra field is more complicated than not having an extra field, so not having it is the null hypothesis. What pattern does having an allocator field allow that not having one doesn’t?
avoiding accidentally using the wrong allocator
convenience when you need to pass an allocator also
But there are downsides:
worse method function signatures in the face of reservations
lost inability to statically initialize
extra memory storage cost, particularly for nested containers
The reasoning goes like this: the upsides are not worth the downsides. Also, given that the correct allocator is always handy, and incorrect use can be trivially safety-checked, the simplicity of only having one implementation is quite valuable compared to the convenience that is gained by having a second implementation.
Also note that std.heap.GeneralPurposeAllocator is std.heap.DebugAllocator