Examples using Result<()> fail to compile

Hi!

First post from a new Rust developer. I've taken some code I had in Python and previously ported to Go to give myself a real project. The code requires the AWS SDK (rusoto), HTTP (reqwest) and JSON (serde_json).

I saw lots of examples while working through this where a question mark was used at the end of a line, the function declared as returning Result<()> and it returned the value Ok(()). There's a few examples of this pattern at serde_json - Rust

I couldn't get this to compile, so I wrote the smallest program I could:

fn main() -> Result<()> {
    println!("Hello, world!");
    Ok(())
}

When compiling, I see:

▶ cargo run
   Compiling result v0.1.0 (/Users/thebazzer/rust/result)
error[E0107]: this enum takes 2 type arguments but only 1 type argument was supplied
   --> src/main.rs:1:14
    |
1   | fn main() -> Result<()> {
    |              ^^^^^^ -- supplied 1 type argument
    |              |
    |              expected 2 type arguments
    |
note: enum defined here, with 2 type parameters: `T`, `E`
   --> /Users/thebazzer/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:241:10
    |
241 | pub enum Result<T, E> {
    |          ^^^^^^ -  -
help: add missing type argument
    |
1   | fn main() -> Result<(), E> {
    |                       ^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0107`.
error: could not compile `result`

To learn more, run the command again with --verbose.

If I try to fix the problem as suggested:

fn main() -> Result<(), E> {
    println!("Hello, world!");
    Ok(())
}

Then I see:

▶ cargo run
   Compiling result v0.1.0 (/Users/thebazzer/rust/result)
error[E0412]: cannot find type `E` in this scope
   --> src/main.rs:1:25
    |
1   | fn main() -> Result<(), E> {
    |                         ^ help: a trait with a similar name exists: `Eq`
    |
   ::: /Users/thebazzer/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/cmp.rs:268:1
    |
268 | pub trait Eq: PartialEq<Self> {
    | ----------------------------- similarly named trait `Eq` defined here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0412`.
error: could not compile `result`

To learn more, run the command again with --verbose.

Frankly, I'm somewhat baffled. In the end, I removed the question marks, and used match to process the return value, which did work:

let sitr:Result<SigninTokenBody> = serde_json::from_str(&*body);
match sitr {
    Ok( json ) => {
        let sign_in_url = get_signin_url( json.signin_token );
        println!( "{}", sign_in_url );
        },
    Err( err) => println!( "JSON Error: {}", err ),
}

If anyone could shed any light on this, I would appreciate it!

Thanks!

You've missed the use serde_json::Result; at the top, which is a type alias.

2 Likes

Note that if you use serde_json::Result without renaming it, it will shadow std::result::Result.

Some other alternatives:

Use and rename serde_json::Result:

use serde_json::Result as SJResult;
fn main() -> SJResult<()> {
    println!("Hello, world!");
    Ok(())
}

Use and rename serde_json::Error:

use serde_json::Error as SJError;
// Note that this is `std::result::Result<(), serde_json::Error>`
// Which is the same as the alias `serde_json::Result<()>`
fn main() -> Result<(), SJError> {
    println!("Hello, world!");
    Ok(())
}

Just use the paths:

fn main() -> serde_json::Result<()> {
    println!("Hello, world!");
    Ok(())
}

Some combination:

use serde_json as sj;
fn main() -> sj::Result<()> {
    println!("Hello, world!");
    Ok(())
}
2 Likes

This, in my opinion. This way, you see immediately that it's not the actual std::result::Result<T, E>.

Redefining Result with an incompatible type alias to me appears to be a code smell/bad ergonomics. I really don't understand why, for instance, some Rust documentation recommends such aliases. I know that I've spent confusing time disambiguating std::result::Result and std::io::Result until I've adopted the strict rule to never use any Result that's not std::result::Result.

Code readability matters, in my opinion. I want to be able to read a method signature without having to consult the module imports, and so should new developers. (One of the reasons people put up with the borrow checker is that local reasoning is supposedly easier to get right than global reasoning; so I'm a bit confused why some prefer shadowing of types in this way.)

I think what anyhow does is quite nice. It declares an alias

type Result<T, E = Error> = Result<T, E>;

... where there still are two arguments, but the E type has a default. I think that makes a great combination of flexibility, no surprises, being easy to read and not to verbose.

1 Like