From conversation w/ @spex_guy, who kindly explained the details below.
- 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 typeT
and compile time known lengthN
.[N]T
is equivalent toextern struct { v0: T, v1: T, ..., vN: T }
, so it will copy all values by value.[_]T
: same as[N]T
but theN
is inferred by looking at the right side of the expression:var foo: [_]i32 = .{ 1, 2, 3}
is the same asvar 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 tostruct { 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
- its
?T
is equivalent tostruct { valid: bool, payload: T }
, so it will copy the payload by value.ErrSet!T
is equivalent tostruct { 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
- 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 is a copy, use a *
to get a reference:
// copy
var a = b;
// reference
var c = *b;