Rust `cannot infer type` despite constraints

Viewed 468

I want to write a piece of code that can take references or owned values of a copyable type, and return an owned version of that type. I've reduced the problems I'm having with the type inference to the following code, which errors:

use std::borrow::Borrow;

fn copy<R, E>(val: E) -> R
where
    R: Default + Copy,
    E: Borrow<R>,
{
    *val.borrow()
}

fn main() {
    assert_eq!(6, copy(&6));
    assert_eq!(6, copy(6));
    assert_eq!(6.0, copy(&6.0));
    assert!((6.0f64 - copy(&6.0f64)).abs() < 1e-6);
}

The error comes from the last assert:

error[E0282]: type annotations needed
  --> src/main.rs:15:13
   |
15 |     assert!((6.0f64 - copy(&6.0f64)).abs() < 1e-6);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: type must be known at this point

My only hypothesis that the Sub trait on f64 allows f64 or &f64, and if the Default constraint weren't there, then a valid expression for the last copy would be copy::<&f64, &f64>(&6.0f64), however that isn't allowed because &f64 doesn't implement Default. If we pass in an f64 by value it works, presumably because then it restricts R to be f64 instead of either.

What I'm not clear on is why the compiler can't further restrict the return type of copy, or how to indicate to the compiler that the value returned won't be a reference.

1 Answers

Nothing in copy constrains R to a specific concrete type. In particular, &f64 could implement Borrow<R> for multiple values of R (not just f64). It doesn't in your current code, but the lack of alternatives is not considered grounds to pick a specific implementation.

I can even add an implementation that matches:

#[derive(Copy, Clone, Debug, Default)]
struct Foo;

impl Borrow<Foo> for &f64 {
    fn borrow(&self) -> &Foo { &Foo }
}

(This trait implementation is permitted even though f64 is a standard library type because Foo is a type defined in the current crate.)

Now we can actually use the choice:

fn main() {
    dbg!(copy::<f64, _>(&1.0));
    dbg!(copy::<Foo, _>(&1.0));
}
[src/main.rs:19] copy::<f64, _>(&1.0) = 1.0
[src/main.rs:20] copy::<Foo, _>(&1.0) = Foo

A function like copy can only have its return type derived from its argument type when the return type actually depends on the argument type: for example, if it is an associated type of trait implemented by the argument. Both AsRef and Borrow have a type parameter rather than an associated type (and can therefore be implemented multiple times for the same implementing type); Deref has an associated Target type instead, but Deref doesn't offer going from f64 to f64. You could implement your own trait for this:

trait DerefCopy: Copy {
    type Output;
    fn deref_copy(self) -> Self::Output;
}

impl<T: Copy> DerefCopy for &T {
    type Output = T;
    fn deref_copy(self) -> T {
        *self
    }
}

impl DerefCopy for f64 {
    type Output = Self;
    fn deref_copy(self) -> Self {
        self
    }
}

fn main() {
    assert_eq!(6, (&6).deref_copy());
    assert_eq!(6, (6).deref_copy());
    assert_eq!(6.0, (&6.0).deref_copy());
    assert!((6.0f64 - (&6.0f64).deref_copy()).abs() < 1e-6);
}

However, this would require you to implement DerefCopy for every non-reference type you wish to use it with, because it's not possible to write a blanket implementation for all non-reference Ts; the reason Borrow can have a blanket implementation is that impl Borrow<T> for T doesn't conflict with impl Borrow<T> for &T because if we suppose T is itself a reference &U, we get impl Borrow<&U> for &&U which is still not the same as impl Borrow<T> for T.

Related