Type bound for nested smart pointer

Hi, I'm trying to implement a function takes something that can deref to an i32, but I don't know how to get it to work. Here is a minimal example:

fn main() {
    let nested_pointer = Arc::new(Arc::new(2i32));
    let sucess: &i32 = &nested_pointer;
    let fail: &i32 = foo(&nested_pointer);

}
fn foo<T: Deref<Target=i32>>(t: &T) -> &i32 {
    t
}

This does not compile and gives error:

error[E0271]: type mismatch resolving `<Arc<Arc<i32>> as Deref>::Target == i32`
  --> src/main.rs:7:22
   |
7  |     let fail: &i32 = foo(&nested_pointer);
   |                      ^^^ expected `i32`, found struct `Arc`
   |
   = note: expected type `i32`
            found struct `Arc<i32>`

I think I know why it doesn't work. But I don't know how to make it work.

Here is my use case:
There a type T,
Some part of the program wraps T with some extra data.
I want to have a collection that stores a bunch of wrapper of T
The collection cares what's in T
The collection does not care what the wrapper is.
All elements in the collection will have the same wrapper.

Deref in std::ops - Rust is clear and should be read in the first place.

let sucess: &i32 = &nested_pointer;

This works due to the coercion of Deref.

let fail: &i32 = foo(&nested_pointer);

This fails because the type of nested_pointer is Arc<Arc<i32>>, and the bound T: Deref<Target=i32> is not met. So solutions are simple:

// explicit dereferencing: the type of `&*nested_pointer` is `&Arc<i32>`, thus the trait bound is met
let tmp1: &i32 = foo(&*nested_pointer);

// or specify the outer wrapper type
let tmp2: &i32 = foo2(&nested_pointer);
fn foo2<T: Deref<Target = i32>>(t: &Arc<T>) -> &i32 {
    t
}

// or abstract the outer & inner wrapper type
let tmp3: &i32 = foo3(&nested_pointer);
fn foo3<'a, T: Deref<Target = i32> + 'a, U: Deref<Target = T>>(t: &'a U) -> &'a i32 {
    t
}

Thank you for answering. I know I explicitly do that. My question is, is there a way to implement foo such that foo<&32>, foo<&Arc<i32>> and foo<&Arc<Arc<i32>>> all work? The type coercion system is able to handle it, so I'm guessing type bound can also handle it.

Coercion/dereferencing is the best here, so you don't need the trait bound at all. Just let the function take i32.

fn main() {
   let _: i32 = foo(**Arc::new(Arc::new(2i32)));
   let _: i32 = foo(*Arc::new(2i32));
   let ref2 = &2;
   let _: i32 = foo(*ref2);
}
fn foo(t: i32) -> i32 {
    t
}

Of course you're allowed to define your own trait and implement types you mentioned with it for some specific reason.

It's just a minimal example I gave. In real scenario is more complicated and actually need a type bound. Let me try and give another example:

struct Foo {
  // ...
}
impl Foo {
  fn version(&self) -> i32 {
    // ...
  }
}
struct EvenOdd<Ptr> {
  vec1: Vec<Ptr>;
  vec2: Vec<Ptr>;
}
impl<Ptr> EvenOdd<Ptr>
where Ptr: Deref<Target = Foo>
{
  fn add(&mut self, p: Ptr) {
    if (&*ptr).version() % 2 == 0 {
      self.vec1.push(p);
    } else {
      self.vec2.push(p);
    }
  }
  // ...
}

I want EvenOdd to be able to store any type that derefs to Foo. At least should make EvenOdd<Arc<Foo>>, EvenOdd<Arc<Arc<Foo>>> to all work. Having EvenOdd implement both single deref and double deref should work, but it feels clunky.

After some experimentation, I found something that sort of works. But without negative type bound, it requires the inner type to manually implement a trait.

use std::sync::Arc;
use core::ops::Deref;

trait RecDeref<'a, T> {
    fn rec_deref(&'a self) -> &'a T;
}

struct Wrapper {
    i: i32,
}

/** Can be replaced by a generic impl block over Wrapper if negative type bound is supported */
impl<'a> RecDeref<'a, Wrapper> for Wrapper {
    fn rec_deref(&'a self) -> &'a Self {
        self
    }
}

impl<'a, A,B,C> RecDeref<'a, A> for C
where C: Deref<Target = B>, B: RecDeref<'a, A> + 'a
{
    fn rec_deref(&'a self) -> &'a A {
        RecDeref::<A>::rec_deref(Deref::deref(self))
    }
}

fn bar<'a, B: RecDeref<'a, Wrapper>>(b: &'a B) -> &'a Wrapper {
    b.rec_deref()
}

fn main() {
    let foo = Arc::new(Arc::new(Arc::new(Wrapper { i: 2i32 })));
    let result1 = bar(&foo);
    println!("{}", result1.i);
}

If this is what you literally want, AFAIK you can't even construct it[1], because Vec<T> must hold a single type T.

vec![Arc::from(""), Rc::from("")]}; // error[E0308]: mismatched types

  1. Any trait and downcasting may help, but it causes indirection more than you need here. ↩︎

Likely Deref isn't what you need. You might instead go for std::borrow::Borrow.

use std::borrow::Borrow;

struct Foo {
    // ...
}
impl Foo {
    fn version(&self) -> i32 {
        todo!()
    }
}
struct EvenOdd<Ptr> {
    vec1: Vec<Ptr>,
    vec2: Vec<Ptr>,
}
impl<Ptr> EvenOdd<Ptr>
where
    Ptr: Borrow<Foo>,
{
    fn add(&mut self, p: Ptr) {
        if (p.borrow()).version() % 2 == 0 {
            self.vec1.push(p);
        } else {
            self.vec2.push(p);
        }
    }
    // ...
}

(Playground)

Does that solve your problem?


I just noticed that Borrow doesn't support the "double deref/borrow" either, so it might not help you.


Maybe something like this:

use std::borrow::Borrow;
use std::ops::Deref;

struct Foo {
    // ...
}
impl Foo {
    fn version(&self) -> i32 {
        todo!()
    }
}
struct EvenOdd<Ptr> {
    vec1: Vec<Ptr>,
    vec2: Vec<Ptr>,
}
impl<Ptr, Tgt> EvenOdd<Ptr>
where
    Ptr: Deref<Target = Tgt>,
    Tgt: Borrow<Foo>
{
    fn add(&mut self, p: Ptr) {
        if ((&*p).borrow()).version() % 2 == 0 {
            self.vec1.push(p);
        } else {
            self.vec2.push(p);
        }
    }
    // ...
}

(Playground)

But before using it, I would try to see if this is semantically collect.

It seems to work in practice though:

use std::borrow::Borrow;
use std::ops::Deref;
use std::sync::Arc;

fn foo<Ptr, Tgt>(arg: Ptr)
where
    Ptr: Deref<Target = Tgt>,
    Tgt: Borrow<i32>
{
    println!("{}", (&*arg).borrow());
}

fn main() {
    foo(Arc::new(13));
    foo(Arc::new(Arc::new(14)));
}

(Playground)


If #45742 was fixed, then this would work also:

use std::borrow::Borrow;
use std::ops::Deref;
use std::sync::Arc;

fn foo<Ptr>(arg: Ptr)
where
    Ptr: AsRef<i32>,
{
    println!("{}", arg.as_ref());
}

fn main() {
    foo(Arc::new(13));
    foo(Arc::new(Arc::new(14)));
}

(Playground)

But it doesn't:

error[E0277]: the trait bound `Arc<Arc<{integer}>>: AsRef<i32>` is not satisfied
  --> src/main.rs:14:9
   |
14 |     foo(Arc::new(Arc::new(14)));
   |     --- ^^^^^^^^^^^^^^^^^^^^^^ the trait `AsRef<i32>` is not implemented for `Arc<Arc<{integer}>>`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the trait `AsRef<T>` is implemented for `Arc<T>`
note: required by a bound in `foo`
  --> src/main.rs:7:10
   |
5  | fn foo<Ptr>(arg: Ptr)
   |    --- required by a bound in this
6  | where
7  |     Ptr: AsRef<i32>,
   |          ^^^^^^^^^^ required by this bound in `foo`

I'm aware. So EvenOdd can only hold one type of "thing" that "deref"s to Foo.

I think I self answered the question with RecDeref. It's probably the best solution for current rust.
The main issue with your solution is that you have to explicitly say two layers of deref/borrow has to be done, which I want to avoid. I want it to support arbitrary number of layers of deref, which is also what type coercion does.

Blanket impl of AsRef for Deref · Issue #45742 · rust-lang/rust · GitHub can solve my need, but it comes with its own issues and probably won't be fixed any time soon.

Thank you for helping though.

If I were trying to do something like this I think I'd just use an enum, as there's a bit of readability vomit that I get when going through all the &** derefing.. When I see it in the std-library layers I just assume there's no other performant way, but at this layer, I don't think an enum or dyn indirection is going to be as killer. Readability is likely more important. Sounds like you might have even needed a Cow<'a, T> as one of the varients. Some ugliness (but explicitness) may prevent some literal head-aches down the road.

Good luck.

I don't know if this fits your use case or not, but Wrapper itself can be made generic to support arbitrary contained types. But the contained types must be in the Wrapper (or have their own base-case implementations).

1 Like

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.