Skip to content

Instantly share code, notes, and snippets.

@ssteinbach
Last active October 21, 2022 16:25
Show Gist options
  • Save ssteinbach/9e3b2c10dbd4e106585ac58473f4aef9 to your computer and use it in GitHub Desktop.
Save ssteinbach/9e3b2c10dbd4e106585ac58473f4aef9 to your computer and use it in GitHub Desktop.
Zig Memory Mental Model

Zig Memory Mental Model

From conversation w/ @spex_guy, who kindly explained the details below.

Types & Memory:

  • A type defines a range of memory, and may contain pointers to other ranges.
  • All copies of any type are shallow, copying only the immediate range of the type, and not anything it points to.
  • *T is a value of only eight bytes, so copying it does not copy the memory it points to.
  • [N]T: contiguous buffer of type T and compile time known length N.
    • [N]T is equivalent to extern struct { v0: T, v1: T, ..., vN: T }, so it will copy all values by value.
    • [_]T: same as [N]T but the N is inferred by looking at the right side of the expression: var foo: [_]i32 = .{ 1, 2, 3} is the same as var foo: [3]i32 = .{ 1, 2, 3 }
    • if you return a value of type [N]T, that is a value type and will be returned by value
  • []T: a "slice" whose length is not known at compile time. has a .len and .ptr.
    • []T is equivalent to struct { len: usize, ptr: [*]T }
    • ...therefore a return type of []T will copy the len and pointer but not the data it points to.
  • std.ArrayList(T) is a []T but with extra machinery to add more stuff on the end, allocating when necessary
    • its .items is a []T
    • ArrayList.{append/appendSlice/etc} will copy data during the append
  • ?T is equivalent to struct { valid: bool, payload: T }, so it will copy the payload by value.
  • ErrSet!T is equivalent to struct { error: ?ErrSet, payload: T }, so it will copy the payload by value.
  • allocator.dupe(...) can be used to copy the contents of something from the stack into an allocated (IE longer memory life) chunk of memory

More notes/Rules of Thumb:

  • Parameters may be passed by value or reference. Code must be written so that either approach is valid.
  • When writing function arguments, if the address is actually important, use *const T, otherwise pass by value.
  • Struct return values will elide a copy between the x = foo() at the call site and the return .{} expression inside the function.
  • For something like your failing test, any time you use & you are creating a graph cut that won't be copied across. Whenever you use it you should consider the lifetime of the thing you are addressing. Temporaries and locals live only until the end of the containing scope, so if you need it to last longer you need to tie it to an object with a longer lifetime. This means either heap allocating, or having an object with longer life passed in by the user.
  • Slicing will also create a graph cut if you are slicing an array. Slicing a slice gives you a peer, where both the new slice and the source slice are separate from each other and from the underlying data.
  • you can use = undefined if you are knowingly not initializing memory
  • if a struct has fields that require allocation, a .init() method typically is provided that does that
  • if you need to do a fast initialize without having init() do an allocation, you can initialize the struct directly into whatever already allocated memory you might have via .{ } syntax, rather than calling .init()

Assignment

Assignment is a copy, use a * to get a reference:

// copy
var a = b;
// reference
var c = *b;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment