EN VI

Why type need to be specified manually in this code, in rust?

2024-03-17 02:00:04
Why type need to be specified manually in this code, in rust?

Rust Playground

trait B<T> {}
impl<T> B<T> for T {}

fn f<K>()
where
    // u32: B<u32>, // 4
    u32: B<K>, // 3
{
    // get::<u32>(&1u32); // 2
    get(&1u32); // 1 error
}

fn get<Q>(k: &Q)
where
    u32: B<Q>,
{
}

fn main() {}

error at 1:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:10:9
   |
4  | fn f<K>()
   |      - expected this type parameter
...
10 |     get(&1u32); // 1 error
   |     --- ^^^^^ expected `&K`, found `&u32`
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected reference `&K`
              found reference `&u32`
note: function defined here
  --> src/main.rs:13:4
   |
13 | fn get<Q>(k: &Q)
   |    ^^^    -----

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 1 previous error

However, &u32 also satisfy trait bound of get.

if I change 1 to 2, it is OK.

if I remove 3, it is OK.

if I add 4, it is OK.

Why?

I think it's possible that the trait bound on f temporarily changed the constraints that already existed on u32, thus affecting the get function's choice of the type parameter Q. If that's the case, how did the u32 bound change? How does get select the type parameter?

Or is there a simpler way to understand it?

Solution:

This is a case of a somewhat less-known detail of Rust's inference: if there are two possible method candidates, and one of them comes from a trait bound of the function (and the other does not), Rust prefers the trait bound's method, unless explicitly told otherwise.

In your case, there are two possible candidates for get::<_>(&u32) (written as get(&1u32)) in the generic parameter Q. The first is to choose Q = u32, the second being Q = K. Since the method requires u32: B<Q>, even though both satisfy this bound, Q = K is selected, because it comes from a trait bound in the caller method, while Q = u32 comes from the environment (the impl impl<T> B<T> for T).

This special case is done to aid inference in cases like the following:

fn foo<T: Into<String>>(v: T) {
    let s = v.into();
}

Here, the programmer obviously wants s to have type String. However, there are actually two candidates here: one being <T as Into<String>>::into(), but the other is the reflexive impl <T as Into<T>>::into(), provided by std. In order for this to not be ambiguous, Rust prefers the trait bound unless explicitly told otherwise.

Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login