20261105 MoonBit v0.10.19 Release
moonc version: v0.10.19
Language Updatesβ
Derive syntax migrationβ
Trait derivation moves from derive(...) after a type definition to an attribute above it. Run moon fmt to convert existing code. Attribute syntax no longer supports #foo(qual.id) and now supports #foo(@pkg.id) and #foo([elem1, elem2, elem3]).
#derive(Debug, Eq, Hash)
pub(all) struct Point {
x : Int32
y : Int32
}
Int/UInt renamed to Int32/UInt32β
Int32 and UInt32 represent 32-bit signed and unsigned integers. Explicit widths make code easier to read when several widths appear together, such as in binary protocols or FFI signatures. Int and UInt remain available as aliases, and both spellings can coexist in a project. The formatter can migrate the old spellings to the explicit-width names.
Floating-point semanticsβ
Arithmetic and comparison operators (==, !=, <=, <, >=, >) continue to follow IEEE semantics. -0.0 == 0.0, and NaN is unequal to every value, including itself. The main change concerns compare when NaN is involved. Its results for NaN are currently unspecified. This mainly affects sorting and binary search, and NaN should not be used as a key in structures such as hash tables. Use the new Float::total_compare or Double::total_compare if you need defined comparison behavior for NaN. Both use the following order.
NaN-Infinity- Negative numbers
-0.0and0.0, which compare equal- Positive numbers
+Infinity
Changes to extenum declarationsβ
The old form combines the type declaration and its constructors in one definition.
extenum E {
A
}
Declare the type with pub extenum E, then add constructors with extenum E += { ... }:
pub extenum E
extenum E += {
A
}
The type declaration only supports pub visibility. A private extensible enum cannot be extended downstream and can be replaced with an ordinary enum; pub(all) has the same downstream effect as pub, so declarations use pub consistently. Constructor extensions can still use pub(all), pub, or priv to control visibility. The old syntax now produces a deprecation warning, and moon fmt migrates it automatically.
New byte regex literals, reb"..."β
Use reb"..." with =~ and lexmatch to match Bytes and BytesView, much as re"..." matches String and StringView. A byte regex matches bytes without encoding or decoding, so it can match arbitrary byte sequences. Its syntax is largely the same as that of re"...". Capture types are determined statically by the regex. A capture that matches exactly one byte has type Byte, as in reb"." as b. Other captures have type BytesView, as do before and after. In ordinary expressions, a reb"..." literal has type BytesRegex. Like Regex, it can be assigned to a constant, combined with + and |, and referenced in patterns.
fn parse_request(line : Bytes) -> Unit {
if line =~ (reb"^(GET|POST|PUT) " + (reb"[^ ]+" as path), after=rest) {
...
}
}
fn content_length(header : BytesView) -> BytesView? {
lexmatch header {
(reb"^(?i:content-length): " + (reb"[0-9]+" as n), after=_) => Some(n)
_ => None
}
}
- Only ASCII characters may appear directly in a literal. Use
\xHHfor non-ASCII bytes, including in character classes and ranges. Examples includereb"\xef\xbb\xbf"andreb"[\x80-\xff]+". Neither\u{...}nor direct non-ASCII characters are supported. For example, writereb"\xc3\xa9"forreb"Γ©". .and[^...]match any single byte, including 0x80β0xFF. Therefore,.matches only the first byte of a multibyte UTF-8 sequence.(?i:...),\b, and[[:word:]]use ASCII semantics. Bytes above 0x7F are neither case-folded nor treated as word bytes.
const HIGH : BytesRegex = reb"[\x80-\xff]"
const HIGH_THEN_A : BytesRegex = HIGH + reb"a"
lexscan currently does not support byte regexes.
Upcoming breaking changeβ
In the next release, we will change the runtime layout of Array on the JavaScript backend to align with the other backends. Array will no longer compile to a native JavaScript Array.
Top-level declare letβ
Declarations previously focused on interfaces such as functions and types. You can now declare a top-level value's name, type, and visibility with declare let, then provide a matching definition in the same package. This lets you review the package's public interface before deciding which file holds the value or how it is initialized.
///|
declare pub let default_limit : Int32
///|
pub let default_limit : Int32 = 100
Toolchain Updatesβ
Build static libraries with the Native backendβ
The following MoonBit program uses #export_name to specify an exported symbol. Set pkgtype(kind: "foreign_library") in moon.pkg, then run moon build --target native to produce an archive a host program can link. The output is an .a file, or a .lib file on Windows.
let offset : Int32 = 2
#export_name("add")
pub fn add(a : Int32, b : Int32) -> Int32 {
a + b + offset
}
$ moon build --target native
Finished. moon: ran 28 tasks, now up to date
Artifacts:
username/foreign/lib native path/to/_build/native/debug/build/lib/lib.a
On the host side, call moonbit_init() to initialize the MoonBit runtime.
#include "moonbit.h"
#include <stdio.h>
extern int32_t add(int32_t a, int32_t b);
int main(void) {
moonbit_init();
printf("add(40, 2) = %d\n", add(40, 2));
return 0;
}
Then compile the host program together with the static library generated by MoonBit and run it.
$ cc -I ~/.moon/include host.c path/to/_build/native/debug/build/lib/lib.a -lm -o host
$ ./host
add(40, 2) = 44
Migrate configuration in .mbtx script modeβ
A script can place configuration between --- delimiters at the top of the file to keep dependencies and runtime permissions together. The header uses the configuration syntax of moon.pkg and moon.mod. A script that needs a host environment variable can declare the requested permission here. The import syntax continues to support aliases and import-all. An import can combine * with a package alias. In the example below, both Queue and @q.Queue refer to the same definition. With * alone, the default package alias remains available.
---
import {
"moonbitlang/core/queue" @q *,
"moonbitlang/x/bcrypt",
}
---
fn main {
println(Queue([1, 2]).length())
println(@q.Queue([3, 4, 5]).length())
}
Generate .mbti files automatically with moon checkβ
After a successful check, moon check updates pkg.generated.mbti for the checked local packages whose preferred backend is included in the check. The file summarizes public types, functions, and traits, making API changes visible alongside source changes in code review. Run moon check --no-mbti to perform type checking without updating these files.
Documentation test improvementsβ
moon test already supported extracting and testing code blocks inside /// doc comments. We have improved extraction and parsing performance to make the coding-and-testing loop more efficient for agents using MoonBit. Test blocks no longer inherit the function's #cfg attribute. If a function supports only certain backends, add the appropriate #cfg marker to the test block itself.
///
/// ```mbt check
/// test {
/// assert_eq(greeting("alice"), "hi, alice")
/// }
/// ```
fn greeting(name : String) -> String {...}
Run test executables in parallel by defaultβ
Moon schedules test executables in parallel based on the available CPU count, reducing waiting time in projects with multiple test packages. Use moon test -j 4 to limit concurrency. For tests that share ports or external resources, use moon test --no-parallelize to run test executables sequentially while keeping the build parallel. This option does not affect concurrency among async tests within a single package.
LSP support for .mbtxβ
MoonBit language services can now handle .mbtx scripts, bringing editor support to standalone tools and automation scripts.
Debug the new Native backend in VS Codeβ
With the CodeLLDB extension installed and preferred-target in moon.mod set to native, click the Debug button above main or a test to start debugging. Debugging is now available for the new Native backend in addition to the LLVM and JavaScript backends.
Experimental moon testcli commandβ
This command tests MoonBit command-line applications and .mbtx scripts and can replace the previous cram subcommand.
Major-version module pathsβ
A module can use a path such as owner/library/v{n} to identify a new major version. When publishing breaking changes, consumers can distinguish major versions through their paths. The default alias of the module's root package remains @library. Use explicit aliases when importing two major versions in the same code. Before MoonBit 1.0, publishing packages at version 1.0 or later is temporarily unsupported.
Conditional importsβ
A package might use system libraries on Native and browser APIs on JavaScript. Use #cfg to choose dependencies for each backend while keeping the same alias. If both implementation packages below provide label(), call sites can use @platform.label() on either backend. The same mechanism applies to black-box and white-box test imports.
#cfg(target = "native")
import { "example/conditional/native_impl" @platform }
#cfg(target = "js")
import { "example/conditional/js_impl" @platform }
Experimental package declarationsβ
Declare a package experimental with experimental = true. Direct consumers receive a warning, which they can suppress with allow-experimental-packages = true.
Removal of legacy toolchain behaviorβ
- JSON manifests are no longer accepted. The
moon fmtmigration entry point remains available. moon installis no longer used to synchronize dependencies.moonxonly supports running Wasm programs.- Support for
bin-depshas been removed. Usemoonxinstead.moon add --binhas also been removed. - Automatic generation from
mblandmbyfiles is no longer supported. Usemoonxwith the corresponding libraries instead. - Versions specified in dependency declarations must actually exist.
- Using a core package without an explicit import now produces an error instead of a warning.
Standard Library and Runtime Updatesβ
New moonbitlang/core/async packageβ
It includes all the functionality of moonbitlang/async, with corresponding packages such as moonbitlang/core/async/http for moonbitlang/async/http. In the future, moonbitlang/core/async will become the default asynchronous runtime, and moonbitlang/async will be gradually deprecated. For now, moonbitlang/core/async is experimental and its APIs are still evolving. To use it, add "allow-experimental-packages": true to the options section of moon.pkg.
Its APIs include substantial improvements over moonbitlang/async. Once they stabilize, we will provide a detailed migration guide. moonbitlang/core/async and moonbitlang/async@0.22.7+ can coexist in one program, allowing applications to migrate incrementally.
Path utilities join the standard libraryβ
Import moonbitlang/core/path for basic file-path operations. As shown in the .mbtx script below, the package can extract a path's extension, filename, and parent directory. It also supports joining path components with /.
---
import {
"moonbitlang/core/path",
}
---
fn main raise {
let file1 = @path.Path("path/to/base/file1.mbt")
assert_eq(file1.extname(), ".mbt")
assert_eq(file1.basename(), "file1.mbt")
let base = file1.dirname()
let target = base / "fixture" / "file2.txt"
assert_eq(target, "path/to/base/fixture/file2.txt")
}
Compression and ZIP archivesβ
core/compress adds raw DEFLATE, gzip, and zlib codecs, while core/archive/zip adds ZIP and ZIP64 support. Choose the corresponding standard-library package to process gzip data, create ZIP files, or read archive entries. The archive API also exposes entry metadata, so you can inspect the contents before deciding which entries to read.
Other Standard Library Updatesβ
- Support for using
moonbitlang/core/abortas a virtual package has been removed. - The
Jsontype andToJsontrait have moved tomoonbitlang/core/json. Some built-in types no longer support calls in the forma.to_json(). UseToJson::to_json(a)orJson(a)instead. moonbitlang/x/timeimproves support for reading TZif (Time Zone Information) files and adds access to the system's current time zone, currently on the Native and JavaScript backends.
Ecosystem and Developer Tool Updatesβ
mooncakes.io improvementsβ
- Added registration, sign-in, and password recovery, and added dependents to documentation pages. Skill uploads are now separate from module uploads.
- Improved search results so modules matching by name consistently appear first.
SeekMoonβ
- Added a Conversations page, allowing SeekMoon to be used without attaching a workspace.
- Added cloud agents. Sign in, then create a conversation on the Conversations page to use them.
- Added image support. Paste images directly into the input box, or let the agent read images through a tool.
- Added scheduled tasks, which can be created from the sidebar.
- Integrated Word, Excel, and PDF previews. SeekMoon can now generate PDFs with Typst and preview them.
- Added built-in
/ultrareviewand/simplifyworkflows, which launch multiple subagents concurrently for code review and codebase simplification. - Added local pull request review. Open the GitHub page from the sidebar to browse a repository's pull requests and view GitHub review comments at the corresponding file locations.
CLI Command-Line Toolsβ
Two new command-line tools are available through moonx cli/typst and moonx cli/rg.