Owned Deref Wrapper?

I have what I consider to be a very simple problem. Consider the following library.

pub trait Foo {}

Now consider the following binary (for which Foo is external).

struct FooWrapperNewtype<T>(T);

I either want T to implement library::Foo, or to be a pointer to a type that implements Foo. How do I do this? I have tried a number of approaches:

BorrowMut

Here is the closest I came to a solution using BorrowMut:

Playground

struct FooWrapperNewtype<X: ?Sized + Foo, T: std::borrow::BorrowMut<X>>(T);

However, it fails to compile:

error[E0392]: parameter `X` is never used
 --> src/lib.rs:2:26
  |
2 | struct FooWrapperNewtype<X: ?Sized + Foo, T: std::borrow::BorrowMut<X>>(T);
  |                          ^ unused parameter
  |
  = help: consider removing `X`, referring to it in a field, or using a marker such as `std::marker::PhantomData`

I believe this is due to the variance rules defined in RFC 738. Might this be an oversight in that RFC?

DerefMut

The following successfully compiles:

trait Foo {}
struct FooWrapperNewtype<T: std::ops::DerefMut>(T) where T::Target: Foo;

However, this doesn't work for types that directly implement Foo (and not DerefMut).

Box<dyn Foo>

struct FooWrapperNewtype(Box<dyn Foo>);

This requires object safety, heap allocation, and dynamic dispatch. Next!

Owned wraper around DerefMut

I am unsure if this has been proposed before, but I could not find any similar proposal.

First, the following code (with much better names) could be added to either std or just an external crate:

/// Identical to `Deref`.
pub trait Wrapper {
    type Target: ?Sized;

    fn wrapped(&self) -> &Self::Target;
}

/// Identical to `DerefMut`.
pub trait WrapperMut: Wrapper {
    fn wrapped_mut(&mut self) -> &mut Self::Target;
}

impl<T: std::ops::Deref> Wrapper for T {
    type Target = T::Target;

    fn wrapped(&self) -> &Self::Target {
        self
    }
}

impl<T: std::ops::DerefMut> WrapperMut for T {
    fn wrapped_mut(&mut self) -> &mut Self::Target {
        self
    }
}

pub struct Owned<T>(pub T);

impl<T> Wrapper for Owned<T> {
    type Target = T;

    fn wrapped(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> WrapperMut for Owned<T> {
    fn wrapped_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

Now you can do the following:

struct FooWrapperNewtype<T: WrapperMut>(T) where T::Target: Foo;
// You can use `FooWrapperNewtype(Owned(...))`

Wrapper and WrapperMut are identical to Deref and DerefMut respectively, but they omit the implication of a small struct that acts as a smart pointer. They are essentially explicit versions of Deref/DerefMut, kind of like how Clone is essentially(-ish) an explicit version of Copy. This enables the use of Owned for types where it makes sense to have a generic Borrowable type.

If this were to be included in std, an alternative to the headache of duplicate Wrapper and Deref traits could be (in the next edition change) making Deref a subtrait of Wrapper (essentially just a marker trait).


Is there any better way to do this, and has the Wrapper/Owned solution been proposed somewhere before?

Your BorrowMut solution doesn't fail to compile because of variance but due to the unused type parameter (which the compiler tells you explicitly). Add a PhantomData<X> field to the wrapper and you should be good to go (which the error message also tells you btw).

playground

Thanks for your reply! I should have clarified, I was citing this section of the variance RFC which talks about unused type parameters. And I saw the error message too, however I have never seen PhantomData used except as a marker trait for unsafe structs, so that makes me question if this is the correct solution. I am worried that PhantomData could impose additional constraints (I haven't quite been able to find a good example of this though).

Edit: I meant to mention, I think part of the problem is that a type can implement BorrowMut<X> for more than one X which implements Foo. That's why I wonder if it might make more sense to use an associated type, but I couldn't figure out a way to solve the core issue (e.g. accept either T or Rc<T>) using an associated type. I understand why associated types don't work (T can implement both Foo and DerefMut where Target: Foo) and I have a very limited understanding of why the type parameter solution doesn't work (to avoid bivariance, but still not sure why it doesn't work in this particular case as it should clearly be covariant?). I am asking how to solve this problem, either by fixing one of those solutions or if it's better to use the new method I described and if it would make sense to change the standard library.

Well, T itself can be Rc<U>. In other words, if you are trying to abstract over something that "is an Rc or is not an Rc", the you just basically found a very roundabout way of saying "any type". In which case you should just use a T. I'm not sure though if that is indeed what you want.

Putting PhantomData in your struct generally has no hidden adverse effects. It might hinder some optimizations around Drop, but it doesn't introduce bugs or unsafety in your code. I don't have any idea what "constraints" you are looking for, though.

1 Like

If I understood correctly, there is some trait Foo, and OP wants for every T: Foo accept either T or Rc<T>. Not for any T, as you could think based on the simplified statement.

1 Like

Then they could still impl<T> Foo for Rc<T> where T: Foo and accept any T: Foo, couldn't they? I mean, I don't see how making the Rc-vs-non-Rc distinction could be in any way useful without making "generic" code a special-cased pile of anti-patterns. Caring about specific types seems like the exact opposite of what generics are for.

1 Like

@Cerber-Ursi Exactly!

@H2CO3 I know I could impl<T: Foo> Foo for Rc<T> but that would be very confusing (and I believe bad practice). In that case, if I have a Rc<MyStruct> where MyStruct: Foo, would it use impl Foo for MyStruct or impl<T: Foo> Foo for Rc<T>? It would get very very confusing with autoref. Plus I would have to do that for all smart pointers. Also, I know people often do this for just Box which I also consider a bad idea for the same reason.

I will try to explain this better. I have a trait Foo and, a struct MyStruct that implements Foo. I want to create a newtype which can wrap any struct that implements Foo, such as MyStruct. I also want the ability to use any pointers like Rc or Box with the same wrapper type (to clarify: by a pointer, I specifically mean anything that implements DerefMut and where the target implements Foo). How do I do this?

For example, one solution could be using Box<dyn Foo> but that requires dynamic dispatch and object safety which makes it far from ideal.

It's pretty much idiomatic. And people do it all the time for wrapper types if/when it makes sense.

By default, the latter, but why does it even matter? Wouldn't you just implement Foo for Rc<T> in terms of T as Foo, i.e. by simply forwarding to the wrapped object? That would mean the two impls would do the same thing anyway.

1 Like

Hm, it’s a bit sad that this is common. I don’t think that implementing a trait for each individual pointer type is a good idea, especially because it actually precludes later adding a generic implementation. In fact, in my case, I am using a pointer type from a garbage collection crate and wrapping a trait from an external crate, so due to the orphan rule I can’t implement the trait specifically for the garbage-collected pointer. I can edit the crate that defines the trait but I’d rather not add the garbage collector as a dependency just for the one impl! Plus, a generic impl for any DerefMut would be much more flexible. This is already possible, but then I can’t use a struct that directly implements the trait! Do you see the problem? I’m mainly looking for a solution to the original question rather than new workarounds (I already know a few workarounds such as the examples I mentioned in the first post). If you don’t know of any solution then that’s ok, I’m also looking for any opinions on the Owned/Wrapper additions that I proposed.

Yes, it’s just not very canonical. You’re right, it doesn’t really matter.

Side note, but:

That's what crate features are for, I think.

@Cerber-Ursi Good point. Still it seems like there should be some generic way to do this. One new way I have just thought of is using an enum like:

enum FooWrapper<T: Foo, R: DerefMut<Target = T> = !> {
    Owned(T),
    Ref(R),
}

I’m on my phone right now though, so I haven't tested that yet.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.