I fully agree that these kinds of API ideas should use some version of partial borrows if possible. The added benefit is that this would mean one could probably refine the lifetimes on existing API.
For example… well, there’s not even that many examples in the standard library because most collections to move values on re-allocation at least…
Your proposal is supposed to be about enhancing VecDeque
then, it seems from the remark at the end of the article?
A collection can add the fine-grained permission API by only adding a scope
method, without breaking any existing APIs. I’m considering to propose a VecDeque::scope
API to std library, which allowed call push
while holding stable value references.
For that however, remember that VecDeque
on .push
does re-allocate memory and move elements if it runs out of capacity, so (unlike C++’s deque
) it cannot promise valid references to elements after insert.
Also generally for such APIs in std, since some “partial borrows” based solution seems best, that might be a good reason against standard library inclusion of such APIs.
An interesting (but even more complex beyond more basic “partial borrows”) idea IMO here would also be so somehow distinguish “shallow” access to data from “deep(-only)” access.
That way, a Vec<Box<T>>
(or VecDeque<Box<T>>
) could have push
even on re-allocation only access the contained Box
es “shallowly”, and any borrows of the deep T
(“deep” means it’s behind further indirection, and thus unaffected by simple moves of the containing Box<T>
) directly could be allowed to be kept.
Another interesting challenge I’ve thought about and don’t know a good way how to model is append-only accesses.
For examples, a fn push_and_take_mutable_reference(self: &mut Vec<T>, elem: T) -> &mut T
style method (and ignoring the reallocations for now) would only mutate the structure part, and then keep a mutable borrow to the values part of the Vec
, however, this only borrows sort-of “newly introduced” parts of that values part of the Vec
… so in principle one shouldn’t make this in conflict with existing references to values.
However it cannot just be a shared borrow of values either, otherwise a subsequent get(len - 1)
after that push_and_take_mutable_reference
call would support acquiring an aliasing reference to the mutable reference to the last element.
One thing that your article doesn’t mention: of course some use-cases could also be addressed by an API that makes the collection !Sync
and permits mutability to the structure through interior mutability. This is of course a trade-off (in this case of reducing API-complexity, especially if done in a library-only fashion, vs. the ability to do shared accesses thread-safely, e.g. throwing in a rayon parralel iterator traversal or the like).
Your article makes me think that !Sync
protected interior-mutability based APIs could also work with a scope
-ish model, or rather, simpler, with a fn (&'a mut self) -> HelpfulWrapper<'mut, …>
API, so you can have only some section of the code use the otherwise ordinary (and thread-safe) collection in such a manner. (With such “scoped” access, they could even both exist! Both a !Sync
interior-mutable structure API and a ValuePerm
one.)
Here are a few notes I’ve had while reading:
However, r1
is definitely valid after a push_back
operation in a double-ended linked list. The code is correct but can’t pass the safe rust compilation.
Not necessarily; as in Rust, operations that can invalidate references include mutable access, an unfortunate implementation
of push_back
could access mutably the whole relevant Node
that needs to be updated and invalidate r1
.
(I’ve asked miri, it looks like the current implementation doesn’t, but still, such restrictions can come up in Rust, compared to C++)
inconvinience
(couldn’t help but notice the typo, sorry)
pub fn new_lru_cache<K, V, F>(fun: F)
where
F: for<'brand> FnOnce(ValuePerm<'brand>, LruCache<'brand, K, V>),
is the issue with dropping the LruCache
early supposed to be addressed yet? (Reading futher eventually shows “no”, but maybe you could acknowledge that that issue is still supposed to be ignored, in some half-sentence somewhere in the “Associate LruCache and ValuePerm” section… or maybe by making the forward reference
Fortunately, this issue is resolved in the subsequent design, so let’s temporarily ignore it here.
more precise by saying something like
Fortunately, this issue is resolved in the “scope API” design below, so let’s temporarily ignore it here.