Hello,
Several n-dimensional array projects for Rust are discussing (e.g. ndarray, mdarray) that it would be really nice to deref (parts of) arrays to custom wide pointers that would represent array slices (in analogy to the built-in Vec<T>
and &[T]
).
However custom DSTs are not a thing yet.
Trying to understand the current state of things, and the motivations behind, I wonder:
Wouldn't a Deref
that allows moving out (that would be the DerefMove
proposal, right?) be already sufficient to implement such custom smart pointers?
Actually, why is it that std::ops::Deref
insists on returning a reference? The code example attached below works and demonstrates that a hypothetical MyDeref
trait could take over the work that std::ops::Deref
does today and in addition support use cases like the ones envisioned by the n-dimensional array libraries.
On this topic, the Rust book says:
The reason the
deref
method returns a reference to a value, and that the plain dereference outside the parentheses in*(y.deref())
is still necessary, is to do with the ownership system. If thederef
method returned the value directly instead of a reference to the value, the value would be moved out ofself
. We donβt want to take ownership of the inner value insideMyBox<T>
in this case or in most cases where we use the dereference operator.
So the reason why Deref
does not allow moving out anything is to prevent a dereferencing operation moving out inner values of the container? I still don't quite get it. In the code snippet below MyDeref
is implemented for &Vec<i32>
, so it is not allowed to move out anything out of the container. If it was implemented for Vec<i32>
it could just as well move out contents, but would also consume the container in doing so - so why not?
The restriction cannot be there for performance reasons, because moving out a struct of two pointer-sized entries is the same as returning a wide reference.
So, in summary, I'd be grateful for hints that help me understand why std::ops::Deref
was not defined like the below MyDeref
in the first place. I certainly must be overlooking something.
I'm aware of previous discussions of this and similar topics (e.g. Why does the Index trait require returning a reference?) but I do not feel that they fully address the questions that I pose here.
Many thanks!
use std::slice::Iter as SliceIter;
// Mockup of a real container (this could be an owned array).
pub struct MyVec(Vec<i32>);
// Mockup of a smart-pointer into that container (this could be
// a n-dimensional slice).
pub struct MySlice<'a> {
data: *const i32,
len: usize,
_slice: std::marker::PhantomData<&'a [i32]>,
}
// Add some minimal functionality to MySlice.
impl<'a> IntoIterator for MySlice<'a> {
type Item = &'a i32;
type IntoIter = SliceIter<'a, i32>;
fn into_iter(self) -> SliceIter<'a, i32> {
let slice: &[i32];
unsafe {
slice = std::slice::from_raw_parts(self.data, self.len);
}
slice.iter()
}
}
// What would be lost if std::ops::Deref was like this?
pub trait MyDeref {
type TargetRef;
// Required method
fn myderef(self) -> Self::TargetRef;
}
// MyDeref works for the mockup container.
impl<'a> MyDeref for &'a MyVec {
type TargetRef = MySlice<'a>;
fn myderef(self) -> MySlice<'a> {
MySlice {
data: self.0.as_ptr(),
len: self.0.len(),
_slice: Default::default(),
}
}
}
// MyDeref also seems to be able to replace std::ops::Deref.
impl<'a> MyDeref for &'a Vec<i32> {
type TargetRef = &'a [i32];
fn myderef(self) -> &'a [i32] {
self.as_slice()
}
}
fn main() {
let vec = vec![0, 1];
let myvec = MyVec(vec![0, 1, 2]);
let slice = vec.myderef();
let myslice = myvec.myderef();
// Types are as expected
dbg!(std::any::type_name_of_val(&slice)); // &[i32]
dbg!(std::any::type_name_of_val(&myslice)); // MySlice
// MySlice works:
for val in myslice {
dbg!(val);
}
}