What am I not understanding about Result?


#1

Code is here: https://play.rust-lang.org/?gist=1e1b15234762a28e6c80&version=stable

I would think that the result type, when unwrapped, would be an InvalidQueen, right? But when I run that code, I keep getting a Queen.

So, what am I not getting here? Clearly something hasn’t penetrated my skull quite yet.


#2

Result<TypeIfOk, TypeIfErr> -> if the function returns Ok, it will be of type Ok(value of TypeIfOk), if it returns Err it will be of type Err(value of TypeIfErr).
What .unwrap() does is:

  • If I get Ok, than get me the value of TypeIfOk(get me the value that Ok stores), so I don’t have to match on it, or use some other mechanism to get the value that is stored in it.
  • If I get Err, I don’t care, just crash the application(by crash I mean Rust’s panic).

That’s basically what the user says to the compiler when calling unwrap() on Result.


#3

Result<Queen, InvalidQueen> contains two different types.

Rust isn’t dependently typed, so Queen::new((1, 2)).unwrap() can’t return two different types according to the value of a boolean test.

So Queen::new((1, 2)).unwrap() is of type Queen, or it panics and shows the InvalidQueen.


#4

Since I’ve hard-coded Queen::new to always return Err(InvalidQueen),then my call to uwrap() should panic, right? But when I run that playground example, I don’t get a panic. I get a failing test.


#5

How I see it is that .unwrap() will panic, but at runtime, this is an error at compile-time.
.unwrap() returns type T or panics. Because type T in this case is Queen, at compile time it fails because InvalidQueen is not the same type that unwrap() returns, which is Queen, and assert_eq expects to compare the same types.


#6

@leonardo @LilianMoraru

To be clear, Ok and Err are not types. They are variants. Result<T,E> can have two values, one is Ok(T), the other is Err(E)

to answer the original question, use .err().unwrap() or unwrap_err(). unwrap() will unwrap the result if it is Ok and panic otherwise.


#7

Thanks. I’m fuzzy on the difference between a type and variant. If I have

enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

Then Quit is not a type? It’s just a variant of the Message type?

I ended up solving my original problem in a probably cheesier way. This was for implementing a new problem in the Rust track of Exercism. The full PR is here: https://github.com/exercism/xrust/pull/89, but here’s an example test.

fn test_queen_with_invalid_position() {
    let white_queen = Queen::new((-1,2));
    match white_queen {
        Ok(_) => panic!("This queen should be invalid"),
        Err(_) => assert!(true)
}

(note that my example solution is intentionally brute-force; I don’t doubt that there’s better ways of getting these tests to pass)


#8

Note that you can make your code a bit more idiomatic by using the err() and expect() methods:

fn test_queen_with_invalid_position() {
    let white_queen = Queen::new((-1,2));
    white_queen.err().expect("This queen should be invalid");
}

It makes it (somewhat) clearer that you are expecting and error.


#9

Thanks. My sense of idiomatic Rust is not great.


#10

Yes, Message::Quit or Message::Move { x: 1, y: 2 } are expressions of type Message; even though variants in enum declarations look like struct declarations, they don’t create separate types. (There was a proposal that would change this, actually, not sure what happened to it…)


#11

Yes, Quit is a variant. It’s uppercase, but it’s ultimately a value, not a type. Similarly, Write is also a value, though on its own Write isn’t a complete value, it has a contained String value too. Both Quit and Write are variants, and cannot be used as types.

This is better, btw:

fn test_queen_with_invalid_position() {
    let white_queen = Queen::new((-1,2));
    assert!(white_queen.is_err())
}

#12

Thanks for pointing out is_err. That has improved our tests. I’m also trying out an alternate API for creating Queen structs. I find this better, but am not sure about having to Unwrap every usage of can_attack.


#13

Do not unwrap() if you can avoid it. Or at least expect().

Also a function called can_attack() should most probably return a bool.


#14

Agreed that it should return a bool. We decided to implement it this way:

I’ve heard the “don’t use unwrap()” advice before. But I’m not sure how to follow it in this case. In the test suite for this exercise, I’ve ended up unwrapping the results of Queen::new because that seemed most expedient. I guess I could go the expect route and have some a custom panic message, but it didn’t seem necessary considering the usage of this code.


#15

It’s fine to use unwrap in tests if you’re expecting the Result to be Ok.


#16

Yes, I agree with jethrogb, in tests it is perfectly fine to unwrap().
In regular code, which can actually panic to the user, it should be avoided to do so.


#17

Result<(),()> makes for a very good bool.


#18
pub enum CanAttack { Totes, Nah }

#19

Only use unwrap if it failing indicates a bug. In your tests, being unable to create a queen at an obviously-valid condition is a bug.

BTW, I would prefer having a Position::new return a Result and having Queen::new take a Position.


#20

Rustaceans seem to use the same conventions as “functional” languages like Haskell. In Haskell they would define “data Boolean = True | False” and then code could say, “let truth = False”. In Rust they followed that practice. Likewise in both languages functions normally aren’t capitalized. A few conventions aren’t copied from Haskell so I won’t press this point too far. I would say though, from an exercise I did with some colleagues to define some coding conventions we would follow, there are only a few interesting name patterns and there are far more sorts of things to name.

Name_With_Initials_Capitalized_And_Underscores
NAME_WITH_CAPS_AND_UNDERSCORES
name_in_lower_case_with_underscores
NameWithInitialsCapitalized
nameWithInitialsExceptFirstCapitalized

From those five (no one likes the first one, so it’s really four), one must choose the convention for each of usually more than four kinds of naming opportunities:
functions, classes (c++ python et al) structs, enums, enum ‘variants’, global, local, and/or member variables, constants, macros, modules, files, executables, …