Why does rust require two ampersands in this scenario?

I've run into this issue using rocket that I'm trying to make sense of. I have an endpoint that takes a JSON body that's deserialized into a struct that is then borrowed by a function. Depending on how it's written, it seems to need two ampersands even though it's just a simple reference and I'd like to know why:

[#derive(Deserialize)]
struct UserInput {
  // ...
}

fn uses_input(input: &UserInput) {
  // ...
}

fn endpoint(input: Result<Json<UserInput>, JsonError>) -> Result<whatever> {
  // Fails with `expected struct `UserInput`, found struct `rocket_contrib::json::Json``
  uses_input(&input?);
  // fails with `expected struct `UserInput`, found struct `rocket_contrib::json::Json`
  uses_input(&(input?));
  // Works
  uses_input(&&input?);
  // Works
  let input = input?;
  uses_input(&input);
  // ...
}

Can anyone explain what's going on here?

I’m not entirely sure how you are getting the &UserInput from the &AnalyticsFilter...

sure, Json<T> implements Deref<Target=T>, so &input? which is originally of type &Json<AnalyticsFilter> can be (implicitly) coerced into &AnalyticsFilter. Maybe you can tell us more about where this type “AnalyticsFilter” comes from and how it converts into UserInput :wink:

Sorry, typo on my part, trying to simplify the example code to focus on the issue and missed a spot, that should be UserInput

I see. So the minimal example is something like this

use std::ops::Deref;
struct A;
struct B;

impl Deref for A {
    type Target = B;
    fn deref(&self) -> &B {
        &B
    }
}

fn foo(a: Option<A>) -> Option<()> {
    // doesn’t work
//  bar(&a?);
    // works
    bar(&&a?);

    Some(())
}

fn bar(b: &B) {
    
}

(playground)

I guess the answer is: Type inference gets unhappy with your code (for some reason) and rejects to insert a coercion in one case, but is happy to do so in the other case. Note that double-references like returned by writing && can implicitly coerce back down to &.

This might be a bug and might be fixed in the future... In the meantime a nicer alternative to &&input? might be to write &*input?.

2 Likes

Going into detail, after desugaring we have something like

fn foo(a: Option<A>) -> Option<()> {
    // doesn’t work
    bar(&match a.into_result() {
        Ok(x) => x,
        Err(y) => return Try::from_error(From::from(y)),
    });

    Some(())
}

the error is

error[E0308]: mismatched types
  --> src/lib.rs:17:18
   |
17 |         Ok(x) => x,
   |                  ^ expected struct `B`, found struct `A`

(playground)

So it seems like Rust’s type inferrence thought to itself “oh, nice, a reference to a match expression, passed to a function expecting &B; well that must mean that the match expression has type B, case closed”. Then a little later it realizes that if the match expression has type B then every match arm must return a value of type B, in particular the x in the first arm must be of type B. There’s a type mismatch now, and right hand sides of match arms are unfortunately no coercion sites.

On the other hand with &&, we have something like

fn foo(a: Option<A>) -> Option<()> {
    // doesn’t work
    bar(&&match a.into_result() {
        Ok(x) => x,
        Err(y) => return Try::from_error(From::from(y)),
    });

    Some(())
}

(which compiles, try modifying the playground above)
in which case the whole situation is “a reference-to-a-reference is passed to a function expecting a reference-to-B; ah type mismatch but wait – function arguments are coercion sites, so we’ll introduce a coercion. Now what is coerced to what? Type-checking the match statement reveals that it’s of type A, so we need to coerce &&A into &B; no problem!


In case that isn't clear, I'm just explaining just how I make sense of the error messages the compiler gives me by guessing what might be going on here. I have no idea how the type checker actually operates.

4 Likes

You can also use as _ to block too much being inferred.

I wrote up the examples that follow, and afterwards found Issue 23014 and Issue 57749. Sure enough, if you make B unsized (e.g. type A = String; type B = str;), the failing examples succeed (n.b. I didn't try every one).


I wondered how much ?'s match and Into-ness had to do with it and tried a few more things:

// A:Works
bar(&a.unwrap());
// B: Error: mismatched types
bar(&{ a.unwrap() });
// C: Works again
bar(&&{ a.unwrap() });

There are no match arms (or Into) here, so I think it's an introduction of a block that does it.

These next examples are based on Option, but you can try them with Result by using a.as_ref().map_err(|&e| e) to remove the & from the Err variant.

// E: Works 
bar(a.as_ref()?);
// F: Error: mismatched types
bar(&a.as_ref()?);
// G: Works again
bar(&&a.as_ref()?);

Or these, which work with Result and make the block explicit.

// I: Works
bar(a.as_ref().unwrap());
// J: Works
bar(&a.as_ref().unwrap());
// K: Error: mismatched types
bar(&{ a.as_ref().unwrap() });
// L: Works again
bar(&&{ a.as_ref().unwrap() });
4 Likes