Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I take the reference of the result of try! with &?

Tags:

rust

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.

like image 556
Cornstalks Avatar asked Jul 11 '15 20:07

Cornstalks


1 Answers

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

like image 68
Barosl Lee Avatar answered Sep 28 '22 09:09

Barosl Lee