How to improve the API?

⚓ Rust    📅 2026-03-25    👤 surdeus    👁️ 3      

surdeus

Info

This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: How to improve the API?

Hello, 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

Read full topic

🏷️ Rust_feed