How to define and work with a Rust-like result type in NuShell
Published: 2024-06-21 11:51:00

## Motivation

Rust—among other modern programming languages—has a data type [`Result<T, E>` in its standard library][1], that allows us to represent error states in a program directly in code. Using data structures to represent errors in code is a pattern known mostly from purely functional programming languages but has gained some traction in programming languages that follow a less restrictive language paradigm (like Rust). 

The idea of modelling errors in code is that—following the functional credo—everything the function does should be contained in the return value. Side effects, like errors, should be avoided wherever possible. 

This is a nice pattern, since it forces you to address that code may not succeed. (If you don't ignore the return value, of course. :D) Combining the use of `Result` return types with [Rust's pattern matching][3] also improves code legibility, in my opinion.

## Error handling in NuShell

[NuShell][5] is a very modern shell and shell language that draws some heavy inspiration from Rust and that I really enjoy writing small programs and glue code in (especially for CI/CD pipelines). 

In contrast to Rust, NuShell has a [`try`/`catch`][2] control structure to capture and deal with errors. There is no result type in the [standard library][4] at the time of writing. 

NuShell's `try`/`catch`, moreover, has the major downside, that you cannot react to specifics of an error, since the `catch` block doesn't receive any parameter like an exception object, that would allow us introspection on what went wrong. 

So what can we do? Well, we may just define a Result type ourselves and use it. Since NuShell also has pattern matching using the `match` keyword, we can write some pretty readable code with it.

Consider, for example, malformed URLs when using the `http` command (in this case the  protocol is missing):

```nushell
nu> http get --full www.google.com
Error: nu::shell::unsupported_input

  × Unsupported input
   ╭─[entry #64:1:1]
 1 │ http get --full www.google.com
   · ────┬───        ───────┬──────
   ·     │                  ╰── value: '"www.google.com"'
   ·     ╰── Incomplete or incorrect URL. Expected a full URL, e.g., https://www.example.com
```

The code above will crash. As mentioned before, we could use `try`/`catch`.  But the problem remains, how do we enable the calling code to react to errors?

```nu
use std log 

try {
    return http get --full www.friedrichkurz.me # Missing protocol
} catch {
    log error "GET \"https://www.nulldomain.io\" failed."
     # Now what?
}
```

Using a result type (and some convenience conversion functions `into ok` and `into error`), we can write a safe http get function as follows:

```nu
def safe_get [url: string] { 
  try {
    let response = http get --full $url
    $response | into ok
  } catch {
    {url: $url} | into error
  }
}
```

We could use it in our code like this:

```nu
nu> match (safe_get "https://www.google.com") {                                    
    {ok: $response} => { print $"request succeeded: ($response.status)" },
    {error: $_} => { print "request failed" }
}
request succeeded: 200
```

And for the failure case:

```nu
match (safe_get "www.google.com") {                                              
    {ok: $response} => { print $"request succeeded: ($response.status)" },
    {error: $_} => { print "request failed" }
}
request failed
```

Now the calling code can react to failure by disambiguating the return values and processing the attached data.

## Addendum

Here are the helper functions `into ok` and  `into error` for completeness sake.
```nu
export def "into ok" [value?: any] {
    let v = if $value == null { $in } else { $value }

    {ok: $v}
}

export def "into error" [cause?: any] {
    let c = if $cause == null { $in } else { $cause }

    {error: $c}
}
```

[1]: https://doc.rust-lang.org/std/result/
[2]: https://www.nushell.sh/commands/docs/try.html
[3]: https://doc.rust-lang.org/book/ch18-00-patterns.html
[4]: https://github.com/nushell/nushell/tree/main/crates/nu-std
[5]: https://nushell.sh