To Owned
Why toOwned()?
- When working with borrowed data (reference to a managed buffer) that may not live long enough and you want to extend the lifetime of that data.
- When you want to take ownership of a read-only buffer to mutate.
- When interacting with functions that require ownership of a buffer.
- To ensure a working copy of a buffer is safe from external mutations.
Simply, toOwned() functions copy data from a managed buffer to an unmanaged buffer on the heap.
Let’s Build Something
I’m going to extend a stack data structure from a previous post with a toOwned() method. Because who doesn’t want another stack example?
In the code below we return null if our top pointer is at the base of the stack buffer since this tells us there are no items. Otherwise, we create a new buffer holding all the items up to the top of the stack buffer called items. We do this because our stack buffer may contain items beyond the top and we want to make sure to stay within the bounds.
We then allocate and copy from our items on the stack to a newly allocated buffer and return the slice. This ensures ownership is transferred to the caller when the scope ends. This also means the caller is responsible for
cleaning up and must call allocator.free() on the returned slice.
pub fn toOwned(self: Self) std.mem.Allocator.Error!?[]T {
if (self.top == 0) {
return null;
}
const items = self.buffer[0..self.len];
if (self.allocator.alloc(T, items.len)) |slice_Ts| {
std.mem.copyForwards(T, slice_Ts, items);
return slice_Ts;
} else |_| {
return std.mem.Allocator.Error.OutOfMemory;
}
}
The biggest trap is forgetting to call allocator.free() on your owned data. Use toOwned() when you need it, but remember copying data isn’t free, and neither is the overhead of tracking all those free() calls.