std: rework ArenaAllocator.State.promote to mutate the original state pointer #13409
Labels
proposal
This issue suggests modifications. If it also has the "accepted" label then it is planned.
standard library
This issue involves writing Zig code for the standard library.
Milestone
Note: I think I'm okay to make this proposal based on what Vexu said in #13244 (comment), since this is a small
std
change which doesn't impact compiler implementation and removes a potential footgun.Motivation
std.heap.ArenaAllocator
rightly suggests that when the child allocator is known through some other means, it can be more efficient to store a value of typeArenaAllocator.State
, andpromote
it when it needs to be used. In theory, this is an elegant and simple pattern: however, the way it is currently designed is a bit unintuitive.Suppose we've created an arena state and stored it, and now want to do some allocation. The correct code for this is as follows:
The last line here is what I'm concerned with.
promote
simply creates an actualArenaAllocator
with the state that was passed into it; that means if you still want the state to be valid (as you normally would in this case) you have to copy it back afterwards. This is easy to forget or not realise, and missing this would lead to confusing state corruption on future allocations which could be quite tricky to debug.This is made worse by the fact that
state
isn't updated until afterdoSomethingWith
returns. That means if anything called withindoSomethingWith
itself makes use ofstate
, all bets are off; we'll again get a corrupted state with overlapping allocations. This is an obvious footgun.Proposal
Replace
std.heap.ArenaAllocator.State.promote
with the following:Then, the interface can be used as e.g.
doSomethingWith(state.promote(allo).allocator())
entirely safely, since every use of the allocator fromPromoted.allocator
automatically copies to the underlying state.This makes the interface technically more restrictive, since
promote
now needs a mutable pointer to the underlying state. However, there are only two cases where you wish to usepromote
: to use the allocator, or to deinit the arena. In the former case, you almost certainly have a mutable state, since that's necessary for theState
to really be useful: if for some reason you have aconst
state which you're going to use locally and then deinit, it's only one extra line to put it into avar
or to just init an actual arena wrapping it. When deiniting, the logic is much the same: you almost certainly have a mutable pointer anyway. (Plus, it could be argued thatArenaAllocator.deinit
should take a mutable pointer likeArenaAllocator.allocator
- related #9814).Performance Implications
The only real disadvantage I see to this proposal is that it almost certainly has slightly worse performance characteristics compared to status quo. This solution adds a level of indirection between the allocator and the actual arena state, and copies to the original state on every allocation (status quo has the advantage that the programmer chooses when to copy the state back, and so can do it only when absolutely necessary).
I haven't done any benchmarking on this code. However, its overhead should be pretty negligible, and I believe is an acceptable loss in exchange for this improvement in safety and ease of use.
The text was updated successfully, but these errors were encountered: