Two Levels of mut

This requirement seems like it can be solved through my recent new design pattern. We can design an API like this:

#![feature(vec_push_within_capacity)]
use std::marker::PhantomData;
use std::slice::SliceIndex;
type InvariantLifetime<'brand> = PhantomData<fn(&'brand ()) -> &'brand ()>;
pub struct VecHandle<'vec, 'brand, T> {
    vec: &'vec mut Vec<T>,
    _lifetime: InvariantLifetime<'brand>,
}
pub struct ValueToken<'brand> {
    _lifetime: InvariantLifetime<'brand>,
}
pub trait VecExt<T> {
    fn push_scope<'vec, F, R>(&'vec mut self, fun: F) -> R
    where
        for<'brand> F: FnOnce(VecHandle<'vec, 'brand, T>, ValueToken<'brand>) -> R;
}
impl<T> VecExt<T> for Vec<T> {
    fn push_scope<'vec, F, R>(&'vec mut self, fun: F) -> R
    where
        for<'brand> F: FnOnce(VecHandle<'vec, 'brand, T>, ValueToken<'brand>) -> R,
    {
        let handle = VecHandle {
            vec: self,
            _lifetime: Default::default(),
        };
        let token = ValueToken {
            _lifetime: Default::default(),
        };
        fun(handle, token)
    }
}
impl<'vec, 'brand, T> VecHandle<'vec, 'brand, T> {
    pub fn push_within_capacity(&mut self, value: T) -> Result<(), T> {
        self.vec.push_within_capacity(value)
    }
    pub fn get<'handle, 'token, I>(&'handle self, index: I, _token: &'token ValueToken<'brand>) -> Option<&'token <I as SliceIndex<[T]>>::Output>
    where
        I: SliceIndex<[T]>,
    {
        // SAFETY: We convert the lifetime of return value from 'handle to 'token.
        // This is safe because the 'token is scoped by the `scope` call,
        // and inside the whole scope, we'll never move/drop any items.
        // Also `scope` method exclusively borrowed the inner vec,
        // so others outside the scope also can't modify the inner vec.
        unsafe {
            std::mem::transmute(self.vec.get(index))   
        }
    }
}

And use it like this:

pub fn main() {
    let mut v = vec!["a".to_string(), "b".to_string(), "c".to_string()];
    v.reserve(10);
    let output = v.push_scope(|mut handle, token| {
        let x1 = handle.get(0, &token).unwrap().as_ref();
        let x2 = handle.get(1, &token).unwrap().as_ref();
        handle.push_within_capacity("d".to_string()).unwrap();
        let x4 = handle.get(3, &token).unwrap().as_ref();
        [x1, x2, x4].join(" ")
    });
    dbg!(output);
}

Checkout the implementation here: Rust Playground

And for the correctness or soundness, please checkout my post:

@steffahn gave some suggestions, inspiring me to apply the design pattern I recently found (I haven't found a good enough name for it) to meet your requirements.

3 Likes