1

I'm trying to write code that makes a references a non-Copy type as needed, while working with the value directly if it is Copy (because it is a reference). Consider the following example:

struct Wrapper<F>(F);

impl<F> Wrapper<F> {
    fn f<'a, G, O>(&'a self, other: &Wrapper<G>) -> O 
    where for<'r> &'r G: Add<&'a F, Output = O> {
        &other.0 + &self.0
    }
}

Playground.

The above code compiles fine. I would now like to call the method (or a similar one) with a few different types. Some are references, some aren't:

// Three different ways of calling
fn g1<'a, T, S>(
    x: &'a Wrapper<T>, y: &'a Wrapper<T>,
) -> S
where for<'r> &'r T: Add<&'a T> {
    x.f(&y)
}

fn g2<T, S>(
    x: &Wrapper<T>, y: &Wrapper<&T>,
) -> S
where for<'r> &'r T: Add<&'r T> {
    x.f(&y)
}

fn g3<T, S>(
    x: &Wrapper<&T>, y: &Wrapper<&T>,
) -> S
where for<'r> &'r T: Add<&'r T> {
    x.f(&y)
}

This doesn't work, as the interface of Wrapper::f seems to be too restrictive.

Is there a way to write Wrapper::f such that one can use it with both reference and non-reference types, without requiring an impl on the F <-> &&G combination?

vandenheuvel
  • 349
  • 2
  • 13
  • Your playground link doesn't compile. Please provide a working example. – Aloso Jun 05 '20 at 08:32
  • I updated the question for clarity. The first block compiles but it doesn't provide the right interface for usage with the second block. As such, the question is, how to adapt the interface defined in the first block such that the second block compiles. – vandenheuvel Jun 05 '20 at 09:39
  • The `for<'r>` bounds all look unnecessary to me. You are in each case *passing* a reference to the function, so the function doesn't have to work for *all possible* references, it only has to [work for the reference passed to it](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b863456cb51019defea4c10a4551950f). The HRTB is overly strict when a looser bound can be used to make the code more general. However, this doesn't solve your problem with `g2` and `g3`. – trent Jun 05 '20 at 12:49
  • I don't see a way to resolve the ambiguity inherent in "do something different when called with references vs. non-references" because there is no way to write a generic function that only works with non-references. It *might* be possible with specialization (see also [Can I avoid eager ambiguity resolution for trait implementations with generics?](/q/52281091/3650362). I would recommend using references even for `Copy` types when writing generic code -- there is no way to guarantee you always have an owned value when the type is `Copy`. – trent Jun 05 '20 at 13:29

1 Answers1

2

When you hear "generic over references and non-references", think std::borrow::Borrow<T>. Borrow<T> is implemented by many kinds of pointers, such as Arc<T>, Box<T> and &T, as well as plain T.

impl<F> Wrapper<F> {
    fn f<'a, T, G>(&'a self, other: &'a Wrapper<G>) -> <&'a T as Add>::Output
    where
        F: Borrow<T>,
        G: Borrow<T>,
        &'a T: Add<&'a T>,
    {
        other.0.borrow() + self.0.borrow()
    }
}

(Note that HRTBs (for<'r> bounds) are generally only necessary when a reference will be taken inside the function with the bound. In the case of f, the reference is passed in by the caller, so a regular bound on &'a T is sufficient, and allows f to be more general.)

The method above works with g1, g2, and g3 (once you add Output = S) because there is only one T that both types can be Borrowed to. Perhaps surprisingly, it also works with concrete F or G that implements Borrow<T> for only one type T, such as i32 (which only implements Borrow<i32>). However, if you try to call f with types that both implement Borrow more than once, the compiler will fail to infer T. Here's an example using &i32, which implements both Borrow<&i32> and Borrow<i32>:

fn gx<'a>(x: &'a Wrapper<&i32>, y: &'a Wrapper<&i32>) -> i32 {
    x.f(&y) // error[E0284]: type annotations needed: cannot satisfy `<&_ as std::ops::Add>::Output == i32`
}

When this happens, you have to specify T explicitly using a turbofish:

fn gx<'a>(x: &'a Wrapper<&i32>, y: &'a Wrapper<&i32>) -> i32 {
    x.f::<i32, _>(&y) // ok
}
trent
  • 25,033
  • 7
  • 51
  • 90