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.