How to specify generics that can be dereferenced into a trait

Viewed 49

I have structs A and B, where B can be dereferenced into A.

A implements GetValue with the function .get_value(), and therefore B also indirectly implements GetValue.

Now I'd like to write a function print_value that can accept &A and &B, so it can internally call .get_value() on them. Technically this should be possible, because both of them can be dereferenced into &GetValue; &A by dereferencing once, and &B by dereferencing twice.

The question is: How do I specify the generic of print_value to achieve this?


Here is my attempt:

use std::ops::Deref;

//### external library code ######################
struct A(i32);
struct B(A);

impl Deref for B {
    type Target = A;

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

trait GetValue {
    fn get_value(&self) -> i32;
}

impl GetValue for A {
    fn get_value(&self) -> i32 {
        self.0
    }
}

//### my code ####################################
fn print_value<T: Deref>(val: T)
where
    <T as Deref>::Target: GetValue,
{
    println!("{}", val.get_value())
}

fn main() {
    let a = A(1);
    let b = B(A(2));

    println!("{}", a.get_value());
    println!("{}", b.get_value());

    print_value(&a);
    print_value(&b); // < fails with error "the trait bound `B: GetValue` is not satisfied"
}

The problem here is that <T as Deref>::Target: GetValue does not match &B, because &B would have to be dereferenced twice. The compiler automatically dereferences many times when a function is called, so is there a way to achieve the same through generics?


Note

I only have power over the implementation of print_value. I do not have the power over the other functions.

A more real-life example would be:

  • get_value = get_error_source
  • A = an error type
  • B = Box<dyn A>
  • GetValue = std::error::Error

I just wanted to keep it as abstract as possible to avoid confusion. I'm still quite certain that this is not an XY problem.

2 Answers

Instead of making a function have an overly complicated signature just to signify that it can take anything that you know to get_value of, you could do a blanket implementation. This way, you automatically signify that something you can get_value of, well, you can get_value of.

impl<T> GetValue for T
where
    T: Deref,
    <T as Deref>::Target: GetValue,
{
    fn get_value(&self) -> i32 {
        self.deref().get_value()
    }
}

Just adding that makes it work, see the playground.

You just need to actually call deref on the object, then you would pass a B or an &A :

use std::ops::Deref;

...

fn print_value<T: Deref>(val: T)
where
    <T as Deref>::Target: GetValue,
{
    println!("{}", val.deref().get_value())
}

fn main() {
    let a = A(1);
    let b = B(A(2));

    println!("{}", a.get_value());
    println!("{}", b.get_value());

    print_value(&a);
    print_value(b); 
}

Playground

Outputs:

1
2
1
2
Related