Consider, for the sake of simplicity, that I want to implement an indexable Vector v with n consecutive elements 0,1,...,n-1, i.e. v[i] = i. This vector is supposed to be filled on demand, that is, if v[i] is used and currently the vector contains n < i+1 elements, the values n+1,n+2,...,i are first pushed onto v, and then the reference to v[i] is returned.
Code below works fine.
struct LazyVector {
data: Vec<usize>
}
impl LazyVector {
fn new() -> LazyVector {
LazyVector{
data: vec![]
}
}
fn get(&mut self, i:usize) -> &usize {
for x in self.data.len()..=i {
self.data.push(i);
}
&self.data[i]
}
}
pub fn main() {
let mut v = LazyVector::new();
println!("v[5]={}",v.get(5)); // prints v[5]=5
}
However, the code above is just a mock-up of the actual structure I'm trying to implement. In addition to that, (1) I'd like to be able to use the index operator and, (2) although the vector may actually be modified when accessing a position, I'd like that to be transparent to the user, that is, I'd like to be able to index any position even if I had an immutable reference to v. Immutable references are preferred to prevent other unwanted modifications.
Requirement (1) could be achieved by implementing the Index trait, like so
impl std::ops::Index<usize> for LazyVector {
type Output = usize;
fn index(&self, i: usize) -> &Self::Output {
self.get(i)
}
}
However, this does not compile since we need a mutable reference in order to be able to call LazyVector::get. Because of requirement (2) we do not want to make this reference mutable, and even if we did, we couldn't do that since it would violate the interface of the Index trait. I figured that this would make the case for the interior mutability pattern through the RefCell smart pointer (as in Chapter 15 of The Rust Book). So I came up with something like
struct LazyVector {
data: std::cell::RefCell<Vec<usize>>
}
impl LazyVector {
fn new() -> LazyVector {
LazyVector{
data: std::cell::RefCell::new(vec![])
}
}
fn get(&self, i:usize) -> &usize {
let mut mutref = self.data.borrow_mut();
for x in mutref.len()..=i {
mutref.push(x)
}
&self.data.borrow()[i] // error: cannot return value referencing a temporary value
}
}
However this doesn't work because it tries to return a value referencing the Ref struct returned by borrow() that goes out of scope at the end of LazyVector::get. Finally, to circumvent that, I did something like
struct LazyVector {
data: std::cell::RefCell<Vec<usize>>
}
impl LazyVector {
fn new() -> LazyVector {
LazyVector{
data: std::cell::RefCell::new(vec![])
}
}
fn get(&self, i:usize) -> &usize {
let mut mutref = self.data.borrow_mut();
for x in mutref.len()..=i {
mutref.push(x)
}
unsafe { // Argh!
let ptr = self.data.as_ptr();
&std::ops::Deref::deref(&*ptr)[i]
}
}
}
impl std::ops::Index<usize> for LazyVector {
type Output = usize;
fn index(&self, i: usize) -> &Self::Output {
self.get(i)
}
}
pub fn main() {
let v = LazyVector::new(); // Unmutable!
println!("v[5]={}",v.get(5)); // prints v[5]=5
}
Now it works as required but, as a newbie, I am not so sure about the unsafe block! I think I am effectively wrapping it with a safe interface, but I'm not sure. So my question is whether that is OK or if there is a better, totally safe way to achieve that.
Thanks for any help.