How can a slice be owned? When a slice is essentially a pointer (with a length)

I got to this question from encountering ArrayListAligned.toOwnedSlice.

My understand of slices is that they are pointers with a known length, hence they do not own the memory they point to neither is their lifetimes connected to the memory they point to.

If this is true, how is it then possible to have a concept of an owned slice?

toOwnedSlice is named that way because before you call this function the backing memory .items slice is owned by the ArrayListAligned.

But after you call toOwnedSlice the slice you get refers to memory that is owned by you the caller of toOwnedSlice. So the function doesn’t mean to-owned-slice, rather you can think of it as to-owned (Slice) meaning that you are taking ownership of the memory that the slice points to.

The slice itself is just a value and you own that too but it isn’t dynamically allocated so it just has stack-lifetime and gets moved around by your code.

My guess is that part of why the function is named that way is to have consistency between functions like insertSlice, appendSlice, fromOwnedSlice, fromOwnedSliceSentinel, toOwnedSlice and toOwnedSliceSentinel.

Basically when you are dealing with slices the function name contains slice when you search the documentation for slice that is clearly a repeating pattern in the standard library.

So in short: you own the memory the slice points to

6 Likes

You’re correct that pointers do not own memory, “ownership” is just the abstract concept which part of your code is responsible to deallocate the memory some specific pointer points to.

When a function is “transferring ownership”, it’s basically allocating memory on your behalf and giving you the responsibility to call e.g. allocator.free on it when you’re done.

fn boxedInt(ally: std.mem.Allocator, n: isize) *isize {
  // Allocator.create gives the caller ownership of the returned pointer
  // i.e. this function is now the owner of `p`
  const p = try ally.create(isize);
  p.* = n;

  // With the pointer we transfer the "ownership" - the caller is now responsible to free it
  return p;
}

fn main() !void {
  const n = boxedInt(ally, 0); // now `main` is the owner of `n`
  defer ally.destroy(n);
}
3 Likes