Hi rustaceans,
I'm trying to write smart references which follows the lifetime borrowing rules:
- A reference cannot outlive its referent
- A mutable reference cannot be aliased
The snippet below is how to implement a smart pointer that honors the same behavior to &mut T
, regardless if the callee is marked as mutable or not.
struct SmartPtr<'a, T: 'a> {
pointer: *mut T,
_marker: PhantomData<&'a mut T>,
}
#![derive(Debug)]
struct Container<T> {
arr: [T; 3]
}
impl <T> Container<T> {
fn get_smart_ptr(&mut self, idx: usize) -> SmartPtr<T> {
SmartPtr {
pointer: &mut self.arr[idx] as *mut T,
_marker: PhantomData,
}
}
}
fn main() {
let mut c = Container{arr: [1, 2, 3]};
let s_ptr = c.get_smart_ptr(1);
// s_ptr is treated as `&mut T` because the lifetime is hard coded in the Phantom type
println!("{:?}", c); // desired compilation fail: cannot borrow `c` as immutable because it is also borrowed as mutable
// if we wrote the above as:
{
let s_ptr = c.get_smart_ptr(1);
}
println!("{:?}", c); // now the scoping is correct and it compiles
The problem is, the smart reference/pointer is hard coded with its mutability signature. Is it possible to create a smart pointer such that we can use it as & T
or &mut T
depends on its context?
For instance, the ideal case would be:
let x = c.get_smart_ptr(); // immutable reference, as if it was &T
let mut x_mut = c.get_smart_ptr(); // mutable reference, as if it was &mut T
Less ideal, but still acceptable:
let x: SmartPtr<&T> = c.get_smart_ptr();
let x_mut: SmartPtr<&mut T> = c.get_smart_ptr_mut();
// Is reference in generic valid?
In this case, SmartPtr
can use the generics to infer if it is a mutable reference or not.
Doable, but hopefully we have a better solution:
let x: SmartPtr<T> = c.get_smart_ptr();
let x_mut: SmartPtrMut<T> = c.get_smart_ptr_mut();
This is the most trivial implementation. We can define two types that has exactly the same layout, only the lifetime signature in the PhantomData is different.
Any thought on how to implement it with idiomatic rust?
Thanks.