The MoonBit Update——0617

Language Update

  • Supported error handling:
  1. Functions can now specify return types with error handling using the syntax Int!String. The following example means the function returns an Int under normal conditions, and throws an error of type String otherwise.

fn div(x: Int, y: Int) -> Int!String { .. }

  1. The raise keyword is used to interrupt the current control flow and throw an error. For example:

fn div(x: Int, y: Int) -> Int!String {

  if y == 0 { raise "divide by 0" }

  x / y

}

  1. The expression try { expr0 } catch { pattern1 => expr1; pattern2 => expr2; .. } can be used to catch errors thrown in expr0 and handle them with pattern matching. For example, the following function calls the div function and prints the error message if an error is thrown, then returns a default value:

fn div_with_default(x: Int, y: Int, default: Int) -> Int {

  try {

    div(x, y)!

  } catch {

    s => { println(s); default }

  }

}

  1. Additionally, the suffix operators ! and !! are available for error handling. These operators can only be used on function calls:

f(x)! rethrows any error immediately.

f(x)!! panics on any error, equivalent to:


try { f(x)! } catch { _ => panic() }

Function calls include method calls, infix operators, and pipeline operators, such as:


fn init {

  let _ = x.f()!!

  let _ = (x + y)!!

  let _ = (x |> f)!!

}

  1. Last, functions that might throw errors but do not use any of the above error handling mechanisms will result in an “unhandled error” error.
  • Support Map literal syntax.

fn init {

  // Keys must be literals

  let m1 : Map[String, Int] = { "x": 1, "y": 2 }

  let m2 : Map[Int, String] = { 1: "x", 2: "y" }

}

IDE Update

  • Fixed a bug where methods from the builtin package would appear twice during autocompletion.

  • Fixed a bug where the Byte type was missing from autocompletion options.

Build System Update

  • Added support for internal packages. These packages are placed in a directory named internal and can only be imported by packages rooted in the parent directory of internal.

For example, if a package is located at username/hello/x/internal/a, its parent directory is username/hello/x. Only username/hello/x and its subpackages (e.g., username/hello/x/a) can import username/hello/x/internal/a. However, username/hello/y cannot import this package.

1 Like

This new error handling looks very cool, and I’m trying to convert my existing code that was returning Result[MyType,String] to instead return MyType!String.

I’m finding that my code uses Array.iter a fair amount, and I’m trying to figure out how to idiomatically raise an error within the anonymous iter function to take advantage of this new language feature, but am drawing a blank. Do you have any recommendations?

A new iter function needs to be implemented with something like:

fn iter_exn[T, Err](arr : Array[T], iter : (T) -> Unit!Err) -> Unit!Err {
  for i = 0; i < arr.length(); i = i + 1 {
    iter(arr[i])!
  }
}
1 Like

Is this new error handling mechanism with raise/try/catch not redundant with the existing:

  • Result[T, E] type, Err(), Ok()
  • Combined with pattern matching and the convenience of the “?” operator to propagate errors

Or are there maybe reasons for it? For example, I thought we had the following:

  1. Recoverable errors are encoded in Result types (Err, Ok). The shorthand “?” brings the convenience of bubbling things easily like raise/try/catch while preserving type safety. Hence best of both worlds between errors as values and exception handling. Patterns matching and options complete the story
  2. And fatal errors are brought about with “panic/abort”

Wondering where this new error handling mechanism fits if we already have 1.? Wouldn’t we now have 2 different interfaces for functions (those that return result types and those that are _exn)

The new error handling mechanism is a second-class language feature, which enables:

  1. More informative static analysis: Compiler reports warnings on unhandled errors
  2. Better performance: The Result type needs heap allocation, though possibly optimized away. The new error handling is guaranteed zero-allocation
  3. More straightforward documentation
1 Like

@Yu-zh Ah thank you the second class nature clarifies it and interesting for the performance impact in hotpaths, good to know there is now a zero cost alternative