Noob question: reassign struct field at runtime

Hello again,

after starting my Zig journey some days ago I already wrote some code but now I’m facing a challenge regarding assigning “strings” to a struct field at runtime. As already stated in my previous question coming from Rust it was not necessary to think about such eventually memory related things much.

In general, I have a struct defined in my library which holds some fields containing “strings” ([]const u8). When used at runtime, there is a function which loops over an array of strings using next() method. At every round the current item is assigned to the struct field for some operations. This means in the next turn the next value is (re)assigned to the same field.

What is the most idiomatic way in Zig to handle this?

Take this as example code:

const MyStruct = struct {
    field_1: []const u8,
    field_2: bool,

    fn assignStr(self: *MyStruct, str: []const u8) void {
        self.*.field_1 = str;
    }
}

// Initialize struct
var my_struct = MyStruct { .field_1 = "", .field_2 = false };

// Some kind of iterator like ArgIterator
var iterator = try std.process.ArgIterator.initWithAllocator(allocator);

// Assigning value to field_1 in struct
while (iterator.next()) |str| {
    my_struct.assignStr(str);
    // Do some stuff with the field
}

The example is of course strongly simplified (and written freely from my mind and therefore not tested, since I don’t have the opportunity atm) and might not make sense regarding some other aspects of Zig I don’t know much about. Plus, in such a simple example, I know, there is no need for storing the string in a struct field, but in the real code it seems to be correct (I’ll make the repo public and present it in this forum when the code is a little bit more evolved). But I hope the central question is clear.

Since in the case of CLI args the length of the strings can vary, I’m unsure if there is something missing, especially regarding memory management. In Rust thats no big deal, because its handled in the background.

The ArgIterator already allocates memory for the strings it returns. But do I need some other action on those strings when I assign them to the struct field? Or might be a different type better suited when defining the struct (e.g. std.ArrayList(u8))?

Sorry if the solution is very obvious, but as mentioned, I’m still at the beginning and a little bit overwhelmed; but nevertheless already loving it to work with Zig :smiling_face_with_sunglasses:

Hi lukeflo. I have a few follow up questions. Is this something that is causing an error right now? Or is it something that you are unsure about how to handle correctly? I think It’s the latter, but if it’s the former, please post what error’s you are getting.

I don’t see anything wrong with the code you have posted. (Other than you might want to free up the memory from the ArgIterator, but that was probably just an oversight in your example).

Remember that a [] const u8 is a slice, which is just fat pointer. Indeed you can think of [] as just syntax for a fat pointer. So in your struct field you are just storing the pointer plus the length. Every time you reassign to field1, the pointer and length are just updated to point to the new string. The key issue to remember when working with slices, is that they don’t hold any memory on their own, they just point to it (you can kind of think of it as they don’t own any memory). So the you have to think about the Iifetime of the underlying memory. In this case there’s no problem, as the memory is allocated on the heap, and is never being freed.

No other action needed. The strings have a place in memory (allocated by the ArgIterator). Switching the type to an ArrayList would be worse, IMO as then you are copying the data over unnecessarily. There may be reasons where such a copy is desirable, but i can’t see any from the example you posted.

2 Likes

No, no errors at the moment. Just a question of general understanding and wanted to catch possible memory errors before the occur. Yes, the iteratoras memory is freed later in my code.

Thats what I was unsure about (but although how I understand it, and now happy that it was right). I mainly got confused by the syntax probably since Rust always uses ampersand to mark a ref/pointer.

Thanks for your detailed answer. It really helps me understand those things and adapt this kind of thinking.

1 Like

this is equivalent to:

self.field_1 = str;

the latter is more usual in Zig.

3 Likes

Ok, thanks for the hint. I wasnt sure if its always necessary to explicitley dereference on assignments.

I want to point out that rust is the weird one here for having the concept of unsized types. [u8] (notice the lack of &) is a standalone type, but it just the vague concept of contiguous u8’s it has no length or location, thus it has to be referred to via a reference &[u8].

I am unsure exactly why this concept exists in rust, but I think it has to do with the way they support heap allocation. Box<[u8]> in particular. It wouldn’t make sense for it to be Box<&[u8]> as that would semantically allocate the reference + length, not the underlying data.

1 Like

I think it’s tech debt from early on in the language development when heap allocation / boxing was part of the language rather than library semantics and denoted by @.