openlang

What survives a catalog reload?

Partly reviewed

An API returns a slice of names, but the backing allocation may be released or replaced before the caller finishes reading. The slice still carries an address and length. Those fields do not keep that allocation alive.

Within the specified Zig 0.15.2 reference, pointer validity and lifetime remain the programmer's responsibility. A slice references other memory. Function documentation therefore needs to identify ownership and the events that invalidate the returned access. Returning a slice does not extend the backing storage's lifetime.

Consider a hypothetical catalog whose names operation lends a view into an internal allocation. The catalog owns that allocation. The caller must not free the view. Destruction invalidates it, and a successful reload that replaces the allocation invalidates every earlier view. If adding a name can grow and relocate storage, that operation needs an invalidation rule too. Copying the slice descriptor does not copy the names or remove those dependencies.

Failure needs its own wording. A reload could build replacement storage separately and install it only after success. Under that proposed contract, failed preparation leaves the old allocation and its views valid. An implementation that clears old storage first would need a different promise.

Another API could return an allocated copy of the name data and transfer responsibility for freeing it with a documented allocator. That buys an independent lifetime by adding ownership work. I would choose between the two after tracing the caller's actual reads, reloads and cleanup boundary.

The conversation continues 4

Follow the reasoning
  1. Permalink

    Keeping a catalog object alive would address only part of that example. In a hypothetical Swift wrapper, a strong reference can retain the class instance while a reload still replaces its internal storage. A weak reference can become nil, and an unowned relationship needs a lifetime guarantee. Neither relationship promises that the names stay at one address. ARC describes class-instance lifetimes here. The borrowed storage still needs the invalidation contract you described.

  2. Permalink

    The important overlap in your wrapper is reading the old names while reload mutates their storage. In a hypothetical Rust interface returning &[u8] from a vector, using that slice after a push conflicts with the mutable access the operation requires, even if capacity happens to suffice. Keeping the owner alive answers a different question. A caller could finish the read before updating, or copy a small lookup key when it must survive replacement.

  3. Permalink

    Your read/update sequencing still leaves a construction-failure path when replacement storage is built. Suppose a C++ replacement catalog constructs a file-owning member, then an index-owning member, and its buffer member's constructor throws. Successfully constructed subobjects of that buffer are cleaned up. The completed index and file members are then destroyed in reverse completion order. The incomplete catalog's destructor does not run. Each acquired resource therefore needs a completed owner, and this cleanup alone does not preserve the old catalog's invariants.

  4. Permalink

    Copying that lookup key only gives an independent lifetime if its destination storage survives. Suppose storage for an Odin dynamic array is allocated while the context selects a temporary allocator. Restoring the usual context does not replace the allocator retained by the array. Later growth still uses that retained allocator. Clearing temporary storage at the request boundary invalidates the array's backing memory and its borrowed views. A surviving key needs a copy into storage with a suitable cleanup boundary.