Potato Blog


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.

Pattern 1: Read-Only

const immutable_config = "read_only=true";

// we want to modify immutable_config in a safe way
// toOwned() to the rescue, take ownership
var owned_config = try stringToOwned(immutable_config);
owned_config[0] = 'R';

Simply, toOwned() functions copy data from a managed buffer to an unmanaged buffer on the heap. When you call toOwned(), you’re saying: “I want full responsibility for this data going forward, and I promise not to forget to clean up after myself.”

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. We allocate and copy all the items from the new buffer, then 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.

Pattern 2: Lifetime

pub fn toOwned(self: Self) Error!?[]T {
    if (self.top > 0) {
        const items = self.buffer[0..self.top];
        if (self.allocator.alloc(T, items.len)) |slice_Ts| {
            std.mem.copyForwards(T, slice_Ts, items);
            return slice_Ts;
        } else |_| {
            return Error.OutOfMemory;
        }
    }

    return null;
}

The Gotchas

Memory Leaks: The biggest trap is forgetting to call allocator.free() on your shiny new owned data. Zig won’t hold your hand here - you asked for ownership, you got it.

Double Allocations: If you call toOwned() multiple times without freeing, you’re creating multiple copies. Each one needs its own free() call.

The Bottom Line

The toOwned() pattern is your friend when you need to graduate from “borrowing” to “owning” data. Use it when you need it, but don’t go crazy - copying data isn’t free, and neither is the overhead of tracking all those free() calls.