Skip to main content

2025-06-03

Β· 4 min read

Language Updates​

1. Omission of ! for Effectful Calls​

Calling effectful functions (those that may throw errors or are asynchronous) no longer requires explicitly appending !. The IDE will automatically underline functions that may throw and render asynchronous functions in italics using semantic highlighting.

2. f?(..) Replaced by try? f(..) Expression​

try? expr is equivalent to try Ok(expr) catch { err => Err(err) }, allowing errors to be converted into the Result type. Compared to the original f?(..), try? is more flexible and can handle multiple function calls with potential errors at once, for example:

try? {
f(..)
g(..)
}

When migrating, special attention should be paid to the following case: f?(g!(..)) cannot be simply rewritten as try? f(g(..)), because this would redirect the error from g into the Result. In this case, the result of g should first be extracted with let, and then passed into try?.

3. Add Support for Error Polymorphism in Higher-Order Functions​

fn[X] Array::each(arr: Array[X], f: (X) -> Unit?Error) -> Unit?Error {
for x in arr {
f(x)
}
}

fn main {
let arr = ["a", "b", "c"]
arr.each(println) // no error
println(try? arr.each(fail)) // outputs Err(Failure)
}

The syntax for error polymorphism is (..) -> T?Error. At the call site, ?Error can be replaced with an actual error type, or it can be elided to represent a case where no error may occur.

Specifically, in the example above,

  • If the argument f does not throw, ?Error will be replaced with "no error", and the entire each call will not throw either.
  • If f throws an error, then ?Error will be replaced with the actual error type of f, and the entire each call will throw the same type of error.

4. Deprecation of fn meth(self: T) as Both Method and Function​

Previously, methods defined in the form fn meth(self: T) acted as both methods and regular functions, and could be invoked using either meth(..) or @pkg.meth(..). This behavior treating methods as regular functionsis now deprecated. The compiler will issue a warning when such usage is detected.

As a result of this change, the recommended API design is:

  • Always define methods using the form fn T::meth(..). Avoid using fn meth(self: T) in new code. (This syntax itself may be removed in the future.)

    • Any API that is associated with a specific type should be defined as a method, unless there is a compelling reason not to.

5. The dot syntax now supports anonymous functions using _, written as _.meth(..).​

At the same time, the right-hand side of the pipe operator |> also supports this form of anonymous method calls.

When using this syntax, it's important to ensure that the type of _ can be determined from the context; otherwise, the method cannot be resolved.

Example:

fn main {
let array_of_array = [ [], [ 1 ], [ 1, 2 ] ]
let lens = array_of_array.map(_.length())
lens |> _.each(println)
}

Compared with writing x.meth(..) directly, the advantage of x |> _.meth(..) is that it allows method calls to be embedded into an existing pipeline. For example: x |> regular_func(..) |> _.meth(..).

6. In method declarations using the fn TypeName::meth(..)​

you can use Self to refer to TypeName, which helps shorten the type signature.

type MyLongTypeName Int

fn MyLongTypeName::to_string(x : Self) -> String {
x._.to_string()
}

If TypeName has parameters, using Self will also require those parameters to be provided.

7. The behavior of implementing trait via method definitions has been officially removed.​

8. The position of type parameters in function declarations has been changed from fn f[..] to fn[..] f, making it consistent with impl.​

This change can be automatically migrated using the formatting tool.

9. The format of methods in .mbti files generated by moon info has changed.​

Previously, all methods were merged into a single large impl block. Now, each method is listed individually in .mbti, consistent with MoonBit's own syntax.

10. The syntax for asynchronous functions has been adjusted back to async(..) -> T, and !Async syntax (used for error handling and async) has been removed due to incompatibility.​

This change can also be automatically migrated using the formatting tool.

11. The literal suffix for Float types now supports .314f.​

Standard Library Updates​

  • The Show::output implementation for Char has changed. Now, all non-printable characters will be escaped, including: Control, Format, Surrogate, Private Use, Unassigned, and Separator (except space).

Toolchain Updates​

  • A single .mbt.md file now supports external dependencies.

Usage example:

---
moonbit:
deps:
moonbitlang/x: 0.4.23
# moonbitlang/x:
# path: "/Users/flash/projects/x" # local deps
backend:
js
---