20260819 MoonBit v0.10.9 Release
moonc version: v0.10.9
Language Updatesβ
-
with-patterns now require explicit parentheses around branches that usewith, making the precedence ofwith-patterns easier for readers to understand. Users can migrate automatically withmoon fmt:fn main { let a = Some("hello") match a { Some(x) | (None with x = "") => println(x) // ^~~~~~~~~~~~~~~~~~ parentheses required here } } -
Bitstring patterns now support
v128le, which extracts 16 bytes at once from a byte sequence and constructs aV128value. Currently only byte-granularity little-endian extraction is supported, i.e. byte 0 of the input maps to the least significant byte of the result:fn main { let bits = Bytes::makei(16, i => i.to_byte()) guard! bits is [v128le(bits), ..] println(bits) // prints V128(0x0706050403020100, 0x0f0e0d0c0b0a0908) } -
lexscanis now officially stable.-
lexscansupports@lexbuf.Lexbuf,@lexbuf.AsyncLexbuf, and@lexbuf.StringScanner.LexbufandAsyncLexbufoperate in streaming mode; we have optimized their memory usage, so you can safely use them on infinite streams. -
The previous
lexscanonString/StringViewhas been migrated to thelexmatchkeyword. -
A catch-all case is no longer required when it is provably unreachable, and the compiler now reports warnings for unreachable cases.
///| async fn wordcount( input : @lexbuf.AsyncLexbuf, lines : Int, words : Int, chars : Int, ) -> (Int, Int, Int) { lexscan input { re"^\n" => wordcount(input, lines + 1, words, chars + 1) re"^[^ \t\r\n]+" as word => wordcount(input, lines, words + 1, chars + word.length()) re"^." => wordcount(input, lines, words, chars + 1) re"^" => (lines, words, chars) } } ///| async fn main { let utf8_reader = Utf8Reader(() => @stdio.stdin.read_some()) let lexbuf = @lexbuf.AsyncLexbuf::from_fn(() => utf8_reader.read()) let (lines, words, chars) = wordcount(lexbuf, 0, 0, 0) println("lines: \{lines}, words: \{words}, chars: \{chars}") }For details, see the documentation:
-
-
Introduced the new
errdefersyntax:errdefer expr restIf
restraises an error, or is cancelled while running async code, theerrdeferstatement will be triggered, executingexpr. The error will then be re-raised.errdeferis especially useful for constructor-style functions, for example:async fn connect_to(addr : @socket.Addr) -> Tcp { let socket = make_tcp_socket() errdefer socket.close() connect_socket(socket) socket }Here, if
connect_toreturns normally, ownership ofsocketis transferred to the caller through the return value, sosocketshould not be released. But ifconnect_socket(socket)raises an error or is cancelled,socketwill not be returned. In this case,socketshould be released to avoid a resource leak.errdeferhandles this kind of resource cleanup robustly.Leaving the scope of an
errdeferviareturn/break/continuedoes not trigger it. Likedefer,errdeferis structured: it only fires when the program leaves the scope of the entireerrdeferexpression. So the following program is wrong:let result = [] for x in xs { let res = make_resource(x) errdefer res.close() do_something_with_res(res) result.push(res) } resultHere, each
errdeferonly covers the single loop iteration it belongs to. After the first iteration completes, the program has left the scope of that iteration'serrdefernormally, so thaterrdeferwill never fire again. If the second iteration then fails, only the second iteration's ownerrdeferfires, and the result of the first iteration leaks. The correct version is:let result = [] errdefer result.each(res => res.close()) for x in xs { let res = make_resource(x) do_something_with_res(res) result.push(res) } result -
deferanderrdefernow supportraiseandasync. Previously, theexprindefer exprcould not raise errors or call async code. This restriction has been lifted. If an error is raised inside adefer/errdefer, the new error replaces the old one. When there are multipledefer/errdeferstatements and one of them raises, the remaining ones still execute in order and are not discarded. -
Added a new warning for migrating
catchtodefer/errdefer.Currently, in
moonbitlang/async, a cancelled async program raises a special error as the cancellation signal, so that the cancelled program can release its resources. But this special error can be accidentally caught or transformed, causing the program to misbehave when cancelled. In the future, we plan to stop using a special error to represent the cancellation signal, and makecatchno longer catch it. However, if a program relies oncatchto perform resource cleanup, this change would prevent it from releasing resources correctly on cancellation. That is why we introducederrdeferand lifted the side-effect restrictions ondefer: nearly all resource-release code can now be expressed withdefer/errdefer(and the cancellation signal will continue to triggerdeferanderrdeferin the future).To help users migrate existing
catch-based resource-release code, we provide a new warning,fragile_catch_all. It identifiescatchexpressions that could likely be rewritten asdefer/errdeferand emits a warning prompting migration. Besides handling async cancellation correctly in the future,defer/errdeferare also more readable and robust thancatch.This new warning may produce false positives. If that happens, you can temporarily disable it for the current function with
#warnings("-fragile_catch_all"). -
guardnow performs exhaustiveness checking and warns on non-exhaustive patterns. Users who want the previous panic-on-no-match semantics should migrate toguard!to state that intent explicitly.fn main { let string = Some("content") guard string is Some(content) // ^~~~~~ Warning (guard_inexhaustive): // This `guard` pattern is not exhaustive and will panic when // it does not match. Missing cases: // None // To fix: add an `else { ... }` clause after the condition to // handle those cases, or write `guard!` if the panic is intended. guard! string is Some(content) // the recommended new form println(content) } -
Labelled blocks are now supported. Once a block is labelled, you can use
breakwith a value inside it to exit early, and that value becomes the result of the whole block.fn absolute(n : Int) -> Int { result~: { if n < 0 { break result~ (-n) } n } }Note that labelled blocks have no anonymous form: an unlabelled
breakalways targets the nearest loop, never a block. To avoid ambiguity, MoonBit specifies that an unlabelledbreakappearing directly inside a labelled block is always an error, even when there is indeed an enclosing loop it could break out of. In that case you must use a label to state explicitly which layer of control flow to exit.fn f() -> Int { for ;; { label~: { break 1 // ^^^^^^^ An unlabelled `break` is not allowed directly inside a labelled block. } } } -
Added a new reserved word:
nocancel. -
#warningsnow also works on syntax warnings. Previously,#warningscould only suppress warnings from the type-checking phase; some syntax warnings, such asdeprecated_syntax, could not be disabled through#warnings. This has been fixed:#warningscan now locally suppress most warnings, including syntax warnings. A few warnings that span top-level definitions, as well as lexical warnings, still cannot be suppressed with#warnings.
Toolchain Updatesβ
-
The default target is now
wasm. -
moon provenow works out of the box as long as at least one supported solver (Z3 / Alt-Ergo / CVC5) is installed; a separate Why3 installation is no longer required. Correspondingly, Why3'sdata-dirandlib-dircan no longer be specified via environment variables; they are always read from~/.moon/share/why3/and~/.moon/lib/why3/. -
You can now run executables from mooncakes.io with
moonx username/example[@version](executed on the WASM backend by default; pass--target nativeto run on the native backend):$ moonx moonbit-community/moongrep error: the following required argument was not provided: 'subcommand' Usage: moongrep <command> Scan MoonBit source files with structural and taint rules. Commands: scan Scan MoonBit source files. lint Scan MoonBit source files with embedded builtin rules. docs Print embedded moongrep documentation. dump Parse a MoonBit impl or expression and print untyped_ast debug output. help Print help for the subcommand(s). Options: -h, --help Show help information. -
Added support for
.moonignorefiles.-
Previously, whether files were included or excluded when publishing to mooncakes.io was configured by
.gitignoreplus the"exclude"and"include"fields inmoon.mod, which was somewhat cumbersome. -
Packaging now follows conventional ignore-file rules: the
.moonignorein a folder β or, if absent,.gitignoreβ is used as the ignore file. -
By default, files and folders starting with
.are ignored (this rule can be overridden via the ignore file), as is the_buildfolder (not overridable). -
The
"exclude"and"include"fields inmoon.modwill be deprecated.
-
-
mooncakes.io previously allowed uploading packages whose names differ only in case (e.g.
user/pkgaanduser/pkgA). This causes problems on case-insensitive platforms, so mooncakes.io no longer allows uploading packages that differ only in case.
Standard Library Updatesβ
-
moonbitlang/core-
QuickCheck updates
-
Two main testing entry points are now provided:
@qc.check(raises an error on failure, and prints nothing extra on success) and@qc.report(returns a structured test report). Users can pass a function(A) -> Bool raise?for property-based testing. -
You can pass a
filter?: (A) -> Boolparameter to the test functions to filter out generated values that do not meet requirements; thediscard_ratioparameter controls at what proportion of discarded values the test fails. -
The
@qc.Generator[T]type and related functions provide a set of common combinators to help buildArbitraryinstances. -
The
core/quickcheck/shrinkpackage providesShrinkers for most common types; once a counterexample is found, it can be shrunk to search for a smaller, simpler counterexample. -
Statistical analysis is supported: you can pass an observation combinator
(A) -> Observationtocheck/reportvia theobserve?parameter, where anObservationcan be constructed with the following functions:-
@qc.label(val: String)attaches a string label -
@qc.classify(cond: Bool, val: String)attaches the labelvalwhen the conditioncondholds -
@qc.collect(val : T)uses a value's debug representation as the label
-
-
For more details, see the documentation: https://mooncakes.io/docs/moonbitlang/core/quickcheck
-
-
New
moonbitlang/core/diffpackage-
Provides two general-purpose sequence diff algorithms, Myers and Patience. Users can obtain the edit script between two sequences via
@diff.Diff(old~, new~).edits(). -
Provides several functions for computing edit distances between sequences β
edit_distance(ArrayView[T]),edit_distance_str(StringView)β along with variants that cap the maximum edit distance.
-
-
New
moonbitlang/core/lexbufpackage, providingStringScanner,Lexbuf, andAsyncLexbuffor use withlexscan.-
StringScanner: a synchronousString-based scanner;lexscanmaintains thecursorfield on the scanner. -
Lexbuf/AsyncLexbuf: streaming scanners whose data source is defined viaLexbuf::from_fn; they refill automatically duringlexscan. The difference between the two is that alexscanexpression over anAsyncLexbufrequires an async context as a whole.
-
-
The
immut/arraypackage, deprecated for a long time, has now been formally removed; useimmut/vectorinstead. -
@debug.to_repr(x)is deprecated; use@debug.Repr(x)instead.
-
-
moonbitlang/asyncis now at 0.21.0. The main updates since the last monthly report (0.20.2) are:
-
[breaking] In the
@httppackage, the type of HTTP headers changed fromMap[String, String]to the case-insensitivetype @http.Headers = Map[@http.CaseInsensitiveString, String], so you no longer need to handle case-folding manually when constructing or reading HTTP headers.@http.CaseInsensitiveStringcan be implicitly constructed fromString, so code that builds headers withMapliterals or reads headers needs no changes.Code that wrote explicit type annotations for headers must change the type to
@http.Headers. -
[breaking] The
createandtruncateparameters of@fs.openand related APIs had been deprecated for a while, replaced bycreate_modeandpermission.In this release,
createandtruncateare completely removed.In addition, the default
create_modeof@fs.write_fileand@process.redirect_to_filechanged fromOpenExistingtoCreateOrTruncate.The default
create_modeof@fs.openremainsOpenExisting. -
[breaking] The default behavior of
@async.protect_from_cancelis nowresume_on_cancel=true, and theresume_on_cancel=falseoption is deprecated.Going forward, only the
resume_on_cancel=truebehavior will exist.In
moonbitlang/async, cancellation is implemented by the runtime as a persistent attribute attached to each task.Once a task is cancelled, it remains cancelled until it finishes, and users cannot revoke that state.
To help cancelled code release resources, the runtime notifies it with a special error, triggering
deferetc.However, catching this cancellation signal does not change the fact that the current task is cancelled.
When cancelled,
protect_from_cancel(resume_on_cancel=false)guarantees the inner code runs to completion, then discards its result and raises the cancellation signal.This is unsafe because the discarded result can lead to resource leaks.
protect_from_cancel(resume_on_cancel=true)does swallow the cancellation signal, but it does not affect the cancelled state of the current task: the next async operation after it will still be cancelled, which makesresume_on_cancel=truethe more sensible behavior.For most user code, this behavior change has no substantive impact.
-
moonbitlang/asyncnow automatically detects deadlocks (e.g. two tasks waiting on each other) and forcibly terminates the program, preventing the event loop from spinning idly forever.You can use
@async.set_deadlock_handlerto control the behavior on deadlock or to disable deadlock detection. -
Previously,
moonbitlang/asynchad to run its own event loop on the main thread, so it could not integrate with external event loops, such as those that ship with GUI frameworks.This update adds the
@async.set_external_event_loopAPI for installing an external event loop.moonbitlang/asyncwill run its own event loop on a separate thread and integrate it with the external loop on the main thread.All MoonBit code still runs on the main thread.
For the APIs an external event loop implementation must provide to
moonbitlang/async, see the documentation of@async.set_external_event_loop. -
Added
@process.pipe, which redirects the output of one child process into the input of another. -
@process.read_from_processand@process.redirect_to_filegained ashared? : Bool = falseparameter.With
shared=true, the output pipe used for redirection can be passed to multiple child processes at the same time, but it must be closed manually with.close()after the last child process has started.With
shared=false(the default and previous behavior), the output pipe can only be passed to a single child process (though it may be passed to bothstdoutandstderrof that same process), and needs no manual close. -
Added the helper function
@http.requestfor performing a single HTTP request with an arbitrary method. -
The Wasm1 backend gained
@websocketand@fs.realpathsupport; it now supports all functionality except@fs.Watcher. -
On Linux/macOS, when a child process is terminated by a signal rather than exiting normally, APIs such as
@process.runrecognize this and return-signal. -
Previously, when an
async fn mainprogram was cancelled by a signal, it would exit with128 + signalas its exit code after releasing resources (the bash convention).But this is ambiguous to the parent process.
Now, when cancelled by a signal,
async fn mainfinishes cleanup and then re-simulates the state of the current process being forcibly terminated by that signal as the program's exit status.
-
moonbitlang/x-
Deprecated
moonbitlang/sys; usemoonbitlang/core/envinstead. -
moonbitlang/x/pathnow works correctly in browser environments, and Windows path comparison now correctly handles non-ASCII characters that have case forms. -
moonbitlang/x/rationalno longer suffers from overflow misjudgments or zero-denominator issues.
-