I have a method (specimens), which is supposed to sort an inner Vec and return a shared read-only reference to the sorted Vec/slice. However, this means that for the lifetime of the read-only reference, there is a mutable borrow from the outer structure alive.
Is there any way around this? I feel like it could be a common problem when a method requires mutable access during runtime, but only shared access after having returned?
What is the best way to deal with these sort of situations? Should I think about an entirely different API?
#[derive(Default)]
pub struct Foo {
meta: i32,
is_sorted: bool,
specimens: Vec<String>,
}
impl Foo {
fn meta(&self) -> i32 {
self.meta
}
fn specimens(&mut self) -> &[String] {
if !self.is_sorted {
self.specimens.sort(); // needs &mut self
self.is_sorted = true;
}
&self.specimens
}
fn specimens_prepare(&mut self) {
if !self.is_sorted {
self.specimens.sort(); // needs &mut self
self.is_sorted = true;
}
}
fn specimens_doit(&self) -> &[String] {
if !self.is_sorted {
panic!("use `specimens_prepare` first");
}
&self.specimens
}
}
fn main() {
let mut foo = Foo::default();
let specimens = foo.specimens();
// `specimens` is borrowed exclusively, so the following fails to compile:
//let _ = foo.meta();
drop(specimens);
// But we could do:
foo.specimens_prepare();
let specimens = foo.specimens_doit();
let _ = foo.meta();
drop(specimens);
}
Not directly; we'd need some sort of new annotation to reflect that the &mut should be reborrowed as & in concert with the value being returned.
Probably. I think your example is just an example so it's hard to give advice; you don't need the functionality in the example (copy out meta first). But you could return something containing both &[String] and &i32 say.
Perhaps take inspiration from entry APIs, if they suit your actual use case.
For that matter you can return something that contains both the &[String] (presumably something more complicated in the real use case) and &Self, and then implement methods on that or even expose the &Self. Basically a manual version of the hypothetical annotation.
I've used this sort of pattern, not for &mut downgrading, but in some DAG structure wherein edges were keys and the root data structure held a store of all the nodes, so if you wanted to access a node's children nodes say, you needed to query the root. It was nicer just to have a NodeHandle<'a> (that had a reference back to the root) which could return an iterator of other NodeHandle<'a> or whatever via method call.
SortedFoo is now some sort of pointer here: a pointer with a guarantee. It guarantees that the pointed-to Foo is sorted. Thus I feel like implementing Deref is idiomatic.
But I'm overall not sure if I need this in my real-world code. Speaking in terms of the example, I might just require reading the meta beforehand. Still curious though, if the SortedFoo approach seems generally reasonable/idiomatic.