Skip to main content

20260310 MoonBit v0.8.3 Release

· 7 min read

Language Updates

  1. Functions marked with #alias and #as_free_fn will no longer inherit attributes that they should not inherit, such as the #deprecated attribute. Now, the alias declared by #alias and the various attributes on the original function body can be controlled independently:

    // Neither the original nor the alias is deprecated
    #alias(g1)
    fn f1() -> Unit
    
    // Only the alias is deprecated
    #alias(g2, deprecated)
    fn f2() -> Unit
    
    // Only the original is deprecated
    #alias(g3)
    #deprecated
    fn f3() -> Unit
    
    // Both the original and the alias are deprecated
    #alias(g4, deprecated)
    #deprecated
    fn f4() -> Unit
  2. const declarations now support string concatenation and string interpolation:

    const Hello : String = "Hello"
    const HelloWorld : String = Hello + " world"
    const Message : String =
      $|========
      $|\{HelloWorld}
      $|========
  3. for .. in loops now support additional loop variables:

    // Sum the array xs
    for x in xs; sum = 0 {
      continue sum + x
    } nobreak {
      sum
    }

    With this new feature, for .. in loops can now maintain extra state in a functional style without using let mut.

  4. Deprecate implicit implementation of method-less traits.Previously, a trait with no methods was implicitly implemented by all types, without requiring an explicit impl Trait for Type. This behavior has now been deprecated, and using such implicit implementations will produce a warning. In the future, this behavior will be removed entirely, and all traits will uniformly require explicit implementation.

  5. Deprecate for { ... } infinite loop syntax.Previously, for { ... } could be used to write an infinite loop with no termination condition. This syntax has now been deprecated. Such loops should instead be written as for ;; { ... } or while true { ... }. This migration can be performed automatically using moon fmt. The motivation for this change is that we may add pattern matching support to for .. in loops in the future, such as for (x, y) in array_of_tuple. The for { .. } syntax conflicts syntactically with pattern matching on structs or Maps.

  6. Allow omitting the semicolon in for loops without an update clause.For for loops such as for i = 0; i < 10; { ... } (with no update clause), the semicolon after the loop condition can now be omitted, so it can be written as for i = 0; i < 10 { ... }.

  7. Remove the behavior allowing impl to always be called via .In MoonBit, an impl can only be called using x.f(..) syntax when both the impl and the type definition are in the same package. However, before the MoonBit beta release, impls in the current package could always be called using . syntax. This behavior was deprecated with a warning in the beta release, and has now been officially removed.

  8. FFI parameters without an explicitly annotated lifetime management mode are now treated as an error rather than a warning. In the future, we will officially change the default lifetime management mode for FFI parameters from #owned to #borrow. For now, the compiler will report an error for any FFI function whose lifetime management mode is not explicitly annotated.

  9. Fix referencing loop variable i in nobreak blocks of for i in x..y loops.Fixed an issue where the loop variable i could still be referenced inside the nobreak block of a for i in x..<y loop. Some code that accidentally relied on this behavior may now fail to compile.

  10. Improve error messages for mismatched top-level function signatures. Improved some error messages for mismatched top-level function signatures: the error output now shows only the differing parts of the signature, making it easier to locate the problem. For example:

    trait I {
      f(Self, flag1~ : Int, flag2~ : Int, flag3~ : Int) -> Unit
    }
    
    impl I for T with f(self, flag1~, flga2~, flag3~) {
      ...
    }

    Previously, the error message was:

    ...
      expected: (Self, flag1~ : Int, flag2~ : Int, flag3~ : Int) -> Unit
      actual:   (Self, flag1~ : Int, flga2~ : Int, flag3~ : Int) -> Unit

    The improved error message is now:

    ...
      expected: (.., flag2~ : _, ..) -> Unit
      actual:   (.., flga2~ : _, ..) -> Unit
  11. Added the #unsafe_skip_stub_check attribute, which can be used to skip checks on whether types in an FFI signature have a stable ABI. This attribute can be used by advanced users for more complex FFI experiments on the wasm backend. Note that once this check is skipped, FFI behavior becomes undefined and may change at any time, so this attribute should only be used for experimentation.

Toolchain Updates

  1. moon ide now includes a new analyze command for analyzing usage of a package’s public APIs. It prints the package’s public APIs in mbti format, and appends a comment to each API showing its usage information, including total usage count, usage count in tests, and whether the API is defined in exports.mbt. Below is an example of the output:

    $ moon ide analyze . # path to packages to be analyzed
    package "username/analyze"
    
    import {
    "username/analyze/util",
    }
    
    // Values
    pub const REPORT_CONST_TAG : String = "analyze-tag"  // usage: 2 (1 in test)
    
    #alias(analyze_raw)                                  // usage: 2 (1 in test)
    pub fn analyze_text(String) -> @util.Token           // usage: 2 (1 in test)
    
    pub fn build_report(String, @util.Level) -> Report   // usage: 2 (1 in test), in exports.mbt
    
    pub fn never_called_pub() -> String                  // usage: 0 (0 in test), in exports.mbt
    
    // Errors
    
    // Types and methods
    pub(all) struct Report {
      title : String                                     // usage: 1 (0 in test)
      score : Int                                        // usage: 1 (0 in test)
    
      fn new(String, Int) -> Report                      // usage: 2 (1 in test)
    }
    #as_free_fn(make_report)                             // usage: 2 (1 in test)
    pub fn Report::new(String, Int) -> Self              // usage: 0 (0 in test)
    pub impl Analyzer for Report                         // usage: 2 (1 in test)
    
    // Type aliases
    pub using @util {type Token as PublicResult}         // usage: 0 (0 in test)
    
    // Traits
    pub trait Analyzer {
      analyze(Self, String) -> @util.Token               // usage: 2 (1 in test)
    }

    moon ide analyze supports two ways of passing arguments:

    • moon ide analyze analyzes all packages in the current module.
    • moon ide analyze path/to/pkg1 path/to/pkg2 ... analyzes all specified packages, and can be used together with shell glob patterns. For example, moon ide analyze internal/* can be used to analyze all packages under internal.

    This command can be used together with AI-powered refactoring to quickly remove unused public APIs within a module. However, since the public APIs of non-internal packages may be used by users outside the module, this kind of refactoring is, in principle, only safe for internal packages. To distinguish between APIs in non-internal packages that are intended for internal use and those intended for external use, we have introduced a new convention: any public API intended for users outside the module should be defined in exports.mbt. Such APIs should not be removed, even if they have no usage within the module. moon ide analyze will specially highlight APIs defined in exports.mbt in its output, such as build_report and never_called_pub

  2. Support for supported-targets has been improved. The new syntax is now enabled, allowing users to explicitly declare which backends are supported using forms like "+js+wasm+wasm-gc", or to declare which backends are not supported using "+all-js".

    This can be defined in both moon.mod.json and moon.pkg. For a given package, the supported backends are the intersection of the two.

    Better error messages are now provided when a dependency graph cannot be constructed.

  3. The build system now tracks the compiler itself, reducing segfault issues caused by compiler version updates and cache mismatches.

  4. The mbtx script mode now supports input from stdin:

    $ echo "fn main {println(\"hello\")}" | moon run -
    $ echo "fn main {println(\"hello\")}" | moon run -
    $ moon run - <<EOF
    import {
      "moonbitlang/core/list"
    }
    fn main {
      debug(@list.from_array([1, 2, 3]))
    }
    EOF

Standard Library Updates

  1. Added the argparse library, which provides basic command-line argument parsing:

    ///|
    async fn main {
      let cmd = @argparse.Command("demo", options=[@argparse.OptionArg("name")], positionals=[
        @argparse.PositionArg("target"),
      ])
      let _ = cmd.parse()
    }
  2. Updates to moonbitlang/async:

    The JavaScript backend now includes HTTP client support based on the Fetch API. All HTTP client APIs in moonbitlang/async/http are available on the JavaScript backend except HTTP proxy support, including in browser environments.

    moonbitlang/async/js_async now includes support for interacting with the Web API ReadableStream.

MoonBit 0.8.0 Released

· 15 min read

We are excited to announce the release of MoonBit 0.8.0.

MoonBit is an AI native programming language. It's reliable,readable and fast.. This release marks an important milestone on MoonBit’s path toward stability and production use.

MoonBit 0.8 is not a simple collection of incremental changes. It represents a clear transition from an experimental language to an engineering-grade language and toolchain. Significant improvements have been made across language semantics, error handling, package management, and developer tooling—making MoonBit better suited for large-scale codebases and agent-centric development workflows.

Why MoonBit 0.8 Matters?

As many developers have observed, Rust provides a solid foundation for AI-assisted development through its strict semantics and strong correctness guarantees. While continuing to pursue similar reliability goals, MoonBit places additional emphasis on much faster compilation speeds—often 10–100× faster than Rust in practical use—as well as a development toolchain deeply integrated with agent-based workflows.

With the release of version 0.8, these design goals are no longer abstract principles. They are now consistently reflected across the language, compiler, runtime, and IDE.

Key Updates

WasmGC/Native/LLVM Backend Backtrace Support

The MoonBit wasmg-gc/native/LLVM backend now supports automatically printing call stacks when a program crashes. Backtraces are mapped directly to the corresponding MoonBit source locations, significantly improving the debugging experience.

RUNTIME ERROR: abort() called
/path/to/moonbitlang/core/array/array.mbt:187 at @moonbitlang/core/array.Array::at[Int]
/path/to/pkg/main/main.mbt:3 by @username/hello/out_of_idx.demo
/path/to/pkg/main/main.mbt:9 by main

AI-Native Specification Support

MoonBit introduces the declare keyword, which can be used to declare types, functions, and other program elements that are intended to be implemented later. If a declare declaration does not have a corresponding implementation, the MoonBit compiler will emit a warning.

The declare keyword provides AI-native specification support. By writing specifications in the form of declare, developers can clearly describe the interfaces and behavior they expect AI systems to implement. AI tools only need to read the declare specifications and the associated test files to begin generating code.

During development, the compiler’s warning messages guide the AI to progressively complete the required implementations. Since missing declare definitions are treated as warnings rather than errors, AI systems can incrementally write and test code as the implementation evolves.

Completed Technical Changelog in MoonBit 0.8

Language Updates

  1. The suberror Err PayloadType syntax has been deprecated. Users need to migrate to the enum-like sytax for declaring error type:

    suberror Err {
      Err(PayloadType)
    }

    This change can be migrated automatically via moon fmt

  2. Type inference for builtin error constructors (maily Failure) is deprecated to avoid scope pollution. When the expected error type is unknown, raise Failure(..) should be migrated to raise Failure::Failure(..), similarly for catch.

  3. Values of type FuncRef[_] can now be called directly from MoonBit code. This feature can be used for dynamic symbol loading or implementing JIT in native backend.

  4. MoonBit's wasm-gc, native backend and LLVM backend now supports backtrace. When a MoonBit program panic (performing an out-of-bound array indexing operation, failure of guard statement without else, or try! expression receiving an error, or manually calling panic/abort, etc.), the stack trace of the panic will be printed under debug mode. Here's an example:

    fn demo(a: Array[Int], b: Array[Int]) -> Unit {
      let _ = a[1]
      let _ = b[2]
    }
    
    fn main {
      let a = [1, 2]
      let b = [3]
      demo(a, b)
    }

    Take native backend as example, when run with moon run --target native, the program will output:

    RUNTIME ERROR: abort() called
    /path/to/moonbitlang/core/array/array.mbt:187 at @moonbitlang/core/array.Array::at[Int]
    /path/to/pkg/main/main.mbt:3 by @username/hello/out_of_idx.demo
    /path/to/pkg/main/main.mbt:9 by main

    Note: Native/LLVM backend stacktrace has not been supported on Windows Platform.

  5. A new keyword declare is introduced to replace the previous #declaration_only attribute. In addition, declare now supports declaring trait implementations. For example:

    declare type T // declare a type to be implemented
    declare fn T::f(x : T) -> Int // declare a method to be implemented
    
    struct S(Int)
    declare impl Show for S // declare an impl relation
  6. for .. in loop now supports iterating over a reversed range via reversed range expressions x>..y and x>=..y:

    ///|
    test "reversed range, exclusive" {
      let result = []
      for x in 4>..0 {
        result.push(x)
      }
      debug_inspect(result, content="[3, 2, 1, 0]")
    }
    
    ///|
    test "reversed range, inclusive" {
      let result = []
      for x in 4>=..0 {
        result.push(x)
      }
      debug_inspect(result, content="[4, 3, 2, 1, 0]")
    }

    To make the syntax for consistent, the syntax x..=y for forward, closed range expression has been migrated to x..<=y, the old syntax is deprecated. This change can be migrated automatically via moon fmt

  7. Using { ..old_struct, field: .. } to update a struct with priv fields (outside its package) is now forbidden

  8. lexmatch expressions in first-match mode now supports pattern guard. lexmatch with guard currently has some performance penalty, so it is recommended to use it during fast prototying, and evaluate if lexmatch guard should be removed later. The syntax is the same as pattern guard for normal match, see https://github.com/moonbitlang/lexmatch_spec for more details:

    lexmatch input {
      ("#!" "[^\n]+") if allow_shebang => ...
      ...
    }
  9. struct now supports user defined constructors. The syntax is as follows:

    struct S {
      x : Int
      y : Int
    
      // declare a constructor for the `struct`
      fn new(x~ : Int, y? : Int) -> S
    }
    
    // implement the constructor for `struct`
    fn S::new(x~ : Int, y? : Int = x) -> S {
      { x, y }
    }
    
    // using the `struct` constructor
    test {
      let s = S(x=1)
    }

The semantic of struct constructors is:

  • struct constructors can be declared by adding a fn new declartion to the end of the body of struct. The signature of the constructor has no restriction, except that it must return the struct itself. So features such as optional arguments, raising error can be used freely in the struct constructor. The parameters of fn new(..) inside the struct must not specify default value for optional arguments, but may omit parameter names
  • For struct with type parameters, fn new can specialize the type parameters or add trait bounds to them. The syntax is the same as a normal toplevel function declaration
  • If a struct declares a constructor via fn new, the constructor must be implemented by adding a fn S::new method declaration in the same package. The signature of S::new must be exactly the same as the fn new declaration in the struct
  • Using the struct constructor is exactly the same as using an enum constructor, except that struct constructors cannot be used for pattern matching. For example, when the expected type is known, S(..) can be used directly in place of @pkg.S(..) or @pkg.S::S(..).
  • The visibility of struct constructor is the same as the fields of the struct. So the constructors of pub struct and pub(all) struct will be public, while the constructors of struct and priv struct will be private
  1. Alias generated by using can now be deprecated by adding #deprecated attribute to the using declaration.

  2. A new trait Debug is introduced, with auto deriving support. Derive is a more advanced version of the Show trait, it can convert MoonBit values to more structural and readable text message:

    ///|
    struct Data {
      pos : Array[(Int, Int)]
      map : Map[String, Int]
    } derive(Debug)
    
    ///|
    test "pos" {
      debug_inspect(
        {
          pos: [(1, 2), (3, 4), (5, 6)],
          map: { "key1": 100, "key2": 200, "key3": 300 },
        },
        content=(
          #|{
          #|  pos: [(1, 2), (3, 4), (5, 6)],
          #|  map: {
          #|    "key1": 100,
          #|    "key2": 200,
          #|    "key3": 300,
          #|  },
          #|}
        ),
      )
    }

    derive(Debug) additionally supports an ignore parameter, which accepts one or more type names. Values with an ignored type will be displayed as ... In the derived Debug implementation. This is especially useful when working with types from third party packages that have no Debug implementation:

    ///|
    struct Data1 {
      field1 : Data2
      field2 : Double
      field3 : Array[Int]
    } derive(Debug(ignore=[Data2, Array]))
    
    ///|
    struct Data2 {
      content : String
    }
    
    ///|
    test "pos" {
      debug_inspect(
        { field1: { content: "data string" }, field2: 10, field3: [1, 2, 3] },
        content=(
          #|{
          #|  field1: ...,
          #|  field2: 10,
          #|  field3: ...,
          #|}
        ),
      )
    }

    In addition to more readable format with automatic line break, the moonbitlang/core/debug package also provides an assert_eq(a, b) function, which automatically calculate and display the diff of a and b when they are not equal. In the future, we will gradually deprecate derive(Show) and migrate to Debug for debugging. The Show trait will focus on hand-written, domain specific printing logic, such as Json::stringify

  3. Constructors with arguments can no longer be used as higher order functions directly. An anonymous function wrapper is necessary:

    test {
      let _ : (Int) -> Int? = Some // removed
      let _ : (Int) -> Int? = x => Some(x) // correct way
      let _ : Int? = 42 |> Some // pipes are not affected
    }

    This behavior has been deprecated for some time. Notice that constructors with arguments can still be used on the right hand side of the pipe operator.

  4. Effect inference has been deprecated for local fn. If a fn may raise error or perform async operations in its body, it must be explicitly annotated with raise/async, otherwise the compiler will emit a warning, which will turn into an error in the future. The arrow function syntax (..) => .. is not affected. So, we recommended using arrow functions instead of fn for callback functions in the future. fn can be used when explicit annotation is desirable for documentation or readability purpose.

  5. The semantic for x..f() has been adjusted back to its original, simple semantic: x..f() is equivalent to { x.f(); x }. Previously, the result of x..f() can be silently ignored. The compiler will now emit a warning for this kind of usage. Users should replace the last ..f() replaced with .f(), or explicitly ignore the result.

  6. for/for .. in/while loops previously support adding an else block to do something when the loop exits normally. To make the syntax more intuitive, the else keyword for these loops is replaced with nobreak:

    fn f() -> Int {
      for i = 0; i < 10; i = i + 1 {
    
      } nobreak {
        i
      }
    }

    This change can be migrated automatically via moon fmt.

  7. A new warning unnecessary_annotation (disabled by default) is introduced, which identifies unnecessary annotation on struct literal and constructors (i.e. places where the compiler can automatically deduce the correct type from the context).

Toolchain Updates

  1. The moon.pkg DSL is now officially recommended way to write package configurations, and the old moon.pkg.json format is deprecated. You can use moon fmt to automatically migrate to the new moon.pkg format, and new MoonBit projects will now use the moon.pkg format by default. Here's a nexample for some common configuration options:

    import {
      "path/to/pkg1",
      "path/to/pkg2" @alias,
    }
    
    warnings = "+deprecated-unused_value"
  2. moon test now supports using the -j parameter to run tests in parallel

  3. moon test can now list all tests to run via the --outline parameter

  4. moon test --index can now specify a range of test index (inclusive on the left and exclusive on the right). For example, moon test /path/to/test/file.mbt --index 0-2 will now run the first two tests in /path/to/test/file.mbt

  5. The old behavior of moon install(install all dependencies of current project) has been deprecated, because moon check and moon build now automatically install dependencies. The new behavior of moon install is similar to cargo install or go install. It allows users to globally install one or more binaries from mooncakes.io, a git repo, or a local project. The installed package must support native backend and must have is-main set to true in its package configuration. For example:

    moon install username/package (when the project root is a package)
    moon install username/cmd/main (install a specific package)
    moon install username/... (install all packages with the specified prefix)
    moon install ./cmd/main (local path)
    moon install https://github.com/xxx/yyy.git (git URLs are automatically detected)

    More usage can be found in moon install --help

  6. moon.pkg now supports a regex_backend option to specify the backend of lexmatch:

    options(
      // The default is "auto", other available options are
      // "block", "table" and "runtime".
      //
      // The "block" backend has the best performance,
      // but generates larger code
      //
      // The "table" backend generates a lookup table at compile time,
      // and interpret the table dynamically at runtime.
      // It hits a good balance between performance and code size.
      //
      // The "runtime" backend generates code that
      // use the `regex_engine` package in `moonbitlang/core`
      // to compile and execute regular expressions dynamically.
      // It can save a lot of code size when regex is used heavily,
      // but has the poorest performance.
      regex_backend: "runtime",
    )
  7. Previously, moon -C <path>does not change the working directory of moon, and interpret all othe path arguments relative to current working directory, which is inconsistent with many other common build systems. Now moon -C <path> will change the working directory of moon. As a consequence, -C must appear before the command line subcommand and all other command line arguments. A new command line option --manifest-path is added, which receive the path of moon.mod.json, and work with that project in current working directory. It can be used to run executables or tests of a MoonBit project in an alternative directory.

  8. moon run and moon build now use --debug by default

  9. The front matter syntax for declaring dependencies of .mbt.md files has been updated. Previously, only module level dependency can be declared, and all packages in those modules will be imported implicitly, which may result in package alias conflict. The the new version, .mbt.md can declare package import directly in the front matter header, with support for package alias. The version of the modules to import should be explicitly written in the import path. If a module appears multiple times in the import list, its version need to specified only once. Dependencies in moonbitlang/core do not need a version number:

    ---
    moonbit:
      import:
        - path: moonbitlang/async@0.16.5/aqueue
          alias: aaqueue
      backend:
        native
    ---
  10. Templates for moon new are simplified, and some simple introduction about skills is added.

  11. A new subcommand moon fetch can be used to fetch the source code of a package without adding at as a dependency to current project. The fetched source code is saved in the .repos directory under project root or current working directory by default. This is useful for AI agents to learn about available third party packages.

  12. moon fmt will now preserve empty line between statements. But multiple consecutive empty lines will be compressed into one empty line:

    // before formatting
    fn main {
      e()
    
      // comment
      f()
    
    
      g()
      h()
    }
    // after formatting
    fn main {
      e()
    
      // comment
      f()
    
      g()
      h()
    }
  13. moonbit inside .mbt.md files and docstring will now not get type checked. moon fmt will automatically convert moonbit to moonbit nocheck automatically.

Standard and Experimental Library Updates

  1. moonbitlang/async Changes:
  • Introduce @process.spawn, which spawns a foreign process inside a TaskGroup, and returns a handle containing the PID of the process. By default, the task group will wait for the process to terminate. If the task group needs to exit early, the foreign process will be terminated automatically
  • Introduce @fs.File::{lock, try_lock, unlock}, which provides advisory file lock support (i.e. normal IO operations do not interact with these locks)
  • Introduce @fs.tmpdir(prefix~), which creates a temporary directory with the given prefix in the system temporary file store
  • Introduce @async.all and @async.any, which are similar to Promise.all and Promise.any in JavaScript
  • Add more examples to the examples directory and a brief introduction to these examples
  1. @json.inspect has been migrated to json_inspect

IDE Updates

  1. Goto definition for alias will now display the definition of the alias itself as well as its original definition: alt text

  2. moon ide now supports a new subcommand moon ide hover, which display the signature and document for a symbol in the source code:

    $ moonide hover -no-check filter -loc hover.mbt:14
    test {
      let a: Array[Int] = [1]
      inspect(a.filter((x) => {x > 1}))
                ^^^^^^
                ```moonbit
                fn[T] Array::filter(self : Array[T], f : (T) -> Bool raise?) -> Array[T] raise?
                ```
                ---
    
                Creates a new array containing all elements from the input array that satisfy
                the given predicate function.
    
                Parameters:
    
                * `array` : The array to filter.
                * `predicate` : A function that takes an element and returns a boolean
                indicating whether the element should be included in the result.
    
                Returns a new array containing only the elements for which the predicate
                function returns `true`. The relative order of the elements is preserved.
    
                Example:
    
                ```mbt check
                test {
                  let arr = [1, 2, 3, 4, 5]
                  let evens = arr.filter(x => x % 2 == 0)
                  inspect(evens, content="[2, 4]")
                }
                ```
    }
  3. moon ide introduces a new subcommand moon ide rename, which generates a patch that renames a symbol. The format of the patch is compatible with the apply_patch tool of OpenAI codex. moon ide rename allows AI agents to perform large scale code refactor robustly and efficiently.

    $ moon ide rename TaskGroup TG
    *** Begin Patch
    *** Update File: /Users/baozhiyuan/Workspace/async/src/async.mbt
    @@
    /// and will result in immediate failure.
    #deprecated("use `async fn main` or `async test` instead")
    #cfg(target="native")
    -pub fn with_event_loop(f : async (TaskGroup[Unit]) -> Unit) -> Unit raise {
    +pub fn with_event_loop(f : async (TG[Unit]) -> Unit) -> Unit raise {
      @event_loop.with_event_loop(() => with_task_group(f))
    }
    
    *** Update File: /Users/baozhiyuan/Workspace/async/src/task_group.mbt
    @@
    ///
    /// The type parameter `X` in `TaskGroup[X]` is the result type of the group,
    /// see `with_task_group` for more detail.
    -struct TaskGroup[X] {
    +struct TG[X] {
      children : Set[@coroutine.Coroutine]
      parent : @coroutine.Coroutine
      mut waiting : Int
    @@
    pub suberror AlreadyTerminated derive(Show)
    
    ///|
    -fn[X] TaskGroup::spawn_coroutine(
    +fn[X] TG::spawn_coroutine(
    -  self : TaskGroup[X],
    +  self : TG[X],
      f : async () -> Unit,
    ...

20260112 MoonBit Monthly Update Vol.07

· 7 min read

Version:v0.7.1

Language Updates

  1. Added warning for unused async. This helps clean up unnecessary async annotations, improving code readability and maintainability while avoiding potential stack overflow issues.

    pub async fn log_debug(msg : String) -> Unit {
      //^^^^^ Warning (unused_async): This `async` annotation is useless.
      println("[DEBUG] \{msg}")
    }
  2. Default value expressions in optional arguments now can raise errors. Optional arguments can now use expressions that may raise an error as their default values. Note that the function has to be marked as "can raise error".

    pub async fn log_debug(
      msg : String,
      file? : @fs.File = @fs.open("log.txt", mode=WriteOnly, append=true),
    ) -> Unit {
      file.write("[DEBUG] \{msg}\n")
    }
  3. New #declaration_only Attribute. Added the #declaration_only attribute for functions, methods, and types. This supports spec-driven development, allowing developers to define function signatures and type declarations first and provide implementations later. When used on functions/methods, the body must be filled with ....

    Example of a TOML parser declaration:

    #declaration_only
    type Toml
    
    #declaration_only
    pub fn Toml::parse(string : String) -> Toml raise {
      ...
    }
    
    #declaration_only
    pub fn Toml::to_string(self : Toml) -> String {
      ...
    }
  4. SourceLoc now displays relative paths. The display for SourceLoc has been migrated to use relative paths.

    ///|
    #callsite(autofill(loc))
    fn show_source_loc(loc~ : SourceLoc) -> Unit {
      println(loc)
    }
    
    ///|
    fn main {
      show_source_loc()
    }

    Running moon run . outputs: main.mbt:9:3-9:20@username/test

  5. Optimized warnings for array literals. Added warnings for array literals that only undergo specific operations, suggesting ReadOnlyArray or FixedArray for better compiler optimization.

    pub fn f() -> Unit {
      let a = [1, 2, 3]
          ^ --- [E0065] Warning (prefer_readonly_array)
      ignore(a[0])
      let b = [1, 2, 3]
          ^ --- [E0066] Warning (prefer_fixed_array)
      b[0] = 4
    }

    Note: These warnings are currently disabled by default. Users must manually enable them by adding warnings +prefer_readonly_array and +prefer_fixed_array to the warn-list in moon.pkg.

  6. Pipeline Syntax Improvement Support for the syntax e1 |> x => { e2 + x } has been added. This allows you to simplify the original e |> then(x => e2 + x) expression.

  7. Support annotating for-loops with loop invariants and reasoning. For example

    fn test_loop_invariant_basic() -> Unit {
      for i = 0; i < 10; i = i + 1 {
        println(i)
      } where {
        invariant: i >= 0,
        reasoning: "i starts at 0 and increments by 1 each iteration",
      }
    }
  8. The behavior of inferring Ref type through struct literal is deprecated:

    let x = { val: 1 } // Previously this will infer the `Ref` type.
                      // This behavior is deprecated and
                      // will be removed in the future
    let x : Ref[_] = { val: 1 } // no problem if type is known
    let x = Ref::{ val: 1 } // no problem if annotated
    let x = Ref::new(1) // you may also use `Ref::new`
                        // instead of struct literal

Toolchain Updates

  1. Experimental moon.pkg Support. We introduced an experimental moon.pkg configuration file to replace moon.pkg.json. It uses a syntax similar to MoonBit Object Notation, simplifying configuration while remaining easy to read.
  • Compatibility: If a moon.pkg file exists in a package, MoonBit will use it as the configuration.

  • Migration: When the environment variable NEW_MOON_PKG=1 is set, moon fmt will automatically migrate old moon.pkg.json files to the new format.

  • Features: Supports comments and empty configurations. All options from moon.pkg.json are compatible via the options(...) declaration.

    Example of the new syntax:

    // moon.pkg
    // import package
    import {
      "path/to/package1",
      "path/to/package2" as @alias,
    }
    
    // import package for black box test
    import "test" {
      "path/to/test_pkg1",
      "path/to/test_pkg2" as @alias,r
    }
    
    // import package for white box test
    import "wbtest" {
      "path/to/package" as @alias,
    }
    
    // Compatible with all options from the original moon.pkg.json
    options(
      warnings: "-unused_value-deprecated",
      formatter: {
        ignore: ["file1.mbt", "file2.mbt"]
      },
      // Compatible with old options using "-" in names; double quotes can be used
      "is-main": true,
      "pre-build": [
        {
          "command": "wasmer run xx $input $output",
          "input": "input.mbt",
          "output": "output.moonpkg",
        }
      ],
    )
  1. Improved Workflow for moon add. Running moon add now automatically executes moon update first, streamlining the dependency management process.

  2. The refactored moon implementation is now enabled by default. Users can manually switch back to the older implementation using NEW_MOON=0.

  3. Support for Indirect Dependencies. Users can now use methods and impl from a package without explicitly importing it in moon.pkg, provided it is an indirect dependency.

    // @pkgA
    pub(all) struct T(Int)
    pub fn T::f() -> Unit { ... }
    
    // @pkgB
    pub fn make_t() -> @pkgA.T { ... }
    
    // @pkgC
    fn main {
      let t = @pkgB.make_t()
      t.f()
    }

    Previously, @pkgC must explicitly import @pkgA in its moon.pkg.json to use the method @pkgA.T::f. With indirect dependency, importing @pkgA is no longer required. The import for a package @pkg is only necessary when it's used directly as @pkg.xxx.

  4. moon fmt no longer formats output from prebuild.

  5. moon check --fmt now supports detecting unformatted source files.

  6. When the target is js, moon's execution of tests and code is no longer affected by the local package.json.

  7. The build artifacts directory target is moved to _build, however we generated a symlink target points to _build in order to keep backward compatibility.

Standard and Experimental Library Updates

  1. moonbitlang/async Changes:

    • Windows Support: Now supports Windows (MSVC only). Most features are implemented except for symbolic links and specific filesystem permissions.

    • Recursive Directory Creation: @fs.mkdir now has a recursive? : Bool = false parameter.

    • Process Management: Improved @process.run cancellation. It now attempts to notify external processes to exit gracefully before forcing termination, always waiting for the process to end before returning.

    • Specific Pipe Types: @process.read_from_process and write_to_process now return specialized types (ReadFromProcess/WriteToProcess) instead of generic pipes.

  2. The Iter type in moonbitlang/core has migrated from internal iterator to external iterator:

    • The signature of Iter::new has changed. Iter::new has previously been deprecated in https://github.com/moonbitlang/core/pull/3050

    • The external iterator type Iterator is merged with Iter. There is only one iterator type in moonbitlang/core now. If you have implemented the same trait for both Iter and Iterator, please remove the implementation for Iterator

    • The name Iterator is now an alias of Iter and is deprecated. Similarly, the various .iterator() methods of data structures in moonbitlang/core are deprecated in favor of .iter()

    For most users who do not construct iterators directly, the changes above should be backward-compatible. There is one notable difference in the behavior of iterators, though: previously the Iter type can be traversed more than once. Every traversal will recompute every element in the iterator. After migrating to external iterators, Iter can be traversed only once. Traversing an iterator more than once is an anti-pattern and should be avoided.

  3. Experimental lexmatch Enhancements. Added support for POSIX character classes (e.g., [:digit:]) in lexmatch, while deprecating escape sequences like \w, \d, and \s.

    fn main {
      let subject = "1234abcdef"
      lexmatch subject {
        ("[[:digit:]]+" as num, _) => println("\{num}")
        _ => println("no match")
      }
    }

IDE Updates

  1. Fixed issues where .mbti files were not working correctly in the LSP.

  2. New moon ide command line tools. Refer to https://docs.moonbitlang.com/en/latest/toolchain/moonide/index.html for documentation.

    • moon ide peek-def: Find definitions based on location and symbol name.
    ///|
    fn main {
      let value = @strconv.parse_int("123") catch {
        error => {
          println("Error parsing integer: \{error}")
          return
        }
      }
      println("Parsed integer: \{value}")
    }

    Run moon ide peek-def -loc main.mbt:3 parse_int, we can see it outputs:

    Definition found at file $MOON_HOME/lib/core/strconv/int.mbt
        | }
        |
        | ///|
        | /// Parse a string in the given base (0, 2 to 36), return a Int number or an error.
        | /// If the `~base` argument is 0, the base will be inferred by the prefix.
    140 | pub fn parse_int(str : StringView, base? : Int = 0) -> Int raise StrConvError {
        |        ^^^^^^^^^
        |   let n = parse_int64(str, base~)
        |   if n < INT_MIN.to_int64() || n > INT_MAX.to_int64() {
        |     range_err()
        |   }
        |   n.to_int()
        | }
        |
        | // Check whether the underscores are correct.
        | // Underscores must appear only between digits or between a base prefix and a digit.
        |
        | ///|
        | fn check_underscore(str : StringView) -> Bool {
        |   // skip the sign
        |   let rest = match str {
    • moon ide outline: Lists an overview/outline of a specified package.
    • moon doc <symbol> has been migrated to moon ide doc <symbol>.
  3. Doc tests now support mbt check. You can write test blocks within doc comments and receive CodeLens support for running, debugging, and updating tests. alt text

20251202 MoonBit Monthly Update Vol.06

· 9 min read

Version 0.6.33

Language Update

  • Improved functionality for ReadOnlyArray.

    ReadOnlyArray was introduced in the previous version, primarily used for declaring lookup tables, and the compiler performs more performance optimizations for ReadOnlyArray. In this version, the feature support for ReadOnlyArray has been improved to provide a user experience basically consistent with other array types, such as pattern matching, slicing, and spread operations.

    fn main {
      let xs: ReadOnlyArray[Int] = [1,2,3]
      let _ = xs[1:]
      match xs {
        [1, .. rest] => ...
        ...
      }
      let _ = [..xs, 1]
    }
  • bitstring pattern supports signed extraction, allowing the extracted bits to be interpreted as signed integers, for example:

    fn main {
    let xs : FixedArray[Byte] = [0x80, 0, 0, 0]
    match xs {
      [i8be(i), ..] => println(i) // prints -128 because 0x80 is treated as signed 8-bit int
      _ => println("error")
    }
    }
  • Cascade function call improvements

    Previously, in cascade-style function calls like x..f()..g(), the return type of f was required to be Unit. This restriction has now been removed. When f returns a value of a type other than Unit, an invalid_cascade warning will be triggered, and at runtime this return value will be implicitly discarded:

    struct Pos {}
    fn Pos::f(_ : Self) -> Int  { 100 }
    fn Pos::g(_ : Self) -> Unit { ()  }
    fn main {
      let self = Pos::{}
      self
      ..f() // warning, discard the returned value 100
      ..g()
    }

    If you wish to prohibit such implicit discarding in your project, you can treat this warning as an error by configuring "warn-list": "@invalid_cascade".

  • Syntax parsing improvements

    • Improved error recovery when :: is omitted in StructName::{ field1: value }
    • Improved error recovery when range syntax is incorrectly written in for x in a..=b {} and match e1 { a..=b => e2 }
  • Improvements to .mbt.md code block support We have decided to make markdown code blocks that participate in compilation more explicit. The specific changes are as follows:

    • Code blocks marked only as mbt or moonbit will no longer be compiled. These code blocks need to be explicitly marked as check, i.e., mbt check or moonbit check, to be compiled as before.

    • Added mbt test and mbt test(async) code blocks. In addition to participating in compilation, these code blocks will also wrap the code in a test or async test block. When using these two types of code blocks in markdown, users no longer need to manually write test {} or async test.

          # A Markdown Example
      
          Highlighting only:
      
          ```mbt
          fn f() -> Unit
          ```
      
          Highlighting and checking:
      
          ```mbt check
          fn f() -> Unit {...}
          ```
      
          Highlighting, checking and treated as a test block:
      
          ```mbt test
          inspect(100)
          inspect(true)
          ```

    The markdown in docstrings has also undergone the same changes, although mbt check is not currently supported but will be supported in the future.

  • #label_migration attribute

    The #label_migration attribute supports declaring aliases for parameter labels, with two main uses: first, it allows giving the same parameter two different labels; second, when an additional msg is provided, it can be used to deprecate a certain parameter label:

    #label_migration(x, alias=xx)
    #label_migration(y, alias=yy, msg="deprecate yy label")
    fn f(x~ : Int, y? : Int = 42) -> Unit { ... }
    
    ///|
    fn main {
      f(x=1, y=2)   // ok
      f(xx=1, yy=2) // warning: deprecate yy label
      f(x=1)        // ok
    }
  • #deprecated default behavior improvement

    The default behavior of deprecated has been changed to skip_current_package=false, meaning warnings will also be reported for usage within the current package. If unexpected warnings appear in recursive definitions or tests, you can explicitly disable warnings for the current package using #deprecated(skip_current_package=true), or use the newly added #warnings attribute to temporarily disable warnings.

  • Warnings and alerts improvements

    • Added mnemonics for warnings

      Now you can configure warnings by their names instead of numbers: "warn-list": "-unused_value-partial_match"

    • #warnings attribute support

      Now supports locally enabling/disabling warnings through the #warnings attribute. The parameter inside the attribute is the same string format as the warn-list configuration. The string contains multiple warning names, each preceded by a symbol indicating the configuration for that warning: -name means disable the warning; +name means enable the warning; @name means if the warning is already enabled, escalate it to an error.

      For example, the following example disables the unused_value warning for the entire function f and escalates the default-enabled deprecated warning to an error. Now it won't prompt that the variable is unused, but if f uses a deprecated API, compilation will fail:

      #warnings("-unused_value@deprecated")
      fn f() -> Unit {
        let x = 10
      }

      Currently, this attribute only supports configuring certain specific warnings.

    • Merge alerts and warnings

      Deprecate alerts-related configurations. Now alerts have become a subset of warnings. When using -a to disable all warnings, all alerts will also be disabled. Specifically, in warn-list, you can use alert to refer to all alerts, and alert_<category> to refer to a specific category of alerts:

      #warnings("-alert")
      fn f() -> Unit { ... } //Disable all alert warnings
      
      #warnings("@alert_experimental")
      fn g() -> Unit { ... } //Related to APIs marked with #internal(experimental, "...")
    • test_unqualified_package warning

      Added the test_unqualified_package warning, which is disabled by default. When enabled, it requires blackbox tests to reference the tested package's API using the @pkg.name format, otherwise this warning will be triggered.

  • Lexmatch syntax update

    The experimental lexmatch expression now supports the first (default) matching strategy. Under this matching strategy, search mode and non-greedy quantifiers are supported. For specific details, please refer to the proposal.

    // Find the first block comment and print its content
    lexmatch s { //  `with first` can be omitted
      (_, "/\*" (".*?" as content) "\*/", _) => println(content)
      _ => ()
    }
  • Type inference improvements

    Fixed the issue where type information in X could not propagate to expressions when the expected type is ArrayView[X], and the issue where type parameters could not propagate to expressions when the expected type is a newtype with parameters.

  • Added #module attribute for importing third-party JS modules For example, the following code imports the third-party JS module "path" and uses "dirname" from that module

#module("path")
extern "js" fn dirname(p : String) -> String = "dirname"

The code above will result in the following generated JS code (simplified, the actual code has name mangling):

import { dirname } from "path";

Toolchain Update

  • IDE completion improvements

    The IDE now displays deprecated APIs with strikethrough formatting: alt text

  • Build system improvements

    • Enhanced the readability of expect/snapshot test diffs. Now, these areas use the unified diff format to display the differences between expected and actual values, making them readable even in scenarios where colors are not output, such as in CI and files, while still showing colored and highlighted comparison results when color display is available. alt text
  • New Build system backend

    We have basically completed a complete rewrite of the build system backend, which can be enabled for trial use through the NEW_MOON=1 environment variable.

    The new backend is less prone to errors due to various edge cases during the build process, offering stronger stability, while also providing performance improvements compared to the current implementation. The new backend now supports the vast majority of existing features and behaves exactly the same as the current implementation, but may lack some optimizations in certain situations (such as running tests in parallel).

    If you encounter any issues when running the new backend, including performance issues and behavioral inconsistencies, please report them at https://github.com/moonbitlang/moon/issues.

  • File path-based filtering for moon {build,check,info} moonbuild#1168.

    When running moon build, moon check, or moon info, you can pass in the folder path where the package to be processed is located or a file path within it, and only the corresponding command for that package will be executed. This usage is similar to -p <package name>, but doesn't require entering the complete package name. This feature cannot be used simultaneously with -p. For example:

    # Build only the package at path/to/package
    moon build path/to/package
    
    # Check only the file at path/to
    moon check path/to/file.mbt
  • moon doc symbol lookup We have created a symbol search command-line tool similar to go doc, making it convenient for AI agents or developers to quickly search for available APIs. Currently supports the following features:

    • Query available packages in a module
    • Query all available items (values, types, traits) in a package
    • Query members of a type (method, enum variant, struct field, trait method)
    • Follow aliases to their final definition when querying
    • Query built-in types
    • Support glob patterns

    Simply run moon doc <symbol name or package name> to query the documentation for the corresponding symbol or package.

  • moon fmt improvements

    • Support formatting code blocks marked as moonbit or moonbit test in documentation comments
    • moon.pkg.json supports configuring ignore lists
    { // in moon.pkg.json
      "formatter": {
        "ignore": [
          "source1.mbt",
          "source2.mbt"
        ]
      }
    }
  • async test now supports limiting the maximum number of concurrently running tests, with a default value of 10, which can be modified through "max-concurrent-tests": <number> in moon.pkg.json

Standard Library and Experimental Library Update

  • Deprecate Container::of function

    Now ArrayView is the unified immutable slice that can be created from Array, FixedArray, and ReadonlyArray. Therefore, initialization functions like of and from_array are unified as Type::from_array, with the parameter changed from accepting Array to ArrayView. It is now recommended to use Type::from_array to create containers from array literals.

  • Added MutArrayView as the unified mutable slice

    ArrayView changed from a mutable type to an immutable type in the previous version, but sometimes it's necessary to modify elements of the original array through a slice, so MutArrayView was introduced as a complement.

    MutArrayView can be created from Array and FixedArray.

  • Renamed @test.T to @test.Test and @priority_queue.T to @priorityqueue.PriorityQueue

  • String indexing improvements

    string[x] will now return UInt16. Please migrate using code_unit_at

  • moonbitlang/x/path experimental library improvements

    Supports dynamic switching between Windows path and POSIX path processing, with Python os.path style API design.

  • moonbitlang/async updates

    • moonbitlang/async experimentally supports the js backend. Currently covered features include:

      • All features unrelated to IO, including TaskGroup, control flow constructs like @async.with_timeout, async queues, etc.

      • Provides a package for JS interaction called moonbitlang/async/js_async, supporting bidirectional conversion between MoonBit async functions and JavaScript Promises, and supporting automatic cancellation handling based on AbortSignal

    • Added WebSocket support, which can be imported through the moonbitlang/async/websocket package

    • moonbitlang/async/aqueue now supports fixed-length async queues. When the queue is full, it supports three different behaviors: blocking the writer/overwriting the oldest element/discarding the newest element, which can be controlled by parameters when creating the queue

    • The HTTP client and HTTP request API in moonbitlang/async/http now support specifying HTTP CONNECT proxies. It can support HTTPS proxies with full-process encryption and proxies that require authentication

    • Improved the HTTP server API, now user callback functions can process one request at a time without manually managing connections

20251103 MoonBit Monthly Update Vol.05

· 7 min read

Version v0.6.30+07d9d2445

Compiler Updates

1. Updates of the alias system

MoonBit has three different alias syntaxes for traits, functions, and types respectively:

  • traitalias @pkg.Trait as MyTrait
  • fnalias @pkg.fn as my_fn
  • typealias @pkg.Type as MyType

This distinction introduces unnecessary complexity and limitations. For example, we couldn't create aliases for const values. In upcoming versions, we will introduce a new using syntax to unify alias creation.

The using { ... } syntax unifies traitalias, fnalias, and simple typealias, with the following specific syntax:

using @pkg {
  value,
  CONST,
  type Type,
  trait Trait,
}

This syntax creates aliases with the same name, allowing you to directly use value, CONST, Type, Trait in the current package to refer to the entity from @pkg.

You can add pub before using to make these aliases visible externally.

You can also use the as keyword for renaming in using statements, for example:

using @pkg {
  value as another_value
}

This creates an alias named another_value referring to @pkg.value. However, we do not encourage using as for renaming as it harms code readability, so the compiler will report a warning when as is used. We will remove this syntax in the future, and it's currently retained to allow migration from fnalias to using.

In addition to using, we also add support for the #alias attribute on types and traits. Therefore, all top-level definitions can now use #alias to create aliases.

The simple typealias mentioned earlier refers to cases where only a type alias is created, such as typealias @pkg.Type as MyType. For more complex type aliases that cannot be created with using, we introduce the type AliasType = ... syntax, for example:

type Handler = (Request, Context) -> Response
type Fun[A, R] = (A) -> R

Overall, in the future we will completely remove traitalias, fnalias, and typealias, replacing them with using, type Alias = ..., and #alias. Specifically:

  • using is used to create aliases of another package in the current package, for example, to import definitions from other packages
  • type Alias = ... is used to create aliases for complex types
  • #alias is used to create aliases for entities in the current package, for example, for migration during naming changes

Using moon fmt can automatically complete most migrations. We will subsequently report warnings for the old typealias/traitalias/fnalias syntax and eventually remove them.

2. Duplicate test name checking. The compiler now reports warnings for duplicate test names.

3. Experimental lexmatch updates

The experimental lexmatch update supports lexmatch? expressions similar to is expressions, and case-insensitive modifier syntax (?i:...). For details, please check the proposal

if url lexmatch? (("(?i:ftp|http(s)?)" as protocol) "://", _) with longest {
  println(protocol)
}

4. Added linting checks for anti-patterns

The compiler now includes linting checks for some anti-patterns, reporting warnings during compilation. Currently includes the following checks:

anti-pattern:

match (try? expr) {
  Ok(value) => <branch_ok>
  Err(err) => <branch_err>
}

Suggested modification:

try expr catch {
  err => <branch_err>
} noraise {
  value => <branch_ok>
}

anti-pattern:

"<string literal>" * n

Suggested modification:

"<string literal>".repeat(n)

5. Added ReadOnlyArray built-in type

The compiler now includes a new ReadOnlyArray built-in type. ReadOnlyArray represents fixed-length immutable arrays, mainly used as lookup tables. When using ReadOnlyArray, if all elements are literals, it is guaranteed to be statically initialized in the C/LLVM/Wasmlinear backends. Its usage is basically the same as FixedArray, for example:

let int_pow10 : ReadOnlyArray[UInt64] = [
  1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL,
  1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL,
  100000000000000UL, 1000000000000000UL,
]

fn main {
  println(int_pow10[0])
}

6. Adjustments to bitstring pattern syntax

Previously, bitstring patterns supported using u1(_) pattern to match a single bit. At the same time, MoonBit allows omitting payloads when pattern matching with constructors, for example:

fn f(x: Int?) -> Unit {
  match x {
    Some => ()
    None => ()
  }
}

This could cause confusion when using u1 - whether it's a bitstring pattern or a single identifier. To avoid confusion with regular identifiers, in the new version, we require bitstring patterns to be written in suffix form, such as u1be(_). (Bit reading order is always high-bit first, so endianness has no meaning when reading data with length not exceeding 8 bits; this is just to more explicitly indicate that the current pattern uses bitstring pattern.)

Additionally, because little-endian semantics are more complex, little-endian can only be used when the number of bits read is a multiple of 8, such as u32le(_).

Overall, the current bitstring pattern syntax is u<width>[le|be] where:

  • width range is (0..64]
  • le and be suffixes must be written explicitly; all width values support be, only width values that are multiples of 8 support le

7. #deprecated adds a new parameter skip_current_package

Previously, when using a deprecated definition from the current package marked with #deprecated, the compiler would not report warnings. This was to prevent uneliminable warnings in tests/recursive definitions, but sometimes it's also necessary to detect usage of deprecated definitions within the current package. Therefore, #deprecated adds a new parameter skip_current_package with a default value of true, which can be overridden with #deprecated(skip_current_package=false). When skip_current_package=false, using this definition within the current package will also report warnings.

8. moon.pkg DSL plan

We plan to design a new moon.pkg syntax to replace the original moon.pkg.json. The new DSL will improve the experience of writing import information under three premises:

  • Compatible with existing options
  • Friendly to build system performance
  • Preserve the semantics of import being "effective for the entire package"

Current moon.pkg syntax overview in the proposal (not final):

import {
  "path/to/pkg1",
  "path/to/pkg2",
  "path/to/pkg3" as @alias1,
  "path/to/pkg4" as @alias2,
}

// Blackbox test imports
import "test" {
  "path/to/pkg5",
  "path/to/pkg6" as @alias3,
}

// Whitebox test imports
import "wbtest" {
  "path/to/pkg7",
  "path/to/pkg8" as @alias4,
}

config {
  "is_main": true, // Comments allowed
  "alert_list": "+a+b-c",
  "bin_name": "name",
  "bin_target": "target",
  "implement": "virtual-package-name", // Trailing commas allowed
}

Proposal discussion and details: https://github.com/moonbitlang/moonbit-evolution/issues/18

Build System Updates

1. Deprecate moon info --no-alias.

In the last monthly, we added the moon info --no-alias functionality to generate pkg.generated.mbti files without default aliases:

moon info

package "username/hello2"

import(
"moonbitlang/core/buffer"
)

// Values
fn new() -> @buffer.Buffer

moon info --no-alias

package "username/hello2"

// Values
fn new() -> @moonbitlang/core/buffer.Buffer

In practice, we found this functionality had limited utility while increasing maintenance costs, so we decided to deprecate it.

2. Updated template project generated by moon new.

We added .github/workflows/copilot-setup-steps.yml to the template project generated by moon new to help users quickly use GitHub Copilot in MoonBit projects.

IDE Updates

1. mbti files support find references.

Users can find references of a definition in mbti files within the project:

alt text

As shown, we currently support three different ways to find references:

  • Go to References: Search in all reverse dependencies of the package
  • Go to References (current package only): Search only within the package, including all tests
  • Go to References (current package only, excluding tests): Search only within the package, skipping all tests

2. mbti files support Hide and Unhide code actions, facilitating API refactoring:

alt text

For definitions in mbti, LSP provides a Hide ... code action to make it private. After executing the above code action, Manager will become a private definition:

alt text

If no errors are reported after executing the code action, it means this is a redundant API that can be remove; if there are errors, users can use the Unhide ... code action to undo the previous operation:

alt text

3. Automatically format updated code blocks after test updates.

When using update codelens in VSCode to update tests, the updated tests are automatically formatted.

4. Added more attribute snippet completions.

Standard Library Updates

  1. Added external iterator type Iterator. We will migrate iterators for common container types in core from internal to external. This is to enable async usage in for .. in loops and support APIs like zip that internal iterators cannot support. The for .. in loop now calls .iterator()/.iterator2() methods for iteration through external iterators. When the iterated type doesn't support external iterators, it falls back to the original internal iterator type Iter. In the future, we will migrate from internal iterators to external iterators, and eventually keep only external iterators.

20251014 MoonBit Monthly Update Vol.04

· 4 min read

Version v0.6.29

Language Updates

  • Asynchronous Programming Enhancements

    Added support for async test and async fn main syntax, enabling asynchronous tests and asynchronous main functions. Both async fn main and async test are based on the moonbitlang/async library and are currently supported on the native backend for Linux and macOS. For more details about async programming in MoonBit, see the documentation and GitHub repository of moonbitlang/async. Async tests declared with async test will run in parallel.

    ///|
    async test "http request" {
      let (response, result) = @http.get("https://www.example.org")
      inspect(response.code, content="200")
      assert_true(result.text().has_prefix("<!doctype html>"))
    }
  • Experimental Feature: lexmatch Expression

    Added the lexmatch expression (experimental feature), which enables pattern matching on StringView or BytesView using regular expressions.

    The following example matches two to four consecutive 'a' characters followed by a 'b', capturing the consecutive 'a's as variable a. For more detailed usage, refer to lexer.mbt in moonbitlang/parser and the lexmatch proposal in moonbit-evolution.

    lexmatch x with longest {
      (("a{2,4}" as a) "b", _) => Some(a.length())
      _ => None
    }
  • Unified Import Syntax with using

    Introduced the using syntax, which unifies fnalias, traitalias, and simple typealias. When importing a type or trait, the corresponding keyword must now be prefixed before the name.

    using @pkg {
      value,
      CONST,
      type Type,
      trait Trait,
    }

    In addition, pub using can be used to achieve re-export, allowing definitions from other packages to be re-exported in the current package. In the future, the fnalias, traitalias, and simple typealias syntaxes will be deprecated — the functionality of creating aliases for external definitions will be replaced by using, while creating aliases for definitions within the current package will be handled by the #alias attribute.

  • Methods in a trait now support optional parameters.

    pub(open) trait Reader {
      async read(Self, buf : FixedArray[Byte], offset? : Int, len? : Int) -> Unit
    }

    The default value of an optional parameter is determined individually by each impl. Different implementations can define their own default values or choose not to provide one at all — in that case, the optional parameter’s type within the impl will be T?, where None indicates that the user did not supply the argument.

  • Operator Overloading via #alias

    Added support for using #alias to overload operators such as op_get, providing better readability compared to the op_xxx naming convention.

    // Previously `op_get`
    #alias("_[_]")
    fn[X] Array::get(self : Array[X], index : Int) -> X { ... }
    
    // Previously `op_set`
    #alias("_[_]=_")
    fn[X] Array::set(self : Array[X], index : Int, elem : X) -> Unit { ... }
    
    // Previously `op_as_view`
    #alias("_[_:_]")
    fn[X] Array::view(
      self : Array[X],
      start? : Int = 0,
      end? : Int = self.length(),
    ) -> ArrayView[X] { ... }

    Currently, the following operators are supported. The actual implementation names (e.g., get, set, view) can be freely chosen — simply attach the corresponding #alias to enable operator overloading. It is recommended to use #alias instead of op_xxx for method-based operator overloading. (Note: Operators like + are overloaded through traits and are not affected by this change.)

  • Removal of Long-Deprecated Syntax and Behaviors

    Several language features that had been deprecated for a long time have now been officially removed:

    Previously, methods defined in the form fn meth(self : T, ..) were both methods and regular functions, allowing them to be called directly as standalone functions. This behavior had long been deprecated (with compiler warnings) and is now removed. The declaration fn meth(self : T, ..) is now equivalent to fn T::meth(self : T, ..). In the future, the self-form method definition itself may also be deprecated.

    The direct_use field in moon.pkg.json has been officially removed and replaced by using.

Toolchain Updates

  • Wasm Toolchain Released — The Wasm version of the MoonBit toolchain is now available for x86 Darwin and ARM Linux users. 🔗 Read more

  • Experimental Build System (RR) — A new experimental version of the build system, codenamed RR, has been introduced. It offers higher performance and better maintainability, and will eventually replace the current internal implementation of moon. You can enable it using the environment variable NEW_MOON=1 or the command-line flag -Z rupes_recta. If you encounter any issues, please report them on GitHub Issues.

  • moon fmt Enhancements — Now supports formatting of .mbt.md files.

  • moon info --no-alias Option — Added a new option to hide type aliases when generating the pkg.generated.mbti file.

Standard Library Updates

  • Improved Hash Security — To mitigate potential HashDoS attacks, hash computation is now randomized per process. This change has already been implemented in the JS backend.

  • Immutable ArrayViewArrayView has been changed to an immutable data structure, providing a unified slicing interface for Array, FixedArray, and ImmutArray.

20250908 MoonBit Monthly Update Vol.03

· 4 min read

Language Updates

  1. Support for Bitstring Patterns
  • Added support for Bitstring patterns, enabling matching of specific-width bits when pattern matching on Bytes or @bytes.View, for example:

    pub fn parse_ipv4(ipv4 : @bytes.View) -> Ipv4  {
      match ipv4 {
        [ // version (4) + ihl (4)
          u4(4), u4(ihl),
          // DSCP (6) + ECN (2)
          u6(dscp), u2(ecn),
          // Total length
          u16(total_len),
          // Identification
          u16(ident),
          // Flags (1 reserved, DF, MF) + Fragment offset (13)
          u1(0), u1(df), u1(mf), u13(frag_off),
          // TTL + Protocol
          u8(ttl), u8(proto),
          // Checksum (store; we'll validate later)
          u16(hdr_checksum),
          // Source + Destination
          u8(src0), u8(src1), u8(src2), u8(src3),
          u8(dst0), u8(dst1), u8(dst2), u8(dst3),
          // Options (if any) and the rest of the packet
          .. ] => ...
        ...
      }
    }

    You can use u<width>be or u<width>le to match bits of the specified width in big-endian or little-endian order. If neither be nor le is specified, big-endian is used by default. The valid width range is [1, 64].

  1. Direct use of constructors in pattern matching
  • Allowed direct use of constructors in pattern matching to match only the tag of an enum, for example:
    fn is_some(x: Int?) -> Unit {
      guard x is Some
    }
    Previously, using a constructor with a payload as if it had none would result in a compiler error. This has now been changed to a warning, allowing users to disable such warnings via the warn-list parameter.
  1. Added #callsite(migration) attribute for code migration of optional arguments.

    For example, if the author wants to change the default value of parameter y in the next version, they can use migration(fill=true, ...) to prompt downstream users to explicitly provide a value. Similarly, if the optional parameter z is planned to be removed in the next version, migration(fill=false, ...) can be used to notify users that they no longer need to explicitly provide z.

    #callsite(migration(y, fill=true, msg="must fill y for migration"), migration(z, fill=false, msg="cannot fill z for migration"))
    fn f(x~ : Int, y~ : Int = 42, z? : Int) -> Unit {
      ...
    }
  2. Added #skip attribute to skip tests, for example:

    #skip("Reason for skipping")
    test "will not run" {
      ...
    }

    However, the compiler will still perform type checking on the code inside the test block.

  3. Added #as_free_fn attribute as a replacement for the fnaIias Type::f functionality, for example:

    #as_free_fn // allow MyType::f to be called as f
    #as_free_fn(f0) // allow MyType::f to be called as f0
    #as_free_fn(f1, deprecated) // allow MyType::f to be called as f1, but with deprecated message
    fn MyType::f(self : MyType) -> Unit {
      ...
    }

    This allows MyType::f to be called directly as a regular function f or f0.

  4. Added visibility control to #alias and #as_free_fn, for example:

    #as_free_fn(visibility="pub")
    fn MyType::f(self : MyType) -> Unit {
      ...
    }

    In this case, the MyType::f method is private, but its free function f is public. The same applies to #alias in the following example.

    #alias(pub_alias, visibility="pub")
    fn priv_func() -> Unit {
      println("priv func")
    }
  5. Async functions now implicitly raise.

  • Functions marked with async no longer need an explicit raise, making the code more concise. If you want type checking to ensure that an async function does not throw errors, you can mark it with noraise.
  1. FFI parameter ownership annotations required.
  • In the C/LLVM/Wasm backends, parameters declared as pointers in FFI must now be annotated with either #borrow or #owned; otherwise, the compiler will emit a warning. #borrow means the called FFI function will only read or write the parameter locally and will not store or return it, so no special handling is required. #owned means ownership of the parameter is transferred to the called function.

    For details, see the documentation on external function calls. The motivation for this change is that in the future we plan to switch the default calling convention for FFI parameters from #owned to #borrow, and explicit annotations help users migrate progressively.

  1. Changed calling convention of FuncRef[_].
  • Previously, FuncRef[_] treated parameter ownership as #owned. It has now been changed to #borrow. This change is breaking: users who use FuncRef[_] in FFI and have pointer-type parameters must update their code accordingly.

Toolchain Updates

  • IDE now supports hover and go-to-definition features for .mbti files.

20250811 MoonBit Monthly Update Vol.02

· 5 min read

Language updates

  1. New conditional compilation attribute cfg.
  • You can now compile specific sections of code based on conditions such as the target backend.
    #cfg(any(target="js", target="wasm-gc"))
    let current_target = "js | wasm-gc"
  1. New #alias attribute.
  • You can now create aliases for methods or functions and attach annotation information. More scenarios will be supported in the future.。
    #alias(combine, deprecated="use add instead")
    fn Int::add(x : Int, y : Int) -> Int {
      x + y
    }
    
    test {
      let _ = Int::add(1, 2)
      let _ = Int::combine(1, 2)
    }
  1. New defer statement.
  • Provides a scope-based resource cleanup feature. When any form of defer expr; body appears in the body of a block, the expr will always be executed when the body ends.
    fn main {
      defer println("End of main")
      {
        defer println("End of block1")
        println("block1")
      }
      for i in 0..<3 {
        defer println("End of loop \{i}")
        if i == 2 {
          break // `break` and similar statements can also trigger `defer`
        }
        println("Looping \{i}")
      }
      return
    }
    block1
    End of block1
    Looping 0
    End of loop 0
    Looping 1
    End of loop 1
    End of loop 2
    End of main
    Currently, the expr in a defer expr cannot contain expressions or calls to async functions, and expr cannot use control flow constructs such as return / break / continue.
  1. Native Backend Bytes Terminator Update
  • In the Native backend, the Bytes representation used to always have an extra trailing '\0' character. Now, Bytes can be directly used to pass C strings in FFI calls without this extra trailing '\0' character being counted in the Bytes length, so the current code behavior will remain unchanged.
  1. Optional Parameter Syntax Enhancement and Unification
  • Adjusted the syntax for optional parameters: default arguments can now depend on preceding parameters (this behavior was previously removed due to incompatibility with virtual packages, but we have now found a way to support such complex defaults while remaining compatible).We have also unified optional parameters with default values (label: T = ...) and those without (label?: T). From the caller’s perspective, there is no longer any difference between them, and both now support the following call styles:
    • Omit the argument to use the default value.
    • Pass explicitly using label=value.
    • Use label?=opt, meaning: if opt is Some(value), it is equivalent to label=value; if opt is None, it is equivalent to omitting the argument.
  1. Use #callsite(autofill(...)) as a shorthand for default arguments.
  • When calling functions with default arguments, the #callsite(autofill(...)) attribute can be used as a shorthand:

      // Original code
      pub fn[T] fail(msg : String, loc~ : SourceLoc = _) -> T raise Failure { ... }
      // new code
      #callsite(autofill(loc))
      pub fn[T] fail(msg : String, loc~ : SourceLoc) -> T raise Failure { ... }
  1. Removed newtype
  • added support for tuple struct.

      // Old syntax, accessing the wrapped Int
      type A Int
      fn get(a : A) -> Int {
        a.inner()
      }
    
      // New syntax, accessing the wrapped Int
      struct A(Int)
      fn get(a : A) -> Int {
        a.0
      }
    
      struct Multiple(Int, String, Char)
      fn use_multiple(x: Multiple) -> Unit {
        println(x.0)
        println(x.1)
        println(x.2)
      }
      fn make_multiple(a: Int, b: String, c: Char) -> Multiple {
        Multiple(a, b, c)
      }
  • For a tuple struct with one field, it is equivalent to the original newtype. If the underlying type is not a tuple, the formatter will auto-migrate old access syntax. In this case, an .inner() method is provided for migration and will be deprecated later.

  • For a tuple struct with multiple fields, differences from the original tuple newtype are:

    • Cannot be constructed directly with tuple syntax.
    • No .inner() method to retrieve the tuple.

    For tuple structs that support conversion to a tuple, you can use:

    struct T((Int, Int))
    
    fn make_t(x: Int, y: Int) -> T {
      (x, y)
    }
    
    fn use_t(t: T) -> (Int, Int) {
      t.0
    }
  • In this case, to access specific elements, you need to use t.0.0 or t.0.1.

  1. Since the primary purpose is for data storage and functions such as @json.inspect, derive(FromJson, ToJson) will no longer provide advanced format adjustment parameters.
  • The currently retained format parameters are: rename for each field, batch renaming, and the style option for enum format selection; all other parameters will be removed.
    • The optional values for style are legacy and flat. The latter simplifies representation and is suitable for scenarios such as @json.inspect. All enums must currently choose one of these styles.
    • If you need to customize the JSON format, please implement the FromJson and ToJson traits yourself.
    ///| Flat
    test {
      @json.inspect(Cons(1, Cons(2, Nil)), content=["Cons", 1, ["Cons", 2, "Nil"]])
    }
    
    ///| Legacy
    test {
      @json.inspect(Cons(1, Cons(2, Nil)), content={
        "$tag": "Cons",
        "0": 1,
        "1": { "$tag": "Cons", "0": 2, "1": { "$tag": "Nil" } },
      })
    }

Toolchain update

  1. Added moon coverage analyze for clearer coverage reports.
Total: 1 uncovered line(s) in 2 file(s)

1 uncovered line(s) in src/top.mbt:

   | fn incr2(x : Int, step? : Int = 1) -> Int {
12 |   x + step
   |   ^^^^^^^^         <-- UNCOVERED
   | }


Total: 1 uncovered line(s) in 2 file(s)
  1. moon test --target js now shows original source locations on panic via sourcemap.
test username/hello/lib/hello_test.mbt::hello failed: Error
    at $panic ($ROOT/target/js/debug/test/lib/lib.blackbox_test.js:3:9)
    at username$hello$lib_blackbox_test$$__test_68656c6c6f5f746573742e6d6274_0 ($ROOT/src/lib/hello_test.mbt:3:5)
    at username$hello$lib_blackbox_test$$moonbit_test_driver_internal_execute ($ROOT/src/lib/__generated_driver_for_blackbox_test.mbt:41:9)

20250714 MoonBit Monthly Update Vol.01

· 2 min read

After the beta release on June 18, 2025, Moonbit's syntax will be more stable, and our focus will gradually shift towards performance improvements and ecosystem development. Starting with this announcement, Moonbit updates will be released monthly, with the primary content still centered on language, standard library, and toolchain enhancements.

Language Updates

  • Support for !expr syntax. Boolean expressions can now be directly negated using the ! symbol, without needing the not function.
fn true_or_false(cond: Bool) -> Unit {
  if !cond {
    println("false branch")
  } else {
    println("true branch")
  }
}

fn main {
  true_or_false(true)  // true branch
  true_or_false(false) // false branch
}
  • The else keyword in try .. catch .. else .. syntax has been replaced with noraise. This change was made because the else in try .. catch .. else .. is followed by pattern matching rather than a code block, which is inconsistent with else elsewhere. The old syntax will be deprecated, and the compiler will issue a warning.
  • Functions can now be marked noraise in their return type. This provides clearer documentation in type signatures and can prevent the compiler from automatically inserting a raise mark in certain situations. For example:
fn h(f: () -> Int raise) -> Int { ... }

fn init {
  let _ = h(fn () { 42 }) // ok
  let _ = h(fn () noraise { 42 }) // not ok
}
  • Ellipsis (...) is now allowed to omit code in pattern matching. For example:
fn f(x: Int) -> Unit {
  match x {
    ...
  }
}

Toolchain Updates

  • More powerful code coverage testing. You can now use the moon coverage analyze command to directly identify unused lines of code. For example:
fn coverage_test(i : Int) -> String {
  match i {
    0 => "zero"
    1 => "one"
    2 => "two"
    3 => "three"
    4 => "four"
    _ => "other"
  }
}

test "coverage test" {
  assert_eq(coverage_test(0), "zero")
  assert_eq(coverage_test(1), "one")
  assert_eq(coverage_test(2), "two")
  // assert_eq(coverage_test(3), "three")
  assert_eq(coverage_test(4), "four")
  assert_eq(coverage_test(5), "other")
}
  • Running moon coverage analyze on the code above will first execute the tests and then print the lines not covered during test execution, as shown below:
 moon coverage analyze
Total tests: 1, passed: 1, failed: 0.

warning: this line has no test coverage
 --> main/main.mbt:6
4 |     1 => "one"
5 |     2 => "two"
6 |     3 => "three"
  |     ^^^^^^^^^^^^
7 |     4 => "four"
8 |     _ => "other"
  • This tool will be a great help in guiding your testing efforts.

Standard Library Updates

  • Reminder: JSON data definitions will change in the next version. Please do not directly use constructors; instead, use functions like Json::number to construct JSON values.

2025-06-16

· 6 min read

Language Updates

  1. Error Function Syntax Updated to raise
  • The ! syntax for representing errors has been replaced with the keyword raise. The specific mappings are as follows:

    • (..) -> T ! SomeErr => (..) -> T raise SomeErr

    • (..) -> T ! => (..) -> T raise

    • (..) -> T ? Error => (..) -> T raise? (This is a recently added error polymorphism syntax; it can be ignored if unfamiliar.)

    • fn f!(..) { .. } => fn f(..) raise { .. }

    • fn!( ..) { .. } => fn (..) raise { .. }

    The above changes can be auto-migrated using code formatting.

  1. Error Type Declaration Syntax Changed
  • The syntax for defining error types, type! T .., has been changed to suberror T ... This change can also be automatically migrated using code formatting.
  1. Deprecation of f!(..) and f?(..)
  • The f!(..) and f?(..) syntaxes have been deprecated. Continued use of them will trigger compiler warnings. Code formatting can automatically remove ! for migration, but f?(..) requires manual migration to try?. This is because for cases like f?(g!(..)), a simple change to try? f(g(..)) would alter the semantics, causing errors in g to also be caught. Special attention is needed during manual migration of f?(..).
  1. Function Type Parameter Syntax Aligned with impl
  • Several weeks ago, the position of type parameters in function definitions was moved from fn f[..](..) to fn[..] f(..) to align with impl. Now, the old syntax is deprecated and will trigger compiler warnings. This change can be automatically migrated using code formatting.
  1. Simplified Syntax for typealias and traitalias
  • Use typealias B as A and traitalias B as A instead of typealias A = B and traitalias A = B. Example:

    Old: typealias Matrix[X] = Array[Array[X]]

    New: typealias Array[Array[X]] as Matrix[X]

    This change can be auto-migrated.

  1. Multi-parameter loop syntax deprecated
  • The multi-parameter loop syntax has been deprecated and should be replaced with a loop that takes a tuple as its parameter. This change aligns loop with match more consistently. The MoonBit compiler can optimize away the overhead of tuples in loop in release mode, so there is no need to worry about performance impacts.
  1. Trait Method Implementation Now Explicit
  • For traits where "every method has a default implementation," previously all types would automatically implement them. Now, even if all methods of a trait have default implementations, explicit implementation is still required. If no custom implementation is needed, impl Trait for Type can be used to indicate "implement Trait for Type using all default methods." impl Trait for Type can also serve as documentation or a TODO. MoonBit will automatically check whether Type implements Trait and report an error if it does not.
  1. Cannot Use Dot Syntax Call Implementation Method for External Types
  • Previously, impl for external types could be called within the current package using .. However, this feature was not refactoring-safe: upstream additions of methods could alter the behavior of downstream code. Therefore, this behavior has been deprecated. As an alternative, MoonBit now supports locally defining new methods for external types, with syntax identical to regular method definitions. Methods defined for external types have the following characteristics:

    • They cannot be pub. This ensures no conflicts arise during cross-package collaboration.

    • If the upstream package (where the type is defined) already has a method with the same name, the compiler will issue a warning.

    • Local methods have the highest priority during method resolution.

    After this change, the resolution rules for x.f(..) are as follows (in order of priority):

    1. Local methods
    2. Methods from the package where x's type is defined
    3. impl from the package where x's type is defined
  1. Auto Insertion of to_json for JSON Literals
  • Within JSON literals, the compiler now automatically inserts ToJson::to_json calls for non-literal expressions, making JSON literals more convenient to write:
let x = 42
// Previously
let _ : Json = { "x": x.to_json() }
// Now
let _ : Json = { "x": x }
  1. Virtual Package Support for Abstract Types
  • The virtual package feature now supports abstract types. Abstract types can be declared in .mbti interfaces, and different implementations can use different concrete types to fulfill the interface's abstract types.
  1. Simplified Syntax for Error Handling
  • For handling errors in simple expressions, try can be omitted, and f(..) catch { .. } can be written directly.
  1. Reserved Words Warning Mechanism
  • A new set of reserved words has been added. These are not currently keywords but may become keywords in the future. Using these names in code will trigger compiler warnings.

Upcoming Changes to Be Released Before the MoonBit Beta on June 18

  1. New Arrow Function Syntax (..) => expr

    This syntax simplifies defining single-parameter inline functions. Example:

test {
  let arr = [ 1, 2, 3 ]
  arr
    .map(x => x + 1) // Parentheses can be omitted for single parameters
    .iter2()
    .each((i, x) => println("\{i}: \{x}"))
}
  1. Matrix Function Syntax Simplified

    Matrix functions have been deprecated to simplify the syntax. Matrix functions of the form fn { .. => expr } can be replaced with arrow functions, while other matrix functions should be replaced with explicit fn and match.

  2. Deprecation of xx._ Syntax in new type Definitions

    Previously, the xx._ syntax could be used to convert a newtype into its underlying representation. However, this syntax was visually ambiguous with partial application syntax (_.f(..)). Therefore, the xx._ syntax has been deprecated. Instead, the compiler will automatically generate an .inner() method for each newtype to replace ._. This change can be automatically migrated using code formatting.

  3. Warnings on Ambiguous Precedence

    For ambiguous or less widely known operator precedence combinations, such as << and +, MoonBit will now issue warnings. Adding parentheses manually or via code formatting will resolve the warnings.

  4. Introduction of letrec and and for Local Mutual Recursion

    The letrec and and keywords have been introduced for declaring locally mutually recursive functions, e.g.:

fn main {
  letrec even = fn (x: Int) { ... } // Anonymous function
  and odd = x => ... // Arrow function
}
  • The right-hand side of the equals sign must be a function-like value, such as an anonymous function or arrow function. The previous implicit mutual recursion syntax using fn will be deprecated, though self-recursive functions can still be declared with fn.*
  1. fnalias Cannot Be Used to Define Non-Function values

    The fnalias keyword is now restricted to function definitions only. Use let to define other types of values.

Standard Library Updates

  • Leveraging the new error polymorphism feature, many higher-order functions in the standard library, such as Array::each, can now accept callback functions that may raise errors.

Toolchain Updates

  • Tests can now be written in the main package. moon test will run tests in the main package, while moon run will execute the main function.
  • The IDE's codelens now supports running tests in documentation.
  • moon test and moon check now include tests in documentation by default.

MoonBit Beta Launch on June 18 — Developer Updates Transition to Monthly Reports

After extensive refinement and continuous improvement based on community feedback, the MoonBit Beta version will be officially released on June 18, marking the transition to a more stable stage of the language.

Starting from this milestone, the MoonBit biweekly report will shift to a monthly cadence. Please stay tuned to this column for future updates.

We also welcome community feedback and discussion — feel free to share your thoughts and suggestions with us!