Passing trait refs to a sub function

The following fails even though I have the impl blocks for &Bar ... any tips for what I'm doing wrong?

trait Foo {}

struct Bar {}
impl Foo for Bar {}

//impl for refs...
impl Foo for &Bar {}
impl Foo for &mut Bar {}

fn do_it(foo: &impl Foo) {
    sub_do_it(&foo);
}

fn sub_do_it(foo: &impl Foo) {
}

fn main() {
    do_it(&Bar{});
}
1 Like

The parameter to do_it is a reference to an object implementing Foo, but you reference it again when calling sub_do_it - in other words, you're trying to pass &&T to a function that expects &T. If you change sub_do_it(&foo) to sub_do_it(foo), your code compiles.

2 Likes

Thanks, followup question - how does that differ when passing a reference of a referenced object directly (not as a Trait)?

For example this works fine - doesn't seem to do the "double reference" thing:

fn do_it(foo: &Bar) {
    sub_do_it(&foo);
}

fn sub_do_it(foo: &Bar) {
}

In your current situation, both Bar and & [mut] Bar implement Foo, but that does not mean that if T : Foo, then &T : Foo. Take, for instance, T = &Bar : does &T = &&Bar implement Foo ? It does not.

OK, that makes sense, given that the trait must be implemented for the specific reference types.

Though I'm still not clear on something that might be a bit more fundamental:

Why is this passing &Bar to sub_do_it

fn do_it(foo: &Bar) {
    sub_do_it(&foo);
}

While this is passing &&Foo (instead of &Foo) ?

fn do_it(foo: &impl Foo) {
    sub_do_it(&foo);
}

In the concrete version the double reference is coerced down to a single reference, this doesn't happen in the generic version.

Would this theoretically be solved by implementing Deref for the Foo trait?

(I tried doing that and got beaten by the compiler)

Oh - I think where I'm confusing myself is also a silly mistake...

thinking do_it(foo) moves foo, which of course it can't because it doesn't own foo - it only owns a reference to foo... and there's no such thing as moving a reference.

Having impl Trait as the type of a function parameter makes the function generic, i.e., each call may be monomorphised to a different type:

fn do_it (x: & (impl Foo));

fn do_it2<T : Foo> (x: & T);
  • Both declaration lead to the same generic function, except for the fact that the second one can be explicitely monomorphised to a target type with turbofish notation:

    let _: fn(& Bar)  = do_it2::<Bar>;
    let _: fn(& &Bar) = do_it2::<&Bar>;
    
  • This means that a call to do_it is equivalent to do_it2::<_>, i.e., we are letting Rust infer the type the generic function must be monomorphised to.

When using concrete types, either with an actual instance Bar, or by using turbofish, we can make Rust accept a &&Whatever where a &Whatever is expected, thanks to a Deref coercion that makes Rust implicitly dereference (use the * operator) on the reference to a reference, so that the types match.

But you cannot both let Rust infer a type and use a Deref coercion to make the current type, after dereference, match the inferred type. And this is what happened with sub_do_it. You can fix that by using turbofish to get rid of the type inference, or by manually dereferencing:

pub trait Trait {}

pub fn foo (_: &(impl Trait)) {}

pub fn foo_generic<T : Trait> (x: &T)
{
    foo_generic::<T>(&x); // A - deref coercion
    // real code: foo_generic::<T>(*&x); 

    /* fails */ foo_generic::<&T>(&x); // B - problem: &T may not impl Trait

    /* fails */ foo_generic::<_>(&x); // type inference beats deref coercion, hence case B

    /* fails */ foo(&x) // same as <_>
}
3 Likes

tangent point: that's a cool idea to use recursion for the sake of typechecking a teaching example (i.e. so we're using the same parameter)!

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