简体   繁体   中英

Am I using ArrayLists wrong in Zig when performing simple variable assignment changes function behaviour?

I have been doing Advent of Code this year, to learn Zig, and I discovered something during Day 5 that really confused me. So: mild spoilers for Day 5 of Advent of Code 2022, I guess?

I decided to implement my solution to Day 5 as an ArrayList of ArrayLists of U8s, which has ended up working well. My full solution file is here (probably terribly un-idiomatic Zig, but we all have to start somewhere).

As part of my solution, I have a function, which I call moveCrates, on a struct which wraps my arraylist of arraylists.

The relevant part of the struct declaration looks as so:

const BunchOfStacks = struct {
    stacks: ArrayList(ArrayList(u8)),
    ...

This function is here , and looks like this:

    fn moveCrates(self: *BunchOfStacks, amount: usize, source: usize, dest: usize) !void {
        const source_height = self.stacks.items[source - 1].items.len;
        const crate_slice = self.stacks.items[source - 1].items[(source_height - amount)..];
        try self.stacks.items[dest - 1].appendSlice(crate_slice);
        self.stacks.items[source - 1].shrinkRetainingCapacity(source_height - amount);
    }

You can note that I refer 3 times to the source list by the very verbose reference self.stacks.items[source - 1] . This is not how I first wrote this function. I first wrote it like below:

    fn moveCrates(self: *BunchOfStacks, amount: usize, source: usize, dest: usize) !void {
        var source_list: ArrayList(u8) = self.stacks.items[source - 1];
        const source_height = source_list.items.len;
        const crate_slice = source_list.items[(source_height - amount)..];
        try self.stacks.items[dest - 1].appendSlice(crate_slice);
        source_list.shrinkRetainingCapacity(source_height - amount);
    }

But this second form, where I make a local variable for my convenience, DOES NOT GIVE THE CORRECT RESULTS, It compiles fine, but seems to always point source_list to the same internal ArrayList(u8) (whichever one it first picks) regardless of what the value of source is. This means that the test example produces incorrect output.

This function is called within a loop, like so:

    while (instructions.next()) |_| {
        // First part is the verb, this is always "move" so skip it
        // Get the amount next
        const amount: usize = try std.fmt.parseInt(usize, instructions.next().?, 10);
        // now skip _from_
        _ = instructions.next();
        // Now get source
        const source: usize = try std.fmt.parseInt(usize, instructions.next().?, 10);
        // Now skip _to_
        _ = instructions.next();
        // Now get dest
        const dest: usize = try std.fmt.parseInt(usize, instructions.next().?, 10);

        var crates_moved: usize = 0;
        while (crates_moved < amount) : (crates_moved += 1) {
            try stacks_part1.moveCrates(1, source, dest);
        }
        try stacks_part2.moveCrates(amount, source, dest);
    }

Ultimately, as you can see, I have just avoided making variable assignments in the function and this passes the test (and the puzzle).

I have checked for issues in the zig repo that might be related, and cannot find anything immediately obvious (search used is this ). I've looked on StackOverflow, and found this question , which does have some similarity to my issue (pointers seem a bit whack in while loops), but it's not the same.

I've scoured the zig documentation on loops and assignment, on the site , but don't see anything calling out this behaviour specifically. I'm assuming I've either completely misunderstood something (or missed something that isn't well documented), or this is a bug — the language is under heavy active dev, after all.

I'm expecting that an assignment like I perform should work as expected — being a simple shorthand to avoid having to write out the repeated self.stacks.items[source - 1] , so I'm hopeful that this is something that I'm just doing wrong. Zig version is v0.11.0-dev.537+36da3000c

An array list stores items as a slice (a pointer to the first item plus length). When you do

var source_list: ArrayList(u8) = self.stacks.items[source - 1];

you make a shallow copy of the array list. You might be confused by your knowledge of some higher-level languages where this would've copied a reference to an object of the array list, but in Zig this isn't the case. In Zig everything is a "value object".

When you call shrinkRetainingCapacity it changes the length property of the items slice, but the changes are done to the local copy of the array list. The "real" array list, that is stored in another array list, remain unaffected.

TL;DR You need to use a pointer :

var source_list: *ArrayList(u8) = &self.stacks.items[source - 1];

This fixes the failing test.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM