Stream: beginners

Topic: ✔ "unwraping" Result in a test case


view this post on Zulip Joseph Montanaro (Oct 03 2025 at 17:27):

I have a function that can fail. I'd like to write a test case for that function, like so:

expect
    Ok(parsed) = parse_rule("34|77")
    parsed == { before: 34, after: 77}

This results in the following error:

This expression is used in an unexpected way:

39│      Ok(parsed) = parse_rule("34|77")
                      ^^^^^^^^^^^^^^^^^^^

This parse_rule call produces:

    Result {
        after : Int Unsigned64,
        before : Int Unsigned64,
    } [
        InvalidNumStr,
        NotFound,
    ]

But you are trying to use it as:

    [Ok *]

However, if I write it like this:

expect
    parsed = parse_rule("34|77")
    parsed == Ok({ before: 34, after: 77})

it works fine.

The error about [Ok *] made me wonder whether the type inference for parsed was just too general, so I tried adding a type annotation like this:

parsed : { before: U64, after: U64 }
Ok(parsed) = parse_rule("34|77")

But that fails with "Nothing is named parsed in this scope."

Obviously I _could_ just use the second form (parsed == Ok({...}), but I'd like to understand why I can't use the first form. I know you can pattern match when assigning, e.g. { a, b } = some_record, so what's different here?

view this post on Zulip Kiryl Dziamura (Oct 03 2025 at 17:30):

The Result may return an error as well, so it's not correct to just destruct it as Ok at the type level

view this post on Zulip Kiryl Dziamura (Oct 03 2025 at 17:32):

In other words, you can't do this:

parsed = Err("error")
Ok(x) = parsed

view this post on Zulip Joseph Montanaro (Oct 03 2025 at 17:33):

Ok, so this is what Rust calls the distinction between "refutable" and "irrefutable" patterns? That makes sense, so what's the preferred way to say "I just want to early-exit if this isn't Ok"? I can't use ? in an expect, it seems.

view this post on Zulip Anton (Oct 03 2025 at 17:47):

I think an early exit is not possible, but you could use Result.map_ok.

view this post on Zulip Joseph Montanaro (Oct 03 2025 at 19:38):

Ok, thanks! I think I'll just stick with == Ok(...) for now, it seems simplest.

view this post on Zulip Notification Bot (Oct 03 2025 at 19:38):

Joseph Montanaro has marked this topic as resolved.

view this post on Zulip Kiryl Dziamura (Oct 03 2025 at 19:39):

I just want to say it's not only simplest, it's correct idiomatically

view this post on Zulip Kiryl Dziamura (Oct 03 2025 at 19:43):

The function returns a particular value and the test asserts this case. It's not only the record inside of the result important here but the whole result of the function! :slight_smile:
But I admit I could misunderstand the problem.

view this post on Zulip Joseph Montanaro (Oct 03 2025 at 19:45):

It makes sense! I was mostly wondering about more complex future cases, where I might need to work with several Results in sequence as part of the same test. E.g. in Rust I might do something like this:

#[test]
fn test_thing() {
    let app = App::load()
        .expect("Failed to load the app.");
    app.set_some_state("blahblah")
        .expect("failed to set state");
    assert_eq!(get_some_state("blahblah"), Ok("blahblah"))
}

But I'm not sure how I would approach that in Roc, outside of using several nested whens or Result.map_oks. But that doesn't feel very nice.

view this post on Zulip Brendan Hansknecht (Oct 04 2025 at 04:49):

Yeah, I think an unwrapping crash would be nice in tests... if it only works in tests....not sure how we could limited to only tests though

view this post on Zulip Brendan Hansknecht (Oct 04 2025 at 04:51):

Something like this should work

expect = |res| {
    match(res) {
        Ok(x) -> x
        Err(e) -> crash(e)
    }
}

view this post on Zulip Kiryl Dziamura (Oct 04 2025 at 08:59):

I assume it's already possible if you write a helper function that has expect statements. Then call it within another expect so if it returned anything and didn't crash - the test is passed

view this post on Zulip Brendan Hansknecht (Oct 04 2025 at 15:33):

Yeah, exactly. Though I don't think expects narrow types. So you need a match like above

view this post on Zulip Joseph Montanaro (Oct 04 2025 at 20:15):

That's cool, I didn't realize you could straight-up crash in an expect and it wouldn't interfere with the rest of the expects. Thanks!

Might make a nice addition to Result in the stdlib, although like with Rust's unwrap() it would open the door to some abuse.

view this post on Zulip Brendan Hansknecht (Oct 04 2025 at 20:36):

Yeah, I think it is more an open secret than anything that should every officially be in the stdlib

view this post on Zulip Brendan Hansknecht (Oct 04 2025 at 20:37):

We don't want to accidentally promote the use anywhere else

view this post on Zulip Richard Feldman (Oct 04 2025 at 21:37):

yeah I consider unwrap and expect to be design mistakes in Rust's stdlib :smile:

view this post on Zulip Richard Feldman (Oct 04 2025 at 21:38):

I definitely don't think we should have them either!

view this post on Zulip Richard Feldman (Oct 04 2025 at 21:39):

We've talked in the past about having ? inside expect "unwrap" by automatically failing the expect rather than doing an early return

view this post on Zulip Richard Feldman (Oct 04 2025 at 21:40):

I like that idea but we haven't implemented it yet

view this post on Zulip Joseph Montanaro (Oct 04 2025 at 22:11):

fast-failing expect via ? would be awesome, I think that's actually the first thing I tried

view this post on Zulip Joseph Montanaro (Oct 04 2025 at 22:17):

Actually, I just realized there's a super-simple way to do this that works already:

val = thing_that_can_fail(...) ?? crash "thing failed"
val == "whatever"

Being able to use ? in expects would be even easier, plus it could do things like print the actual value inside the Err, but for now this is easier than having to have a helper function available everywhere.


Last updated: Oct 18 2025 at 12:13 UTC