How to improve the API?
⚓ Rust 📅 2026-03-25 👤 surdeus 👁️ 3Hello, I am prototyping an API for the bump allocator. I like what I have, but it would be better if alloc won't require token - new may require it instead. I believe that if there would've been a way to express "another type of borrow", this would have been possible.
Currently we have that &'a T will ensure that T is alive for 'a and no &mut T can be taken, while &'a mut T also ensures that no &T can be taken. It would benefit from another type - something that ensures T is alive for 'a, but won't prevent neither & nor &mut - it won't conflict with exclusivity of &mut because it won't borrow anything, just ensure that the value is alive.
If something like that would've been possible, alloc may have used it, scope would have taken &mut self and previously allocated data would have still been accessible.
Or maybe there is an alternative way to keep the allocator ergonomic?
use std::marker::PhantomData;
use generativity::{Guard, make_guard};
struct InvariantLt<'id>(PhantomData<fn(&'id ()) -> &'id ()>);
struct Bump<'id>(InvariantLt<'id>);
struct SubBump<'a, 'id>(PhantomData<&'a ()>, InvariantLt<'id>);
struct Token<'id>(Guard<'id>);
impl<'id> Bump<'id> {
fn new() -> Self {
unimplemented!()
}
// Take `&Token<'id>` to ensure no `SubBump` is present.
fn alloc<'a, T>(&'a self, token: &Token<'id>) -> &'a mut T {
unimplemented!()
}
fn scope<'a, 'sub_id>(&'a self, token: &'a mut Token<'id>) -> SubBump<'a, 'sub_id> {
unimplemented!()
}
}
impl<'id> SubBump<'_, 'id> {
// Take `&Token<'id>` to ensure no `SubBump` is present.
fn alloc<'a, T>(&'a self, token: &Token<'id>) -> &'a mut T {
unimplemented!()
}
fn scope<'a, 'sub_id>(&'a self, token: &'a mut Token<'id>) -> SubBump<'a, 'sub_id> {
unimplemented!()
}
}
fn main() {
make_guard!(id);
let mut id1 = Token(id);
make_guard!(id);
let mut id2 = Token(id);
let bump = Bump::new();
let foo = bump.alloc::<i32>(&id1);
// Not allowed (cryptic error):
// - Misleading error message: temporary value dropped while borrowed
// - "Real" cause: `bad_id` isn't `id1` that is associated with `bump`.
// make_guard!(id);
// let mut bad_id = Token(id);
// let _ = bump.scope(&mut bad_id);
let checkpoint_1 = bump.scope(&mut id1);
let bar = checkpoint_1.alloc::<i32>(&id2);
// Not allowed: cannot borrow `id1` as immutable because it is also borrowed as mutable
// let _ = bump.alloc::<i32>(&id1);
// Not allowed: cannot borrow `id1` as mutable more than once at a time
// let _ = bump.scope(&mut id1);
make_guard!(id);
let mut id3 = Token(id);
let checkpoint_2 = checkpoint_1.scope(&mut id2);
let baz = checkpoint_2.alloc::<i32>(&id3);
let use_foo = (*foo, *foo = 1);
let use_bar = (*bar, *bar = 1);
let use_baz = (*baz, *baz = 1);
drop(checkpoint_2);
{
let branched = checkpoint_1.scope(&mut id2);
let qux = branched.alloc::<i32>(&id3);
let use_foo = (*foo, *foo = 1);
let use_bar = (*bar, *bar = 1);
// Not allowed (cryptic errors):
// - E1: cannot move out of `checkpoint_2` because it is borrowed
// - E2: cannot borrow `id2` as mutable more than once at a time
// - "Real" cause: `checkpoint_2` is dead, `baz` borrowed from it,
// thus it can't be used anymore.
// let use_baz = (*baz, *baz = 1);
let use_qux = (*qux, *qux = 1);
}
drop(checkpoint_1);
let quux = bump.alloc::<i32>(&id1);
}
impl Drop for Bump<'_> {
fn drop(&mut self) {}
}
impl Drop for SubBump<'_, '_> {
fn drop(&mut self) {}
}
1 post - 1 participant
🏷️ Rust_feed