Skip to content

Conversation

kryesh
Copy link

@kryesh kryesh commented Oct 12, 2025

This PR adds a "CheckPoint" struct that can be used to save a position for a Bump that can be later reset to, allowing for partial resets of the allocator.

I have a use case where I build a collection from many complex structs that use a bump allocator, however these structs must pass a filter before being added to the collection, In cases where very few events make it through the filter I found that the bump allocator would grow to be massive due to leaked allocations from rejected structs.
After testing using a second bump allocator as scratch space that resolved the memory leak issue however caused a ~20% performance hit due to the extra memory copies, this PR solves the problem by taking a checkpoint before building the complex struct, then performing a partial reset if the struct is rejected.

Not sure if this kind of conditional logic is used elsewhere but figured I'd make a PR anyway in case it's useful, happy to update docs etc if needed.

Copy link
Owner

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

I do want to add a similar API to bumpalo, but I think that the exact details here are not what we want. I think the API as proposed here is not quite powerful enough to satisfy most uses cases that people have for checkpoints, particularly the ability to do stuff like the following:

let mut bump = Bump::new();

let x = bump.alloc(5);

let checkpoint = bump.checkpoint();

let y = bump.alloc(10);
foo(x, y); // okay

drop(checkpoint); // or `bump.reset_to_checkpoint(checkpoint)` or whatever...

bar(x); // still okay

// bar(y); // NOT OKAY, should cause borrow checker errors

The API in this PR will not allow x to continue to be used across sub-arenas/checkpoints. And to be fair, I don't the exact pseudocode above can be implemented by any safe+sound Rust API because without changing some API details we have the conflicting requirements that (a) the bump has live shared borrows from earlier allocations and (b) we must have exclusive access at reset points to act as a barrier and ensure that none of the allocations within the nested scope/checkpoint remain live. The only way I am aware of resolving this is to separate the underlying storage and the allocator/checkpoint/scope so that the allocator/checkpoint/scope has a lifetime and borrow of the storage and then you can make sub-allocators/checkpoints/scopes with sub-lifetimes. And each allocation would have the allocator's/checkpoint's/scope's lifetime, not the lifetime of the underlying storage.

There is some existing discussion of this feature in #105, and a proposed API, although it would be a breaking change. It might be possible to come up with something that is semver compatible though, even if not quite as nice API-wise. Anyways, I suggest reading up on that thread and grokking the API sketch in there before continuing any further down this line of investigation.

…fetimes for temporary allocations that may be reset
@kryesh
Copy link
Author

kryesh commented Oct 22, 2025

Thanks for the feedback!
I had a read through #105 and came up with run_scoped as a minimal adaptation of my current code.

I made checkpoint and the associated methods private, then added a run_scoped method that provides temporary access to the allocator. If the function returns Some(T) then the result can persist on the Bump, but if the function returns None then the checkpoint is restored and the position of the allocator is reset.

The API could definitely be polished up a bit, maybe adding a Result variant as well so it's not just Option but I think it addresses the potential issues with the borrow checker.

As for functionality, the run_scoped method was sufficient for my use case at least - you can see the update for the changed api here: https://codeberg.org/Kryesh/crystalline/commit/da364cd0781bfbb88632ce1ec8c4e1a60a43737f?style=split&whitespace=show-all
My app probably shouldn't be used as a reference however since I do bunch of casts to 'static

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants