The use Expression

The Nesting Problem

i
  let ok_result = case parse_int("30") {
    Error(reason) -> Error(reason)
    Ok(total) ->
      case parse_int("6") {
        Error(reason) -> Error(reason)
        Ok(divisor) ->
          case safe_divide(total, divisor) {
            Error(reason) -> Error(reason)
            Ok(quotient) -> validate_positive(quotient)
          }
      }
  }
  io.println(string.inspect(ok_result))
i
Ok(5)
Error("division by zero")
i
  let err_result = case parse_int("30") {
    Error(reason) -> Error(reason)
    Ok(total) ->
      case parse_int("0") {
        Error(reason) -> Error(reason)
        Ok(divisor) ->
          case safe_divide(total, divisor) {
            Error(reason) -> Error(reason)
            Ok(quotient) -> validate_positive(quotient)
          }
      }
  }
  io.println(string.inspect(err_result))

Flattening with use

i
  let ok_result = {
    use total <- result.try(parse_int("30"))
    use divisor <- result.try(parse_int("6"))
    use quotient <- result.try(safe_divide(total, divisor))
    validate_positive(quotient)
  }
  io.println(string.inspect(ok_result))
i
Ok(5)
Error("division by zero")
i
  let err_result = {
    use total <- result.try(parse_int("30"))
    use divisor <- result.try(parse_int("0"))
    use quotient <- result.try(safe_divide(total, divisor))
    validate_positive(quotient)
  }
  io.println(string.inspect(err_result))

How use Works

f(a, b, fn(x) {
  body
})

use Beyond Results

i
  with_greeting("long form", fn(greeting) { io.println(greeting) })
i
  {
    use greeting <- with_greeting("with use")
    io.println(greeting)
  }
with_greeting("with use", fn(greeting) {
  io.println(greeting)
})
i
  {
    use item <- list.each([1, 2, 3])
    io.println(int.to_string(item))
  }
i
Hello, long form!
Hello, with use!
1
2
3

Guidelines for use

Further Reading

Check Understanding

What does use x <- result.try(expr) desugar to?

It desugars to:

result.try(expr, fn(x) {
  rest of block
})

result.try calls the callback with the value inside Ok(x), or returns the Error unchanged without calling the callback at all. The use line just removes the need to write the anonymous function explicitly.

Can you use use with a function that returns something other than Result?

Yes. use works with any function whose last parameter is a callback. The function controls what it does with that callback: result.try calls it only on success, list.each calls it once per element, and a custom function can call it however it likes. The Result type has no special relationship to use.

If result.try were replaced by a function that always calls its callback, could the block still short-circuit?

No. Short-circuiting is not a property of use itself — it is a property of result.try. use only rewrites syntax; whether the callback is called, skipped, or called multiple times depends entirely on what the receiving function does. A function that always calls its callback will always execute the rest of the block, regardless of how many use lines appear.

Exercises

Identify the callback (5 minutes)

Rewrite this use expression as an explicit callback without use:

let result = {
  use name <- result.try(find_user(id))
  use score <- result.try(fetch_score(name))
  Ok(score * 2)
}

What type must find_user and fetch_score return for this to compile?

Three-step chain (15 minutes)

Write three functions: read_config(path: String) -> Result(String, String) (returns Error if the path is empty), parse_port(raw: String) -> Result(Int, String) (wraps int.parse with a descriptive error), and validate_port(port: Int) -> Result(Int, String) (returns Error if the port is outside 1–65535). Chain all three with use so that read_config("") short-circuits before the other two are called.

Custom use target (15 minutes)

Write with_default(maybe: Option(a), default: a, callback: fn(a) -> b) -> b that calls callback with the value inside Some, or with default if the option is None. Then use it with use to unwrap an Option(Int) and double the result, falling back to 0 when the option is None.