Optional reference in generics

Say you want something like:

use std::io::Read;

pub struct ReadWrapper<R: Read>(R);

pub struct OtherReadWrapper<R: Read>(R);

impl<R: Read> ReadWrapper<R> {
    pub fn other(&mut self) -> OtherReadWrapper<&mut R> {
        OtherReadWrapper(&mut self.0)
    }
}

So a Read wrapper that can temporarily spawn a different wrapper on the same Read.

When R is a struct, everything is fine: ReadWrapper wraps the struct, and the OtherReadWrapper wraps a mut reference to the struct.

But when R is a Box<T> or a &mut T to begin with, the OtherReadWrapper now uselessly has to go an extra level of pointer indirection.

I can't think of a way to make this work without the extra level of pointer indirection while supporting both use cases of either a struct or a box/mut ref. Do I have a lack of imagination, or is it just not possible?

The more pertinent question would be what is the use of OtherReadWrapper?

1 Like

How about:

-    pub fn other(&mut self) -> OtherReadWrapper<&mut R> {
+    pub fn other(self) -> OtherReadWrapper<R> {
-        OtherReadWrapper(&mut self.0)
+        OtherReadWrapper(self.0)

(Playground)

Not sure what's your use case though and whether this helps. Note that other is now consuming self.


Some Readers are cloneable, e.g. because they are references to files or byte slices:

use std::io::Read;

pub struct ReadWrapper<R: Read>(R);

pub struct OtherReadWrapper<R: Read>(R);

impl<R: Read> ReadWrapper<R> {
    pub fn other(&self) -> OtherReadWrapper<R>
    where
        R: Clone,
    {
        OtherReadWrapper(self.0.clone())
    }
}

fn main() {
    let data = b"some data";
    let wrapped_reader: ReadWrapper<&[u8]> = ReadWrapper(data as &[u8]);
    let other_wrapped: OtherReadWrapper<&[u8]> = wrapped_reader.other();
    drop(wrapped_reader);
    drop(other_wrapped);
}

(Playground)

1 Like

Unfortunately, the idea is to be able to continue using ReadWrapper once you're done with OtherReadWrapper. I guess one way to do that would be to make the OtherReadWrapper wrap ReadWrapper itself, but ReadWrapper is not Read (although the name could imply it, blame that on bad naming for this reduced usecase) so you'd then have to initialize OtherReadWrapper with a ReadWrapper, which would make testing more difficult.

I guess the simplest would be to have ReadWrapper just always wrap a &mut R in the first place.

The kind of usecase I have is that ReadWrapper handles a container format that comes from a Read (it's streamed), and the container format has internal parts, so OtherReadWrapper would be a struct representing those parts. ReadWrapper is kind of like an iterator that only allows to keep one item at a time. And some parts need to stream from the original Read.

One possibility is to do the following. However, I don't think it's a good thing to do (edit: or is it!?):

use std::io::Read;

pub struct ReadWrapper<R: Read>(R);

pub struct OtherReadWrapper<R: Read>(R);

impl<R: Read> ReadWrapper<R> {
    pub fn other(self) -> OtherReadWrapper<R> {
        OtherReadWrapper(self.0)
    }
}

impl<R: Read> OtherReadWrapper<R> {
    pub fn into_inner(self) -> R {
        self.0
    }
}

fn main() {
    let reader = b"read me" as &[u8];
    let wrapper = ReadWrapper(reader);
    let other_wrapper = wrapper.other();
    // use `other_wrapper` here
    let wrapper = other_wrapper.into_inner();
    // use `wrapper` here
    drop(wrapper);
}

(Playground)

Can you give an example of what you mean?

One possibility is to do the following. However, I don't think it's a good thing to do (edit: or is it!?):

That loses the state of the ReadWrapper. To give a more concrete example of the sort of usecase, think of ReadWrapper as tar::Archive and OtherReadWrapper as tar::Entry. But the tar crate doesn't try to avoid the indirection when using Archive<&mut T>

struct ReadWrapper<'a, R: Read>(&'a mut R);

which, sadly, will add lifetimes everywhere.

I feel like this is the correct approach, however. Semantically, you keep a borrow of the reader. The fact that there is an impl<R: Read + ?Sized> Read for &mut R in std doesn't change the semantics of you wanting to mutably borrowing the reader (if I understand it right).

The downside is that it prevents uses of ReadWrapper with an owned or boxed R.

Maybe you can fix that by using a generic T: Deref<Target=R> + DerefMut instead of &mut R.

When you want to make the struct own the reader, you could use something akin to what I used here: Owned smart pointer. (Note this example is implementing Deref only, and not DerefMut.)

But I'm not sure if I fully overlook the issue right now (it's late here :sweat_smile:).


I mean something like this:

use std::io::Read;
use std::ops::{Deref, DerefMut};

pub struct ReadWrapper<R: Read, T: Deref<Target=R> + DerefMut>(T);

pub struct OtherReadWrapper<R: Read, T: Deref<Target=R> + DerefMut>(T);

impl<R: Read, T: Deref<Target=R> + DerefMut> ReadWrapper<R, T> {
    pub fn other(&mut self) -> OtherReadWrapper<R, &'_ mut R> {
        OtherReadWrapper(&mut *self.0)
    }
}

(Playground)


But not sure if that solves your original problem. I don't think it does… :sleeping:


:thinking:

On the other hand, &mut * contains both a reference and a dereference. Maybe it's exactly what you need. I will think more about this tomorrow.

Thanks. I tried something similar with AsMut but the need for two generics parameters was kind of off-putting.

If you still remember what you did, I'd enjoy to see how you used (or wanted to use) AsMut.

What about a Cow-like type?

pub enum ReadWrapper<'a, R> 
where
    R: 'a + Read + ?Sized, 
{
    Borrowed(&'a mut R),
    Owned(R),
}

(There may be better options if you want to avoid needing to "add lifetimes everywhere".)

Looks like it works:

use std::io::Read;
use std::ops::{Deref, DerefMut};

#[derive(Debug)]
pub struct ReadWrapper<R: Read, T: Deref<Target=R> + DerefMut>(T);

#[derive(Debug)]
pub struct OtherReadWrapper<R: Read, T: Deref<Target=R> + DerefMut>(T);

impl<R: Read, T: Deref<Target=R> + DerefMut> ReadWrapper<R, T> {
    pub fn other(&mut self) -> OtherReadWrapper<R, &'_ mut R> {
        OtherReadWrapper(&mut *self.0)
    }
}

pub struct Owned<T>(T);

impl<T> Deref for Owned<T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.0
    }
}

impl<T> DerefMut for Owned<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.0
    }
}

#[allow(unused_mut)]
fn main() {
    let mut reader = b"Hello World!" as &[_];
    let mut wrapper: ReadWrapper<&[u8], Owned<&[u8]>> = ReadWrapper(Owned(reader));
    let mut other: OtherReadWrapper<&[u8], &mut &[u8]> = wrapper.other();
    drop(other);
    drop(wrapper);
    let ref_reader: &mut &[u8] = &mut reader;
    let mut ref_wrapper: ReadWrapper<&[u8], &mut &[u8]> = ReadWrapper(ref_reader);
    // No extra indirection here:
    let mut ref_other: OtherReadWrapper<&[u8], &mut &[u8]> = ref_wrapper.other();
    drop(ref_other);
    drop(ref_wrapper);
}

(Playground)

I think Cow comes with some runtime-overhead, I guess? Because it's determined at runtime whether I deal with an owned value or with a reference (see also this post by me). That's why I used something like an Owned smart pointer in mmtkvdb. I wonder if it would make sense to add something like Owned to std. Is that very far-fetched? Maybe I should start a discussion on IRLO on that.


Update:

I just published a new crate deref_owned to provide such a smart pointer, which might aid further discussion regarding the usefulness or uselessness of such a data structure.

I started a new thread on IRLO: Smart pointer which owns its target.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.