可以对引用和非引用做泛型吗?

Can one do generics over references and non-references?

我正在尝试编写代码,根据需要使引用成为非 Copy 类型,同时如果它是 Copy(因为它是引用)则直接使用该值。考虑以下示例:

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.

以上代码编译正常。我现在想用几种不同的类型调用该方法(或类似方法)。有些是参考,有些不是:

// 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)
}

这行不通,因为 Wrapper::f 的接口似乎限制太多。

有没有一种方法可以编写 Wrapper::f 以便可以将其与引用类型和非引用类型一起使用,而不需要对 F <-> &&G 组合使用 impl?

当你听到 "generic over references and non-references" 时,想想 std::borrow::Borrow<T>Borrow<T>由多种指针实现,如Arc<T>Box<T>&T,以及普通的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()
    }
}

(请注意,HRTB(for<'r> 边界)通常仅在引用 inside 具有边界的函数时才需要。在 f,引用由调用者传入,因此 &'a T 上的正则绑定就足够了,并允许 f 更通用。)

上述方法适用于 g1g2g3 (once you add Output = S),因为只有一个 T 可以同时使用两种类型Borrow 到。也许令人惊讶的是,它也适用于具体的 FG,它们只为一种类型 T 实现 Borrow<T>,例如 i32(仅实现 Borrow<i32>).但是,如果您尝试使用同时实现 Borrow 的类型多次调用 f,编译器将无法推断出 T。这是一个使用 &i32 的示例,它实现了 Borrow<&i32>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`
}

发生这种情况时,您必须使用 turbofish:

明确指定 T
fn gx<'a>(x: &'a Wrapper<&i32>, y: &'a Wrapper<&i32>) -> i32 {
    x.f::<i32, _>(&y) // ok
}