10
votes

It's not exactly an idiomatic MVCE, but it should illustrate the question. Given the following code:

fn foo() -> Result<String, i32> {
    return Ok("world".to_string());
}

fn bar() -> Result<String, i32> {
    let mut value = String::new();
    value.push_str(&try!(foo())); // this line here

    return Ok("Hello ".to_string() + &value);
}

fn main() {
    match bar() {
        Ok(message) => println!("{}", message),
        _ => return,
    }
}

Rust returns the error:

<std macros>:3:43: 3:46 error: mismatched types:
expected str,
found collections::string::String
(expected str,
found struct collections::string::String) [E0308]
<std macros>:3 $ crate:: result:: Result:: Ok ( val ) => val , $ crate:: result:: Result::
<std macros>:1:1: 6:48 note: in expansion of try!
<std macros>:3:43: 3:46 help: run rustc --explain E0308 to see a detailed explanation
error: aborting due to previous error

If I instead capture the result of try! and separately apply & to the result, it works (and prints out Hello world):

fn foo() -> Result<String, i32> {
    return Ok("world".to_string());
}

fn bar() -> Result<String, i32> {
    let mut value = String::new();
    let foo_result = try!(foo()); // capture the result of try!
    value.push_str(&foo_result); // now use the result with &

    return Ok("Hello ".to_string() + &value);
}

fn main() {
    match bar() {
        Ok(message) => println!("{}", message),
        _ => return,
    }
}

Why does let foo_result = try!(foo()); value.push_str(&foo_result); work but value.push_str(&try!(foo())); not? From my naive perspective, they appear to be equivalent, so I'm not sure what key part of Rust I'm not understanding.

1
It's a very good MVCE. Not an answer, but still a little something I usually do: is.gd/PZKWz0ArtemGr
You can resolve even with value.push_str(&&try!(foo()));eulerdisk
Thanks, @Shepmaster. You're totally right.Cornstalks

1 Answers

13
votes

It seems that the compiler handles the coercion of a block differently. try!() is expanded to a match block, and the compiler fails to auto-deref it. Your problem can be abbreviated like below:

fn f(_: &str) {}

fn main() {
    let x = "Akemi Homura".to_owned();

    f(&x); // OK
    f(&(x)); // OK
    f(&{x}); // Error
}

I think this is a bug of the compiler. As stated in RFC 401, the compiler should be able to coerce blocks with appropriate types.

blocks, if a block has type U, then the last expression in the block (if it is not semicolon-terminated) is a coercion site to U. This includes blocks which are part of control flow statements, such as if/else, if the block has a known type.

As a workaround, I recommend you to convert String into &str directly, using &*try() or &try()[..]. Both have the same meaning, although I prefer the former slightly.

I opened an issue to track this. https://github.com/rust-lang/rust/issues/26978