20260819 MoonBit v0.10.9
对应 moonc 版本:v0.10.9
语言更新
-
withpattern 现在要求使用with的分支显式加上括号,便于读者理解withpattern 的优先级。可以使用moon fmt自动迁移:fn main { let a = Some("hello") match a { Some(x) | (None with x = "") => println(x) // ^~~~~~~~~~~~~~~~~~ 需要加上括号 } } -
bitstring pattern 支持
v128le,可以从字节序列中一次提取 16 个字节,构造成一个V128值。目前只支持以字节为单位的小端序,即输入的第 0 个字节对应结果的最低位字节:fn main { let bits = Bytes::makei(16, i => i.to_byte()) guard! bits is [v128le(bits), ..] println(bits) // 输出 V128(0x0706050403020100, 0x0f0e0d0c0b0a0908) } -
lexscan正式进入稳定状态。-
lexscan支持@lexbuf.Lexbuf、@lexbuf.AsyncLexbuf和@lexbuf.StringScanner。其中Lexbuf和AsyncLexbuf采用流式模式;我们优化了它们的内存占用,可以放心用于无限流。 -
此前作用于
String/StringView的lexscan已迁移为lexmatch关键字。 -
现在,当 catch-all 分支明确不可达时无需再添加;编译器也会对不可达分支报告警告。
///| 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}") }具体请参考文档:
-
-
引入了新的
errdefer语法:errdefer expr rest如果
rest抛出了错误或者作为一段异步代码被取消了 ,errdefer就会被触发,执行expr,然后把错误继续向上抛出。errdefer对于构造器类的函数尤其实用,例如:async fn connect_to(addr : @socket.Addr) -> Tcp { let socket = make_tcp_socket() errdefer socket.close() connect_socket(socket) socket }当
connect_to成功返回时,socket的所有权会随返回值转移至调用方,因此当前作用域无需释放该资源。若connect_socket(socket)执行失败或被取消,socket会被丢弃,此时若不释放socket,就会导致资源泄漏。errdefer适用于此类仅在错误路径上执行清理的场景,可用于确保资源得到可靠释放。使用
return/break/continue跳出errdefer的范围不会触发errdefer。和defer一样,errdefer是结构化的:它只会在程序离开整个errdefer表达式的范围时触发。所以,下面的程序是错误的:let result = [] for x in xs { let res = make_resource(x) errdefer res.close() do_something_with_res(res) result.push(res) } result这里,每个
errdefer仅在其所在的单次循环作用域内有效。第一次循环正常结束后,对应的errdefer即不再生效。当后续循环发生错误时,只会执行当前循环对应的清理逻辑,此前循环中已成功创建的资源无法得到释放,进而导致资源泄漏。正确的写法是: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 -
defer和errdefer支持raise与async。之前,defer expr的expr中不能抛出错误或调用异步代码。这一限制现在已被解除。如果defer/errdefer中抛出错误,新的错误会替代旧的错误。有多条defer/errdefer语句时,如果其中某处defer/errdefer抛出了错误,剩下的defer/errdefer依然会按顺序执行,不会被丢弃。 -
新增将
catch迁移至defer/errdefer的警告。目前,在
moonbitlang/async中,被取消的异步程序会抛出一个特殊的错误作为被取消的信号,以方便被取消的程序释放资源。但这个特殊错误有可能被意外捕获、转换,从而导致程序在被取消时出现错误。未来,我们计划不再使用特殊错误来表示取消信号,并让catch不再捕获取消信号。但如果程序依赖于catch来执行资源清理,这一改动会导致程序在被取消时无法正确释放资源。因此,我们引入了errdefer并解除了defer的副作用限制,以保证几乎所有资源释放代码都可以用defer/errdefer表达(取消信号未来也依然会触发defer和errdefer)。为了帮助用户迁移现有的、基于
catch的资源释放代码,我们提供了一个新的警告fragile_catch_all。它会识别可能可以改写成defer/errdefer的catch表达式,并给出警告提示用户迁移。除了在未来能正确处理异步取消之外,defer/errdefer本身相比catch也更加可读和健壮。这一新警告可能会出现误报的情况。如果发生了误报,可以通过
#warnings("-fragile_catch_all")对当前函数临时关闭警告。 -
guard现在会进行完备性检查,对于不完备的 pattern 会报警告。对于希望使用之前的guard无法匹配则 panic 的语义的用户应该迁移到guard!来更加明确地表达自己的意图。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) // 推荐的新写法 println(content) } -
支持 labelled block,给代码块加上标签后,块内可以用
break携带一个值提前退出,该值就是整个块的求值结果。fn absolute(n : Int) -> Int { result~: { if n < 0 { break result~ (-n) } n } }Labelled block 不存在匿名形式:不带 label 的
break始终以最近的循环为目标,而不会作用于某个 block。为了避免阅读代码时的歧义,直接出现在 labelled block 内的不带 label 的break会被视为错误,即使外层存在可作为跳转目标的循环。此时必须显式指定 label,以明确需要退出的控制流层级。fn f() -> Int { for ;; { label~: { break 1 // ^^^^^^^ An unlabelled `break` is not allowed directly inside a labelled block. } } } -
增加了新的保留字
nocancel -
#warnings现可作用于语法警告。此前,它仅支持屏蔽类型检查阶段的警告,deprecated_syntax等语法警告不受影响。现在,#warnings已可局部屏蔽大多数警告,包括语法警告;部分跨顶层定义的警告和词法警告仍不支持。
工具链更新
-
默认后端改为 wasm
-
现在
moon prove只需要用户安装至少一个支持的求解器 (Z3 / Alt-Ergo / CVC5) 即可直接使用,不再需要单独安装 Why3。相应的, 现在 Why3 的data-dir和lib-dir不支持通过环境变量指定,始终读取~/.moon/share/why3/和~/.moon/lib/why3/。 -
现在可以通过
moonx username/example[@version]的方式来执行 mooncakes.io 上面的 WASM executable (默认以 WASM 后端执行,可以通过--target native的方式以 native 后端执行):$ 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. -
支持
.moonignore文件-
之前如果用户需要显式指定某些文件是否应包含在通过
moon publish发布到 mooncakes.io 的模块中,需要通过.gitignore配合moon.mod中的exclude和include字段来配置,修改起来不方便。 -
现在打包时遵守通行的 ignore 文件规则:使用文件夹中的
.moonignore,若不存在则使用.gitignore。 -
默认忽略以
.开头的文件和文件夹(可通过 ignore 文件覆写规则),以及_build文件夹(不可覆写规则)。 -
exclude和include字段将被废弃。
-
-
mooncakes.io 现已禁止上传仅大小写不同的包。此前,如
user/pkga与user/pkgA这样的包可以同时存在,但可能在大小写不敏感的平台上产生冲突,因此现在会对这类包名进行限制。
标准库更新
-
moonbitlang/core-
QuickCheck 更新
-
支持
@qc.check(失败会 raise 一个错误,成功则不打印其他东西) 和@qc.report(返回结构化的测试报告)两种主要测试函数,用户可以传递函数(A) -> Bool raise?来进行基于属性的测试 -
可以向测试函数传递
filter?: (A) -> Bool参数来过滤 Generator 一些不满足要求的值,利用参数discard_ratio可以控制测试在丢弃多少比例的时候会失效 -
@qc.Generator[T]类型和相关的函数提供了一套常用的组合子,可用于辅助构建Arbitrary实例 -
core/quickcheck/shrink包提供了大部分常用类型的 Shrinker (收缩器),可在找到反例的时候进行收缩,以寻找更小更简单的反例 -
支持统计分析功能,可以通过
observe?参数给 check / report 传递一个观测组合(A) -> Observation,其中Observation可以使用如下函数构造:-
@qc.label(val: String)标注一个字符串标签 -
@qc.classify(cond: Bool, val: String)在条件cond成立的时候打上标签val -
@qc.collect(val : T)把一个值的 Debug Repr 作为标签
-
-
更多具体细节可以参考文档:https://mooncakes.io/docs/moonbitlang/core/quickcheck
-
-
新增
moonbitlang/core/diff包-
提供 Myers 和 Patience 两种通用的序列 diff 算法。用户可以通过
@diff.Diff(old~, new~).edits()获取两个序列的编辑脚本。 -
提供多个计算不同序列编辑距离的函数,如
edit_distance(ArrayView[T])、edit_distance_str(StringView),以及对应的限制最大编辑距离的版本。
-
-
新增
moonbitlang/core/lexbuf包,提供StringScanner、Lexbuf和AsyncLexbuf,以配合lexscan使用。-
StringScanner:基于String的同步扫描器,由lexscan维护扫描器上的cursor字段。 -
Lexbuf/AsyncLexbuf:流式扫描器,由Lexbuf::from_fn定义数据源,并在lexscan过程中自动补充数据。两者的区别是,对AsyncLexbuf执行lexscan的整个表达式需要异步上下文。
-
-
immut/array包已弃用很长一段时间,现在正式移除,应改用immut/vector。 -
@debug.to_repr(x)弃用,可改用@debug.Repr(x)。
-
-
moonbitlang/async目前最新版本为 0.21.0,自上次月报(0.20.2)以来的主要更新有:-
[breaking]
@http包中,HTTP headers 的类型从Map[String, String]变成了大小写不敏感的type @http.Headers = Map[@http.CaseInsensitiveString, String],因此构造和读取 HTTP header 时不再需要手动注意大小写问题。@http.CaseInsensitiveString可以从String隐式构造,因此直接使用Map字面量构造 header 和读取 header 的代码无需修改。但对 header 写了类型标注的代码需要将类型改为@http.Headers -
[breaking]
@fs.open等 API 的create和truncate参数已被废弃一段时间,由create_mode和permission代替。这次更新中,create和truncate被正式移除。此外,@fs.write_file和@process.redirect_to_file的默认create_mode从OpenExisting变成了CreateOrTruncate。@fs.open的默认create_mode则依然是OpenExisting -
[breaking]
@async.protect_from_cancel的默认行为变为resume_on_cancel=true,resume_on_cancel=false选项被废弃。未来只会有resume_on_cancel=true的行为protect_from_cancel(resume_on_cancel=false)在被取消时,会保证内部的代码完整运行,然后丢弃其结果并抛出取消信号。这里被丢弃的结果可能导致资源泄露,因此是不安全的对大部分用户的代码来说,这一行为变动不会产生实质性的影响
-
moonbitlang/async现在会在程序陷入死锁状态(例如两个任务互相等待)时,自动检测到死锁并强行终止程序,防止事件循环无限空转。可以通过@async.set_deadlock_handler控制死锁时的行为或是关闭死锁检测 -
之前,
moonbitlang/async必须在主线程中运行自己的事件循环,因此无法与其他外部事件循环,例如 GUI 框架自带的事件循环整合。本次更新新增了@async.set_external_event_loopAPI,可以用于设置一个外部事件循环。moonbitlang/async会将自己的事件循环运行在单独的线程里,并和主线程的外部循环整合。所有 MoonBit 代码依然会运行在主线程里。关于外部事件循环的实现需要为moonbitlang/async提供哪些 API,详见@async.set_external_event_loop的文档 -
新增了
@process.pipeAPI,可以用于将一个子进程的输出重定向到另一个子进程的输入 -
@process.read_from_process和@process.redirect_to_file新增了shared? : Bool = false参数。如果shared=true,用于重定向的输出管道可以被同时传给多个子进程,但必须在最后一个子进程启动后通过.close()手动关闭。如果shared=false(默认行为,和之前相同),用于重定向的输出管道只能被传给一个子进程(但可以同时传给同一个子进程的stdout和stderr),不过无需手动关闭 -
新增辅助函数
@http.request,可以用于执行任意方法的单次 HTTP 请求 -
Wasm1 后端新增了
@websocket和@fs.realpath支持,现已支持除@fs.Watcher外的所有功能 -
在 Linux/macOS 上,当子进程被某个信号终止,而非正常退出时,
@process.run等 API 能识别这种情况并返回-signal -
当一个
async fn main程序被信号取消时,之前async fn main会在程序释放完资源后以128 + signal作为返回值退出(bash convention)。但这一行为对父进程来说是有歧义的。现在,async fn main在被信号取消时,会在程序完成清理后,重新模拟出当前进程被信号强制中止的状态作为程序的退出状态
-
-
moonbitlang/x-
弃用了
moonbitlang/sys,应该改用moonbitlang/core/env -
moonbitlang/x/path现在可以在浏览器环境正常使用,Windows 的路径比较现在可以正确处理非 ASCII 且有大小写形式的字符。 -
moonbitlang/x/rational现在不会出现溢出误判和分母零问题。
-