How to define and work with a Rust-like result type in NuShell

Motivation

Rust—among other modern programming languages—has a data type Result<T, E> in its standard library, that allows us to represent error states in a program directly in code. Using data structures to represent errors in code is…


This content originally appeared on DEV Community and was authored by Friedrich Kurz

Motivation

Rust—among other modern programming languages—has a data type Result<T, E> in its standard library, 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 you call may not succeed. (If you don't ignore the return value of course. Modeling errors in code doesn't save you from that, unfortunately.) Combining the use of Result return types with Rust's pattern matching also improves code legibility, in my opinion.

Error handling in NuShell

NuShell 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 try/catch control structures to capture and deal with errors, however, like more imperatively leaning languages (for example, Java or Python). There is no result type in the standard library 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):

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?

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:

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> 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:

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.

use std log

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}
}


This content originally appeared on DEV Community and was authored by Friedrich Kurz


Print Share Comment Cite Upload Translate Updates
APA

Friedrich Kurz | Sciencx (2024-06-21T11:51:00+00:00) How to define and work with a Rust-like result type in NuShell. Retrieved from https://www.scien.cx/2024/06/21/how-to-define-and-work-with-a-rust-like-result-type-in-nushell/

MLA
" » How to define and work with a Rust-like result type in NuShell." Friedrich Kurz | Sciencx - Friday June 21, 2024, https://www.scien.cx/2024/06/21/how-to-define-and-work-with-a-rust-like-result-type-in-nushell/
HARVARD
Friedrich Kurz | Sciencx Friday June 21, 2024 » How to define and work with a Rust-like result type in NuShell., viewed ,<https://www.scien.cx/2024/06/21/how-to-define-and-work-with-a-rust-like-result-type-in-nushell/>
VANCOUVER
Friedrich Kurz | Sciencx - » How to define and work with a Rust-like result type in NuShell. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/06/21/how-to-define-and-work-with-a-rust-like-result-type-in-nushell/
CHICAGO
" » How to define and work with a Rust-like result type in NuShell." Friedrich Kurz | Sciencx - Accessed . https://www.scien.cx/2024/06/21/how-to-define-and-work-with-a-rust-like-result-type-in-nushell/
IEEE
" » How to define and work with a Rust-like result type in NuShell." Friedrich Kurz | Sciencx [Online]. Available: https://www.scien.cx/2024/06/21/how-to-define-and-work-with-a-rust-like-result-type-in-nushell/. [Accessed: ]
rf:citation
» How to define and work with a Rust-like result type in NuShell | Friedrich Kurz | Sciencx | https://www.scien.cx/2024/06/21/how-to-define-and-work-with-a-rust-like-result-type-in-nushell/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.