跳到主要内容

20260921 MoonBit v0.10.14

· 阅读需 12 分钟

对应 moonc 版本:v0.10.14

语言更新​

新增 MySubError::_ 模式匹配​

支持使用 T::_ 按类型匹配子错误,无需逐一列举该类型的错误构造器。并且该特性可以用于把 Error 类型 narrow 到具体的子错误类型。

suberror ParseError {
  Empty
  InvalidChar(Char)
}

fn describe(error : Error) -> String {
  match error {
    ParseError::_ as e => use_suberror(e) // type of e is ParseError
    _ => "其他错误"
  }
}

fn use_suberror(_e: ParseError) -> String {
  "解析错误"
}

fn main {
  println(describe(ParseError::Empty)) // 解析错误
  println(describe(ParseError::InvalidChar('?'))) // 解析错误
}

for... in... 支持模式匹配​

for... in... 循环现在可以直接解构迭代元素:

fn main {
  let items = [("苹果", 2), ("梨", 3)]
  for (name, count) in items {
    println("\{name}: \{count}")
  }
  // 输出:
  // 苹果 : 2
  // 梨 : 3
}

enum 新增 #non_exhaustive 标记​

有 #non_exhaustive 标记的 enum 在未来可能添加新的构造器,因此匹配一个来自外部的、 有 #non_exhaustive 标记的 enum 时,在匹配完所有已知的构造器之后,还需要用 TypeName::.. 的形式处理未来潜在的未知构造器,否则编译器会给出警告:

// @pkg
#non_exhaustive
enum E {
  A
  B
}

// 在另一个包内:

fn bad(x : @pkg.E) -> Unit {
  // 编译器会给出警告:匹配  `#non_exhaustive` 的  enum 需要处理未知构造器
  match x {
    A | B => ...
  }
}

fn good(x : @pkg.E) -> Unit {
  match x {
    A | B => ...
    E::.. => println("fallback")
  }
}

E::.. 是一个专门用来处理未知构造器的构造。因此,使用 E::.. 时,如果还有未匹配的已知构造器,编译器同样会给出警告。例如,如果 @pkg 给 E 加上了一个新的构造器 C,那么 good 函数内的模式匹配就会收到一个警告,告诉用户有未匹配的已知构造器 C 。这样一来,下游就可以通过警告收到上游新增构造器的通知。在运行时,E::.. 会匹配任何值。因此在 good 适配新的构造器 C 之前,C 会由 E::.. 的默认分支处理,而不会使程序崩溃。

如果只想匹配某个 #non_exhaustive 类型中的少数特殊构造器,可以使用 _ 。_ 即使在已知构造器未全部匹配时也可以使用,表示显式忽略剩余的所有构造器。不过,如果在已知构造器已全部匹配的情况下使用 _,编译器会给出警告提示用户改用 E::..,因为上游新增构造器后, 只有 E::.. 能收到通知

管道表达式改进​

lhs |> x => {...} 右侧的匿名函数体支持调用异步函数:

.mbtx 脚本

import {
  "moonbitlang/async@0.21.3",
}

async fn main {
  let result = 21 |> value => { @async.sleep(1); double(value) }
  println(result) // 42
}

切片 a[i:j] 改进​

切片 a[i:j] 改为自动限制边界的视图(clamped view),切片范围会限制在有效边界内。

fn main {
  let values = [10, 20, 30]
  let middle = values[1:10] // 实际范围是  [1:3] ,包含  20 、 30
  println(middle.length()) // 2
  println(middle[0]) // 20
  println(values[5:10].length()) // 0 ,得到空视图
}

这里的主要动机是 a[i:j] 确保不会在运行时 crash,包括对 String 取视图时使用位于 UTF16 surrogate 边界上的非法下标的情况。如果需要使用之前可能会 crash 版本的取视图操作,需要使用新加的 exact_view API

改进未使用包的警告。下列两种情况现在会收到警告:​

  • 显式 import 了 moonbitlang/core 中的包但没有使用

  • 没有通过 @xxx.xxx 的形式显式使用一个 import,仅通过调用方法等形式间接地使用了 import 的包的内容

这两种情况里,import 都是不必要的,但之前编译器不会给出警告。现在编译器能对这两种情况也给出警告了。例如,普通包中的以下配置和代码会使 moon check 报告 unused_package,删除未使用的 math 导入即可消除警告。

import {
  "moonbitlang/core/math", // 本包没有使用它,可删除此导入
}
fn main {
  println("Hello, MoonBit!")
}

针对 var x = 10 的错误恢复改进​

改进对 var 声明(含类型标注)的诊断,识别后给出警告,提示改用 let mut 。

fn main {
  var count = 0 // 警告:改用  let mut
  count += 1
  println(count)
}

Deprecate 在 JavaScript FFI 边界上使用 Array​

后续版本中会调整 MoonBit Array 在 JavaScript 后端的 ABI,不再保证其等价于 JavaScript 后端的 Array,所以在 FFI 边界上使用 Array 被 deprecate 了,需要使用 FixedArray 替代

默认开启 impl 自动提升为方法的警告​

之前,和类型定义在同一个包里的 impl 会自动被转换成方法,方便用户通过 . 调用。我们计划废弃这一行为,转而要求显式使用 extend Type with Trait::{f, g} 语法来将 impl 变为方法。编译器提供了一个警告,会对所有按当前语义自动提升为方法的 pub impl 发出警告,提示用户用 extend 进行迁移。具体的迁移方式是:

  • 如果希望保留对应的方法,直接添加一个 extend 声明即可。

  • 如果希望废弃对应的方法,仍需添加一个 extend 声明,但可以将该声明标记为 #deprecated。

之前,该警告默认关闭。本次更新后,该警告默认开启,所有用户都应当进行迁移。关于迁移的更多细节,详见 2026/07/13 的月报。

默认开启黑盒测试自动引入定义的警告​

在 MoonBit 中,黑盒测试(_test.mbt 文件)用于测试某个包的公开 API,因此测试自身是一个独立的包,引用被测试的 API 时应通过 @pkg.xxx 的形式显式调用。之前,为了简化测试,编译器会在黑盒测试中自动导入被测试包的全部定义,除非该定义被测试内部的本地定义覆盖。这一行为较为隐式,因此我们不再鼓励使用这一功能。编译器提供了 test_unqualified_package 警告,会在依赖上述自动导入功能的地方发出警告。之前,该警告默认关闭,本次更新后默认开启。

工具链更新​

Moon 与运行时​

  • 新增 moon search,用于查询包,比如 moon search 'html markdown' 可以用于查找和 html markdown 相关的包

  • 新增 moon view,用于查看包的版本、某位用户发布的包等信息,如 moon view --my 或 moon view moonbitlang/async --versions

  • 新增 moon deprecate,用于标记某个包为弃用;暂不支持对特定版本进行处理

  • moon tree [--json] 输出模块外部依赖,moon tree --package [--json] 输出包依赖

  • moonx 将聚焦 Wasm 后端执行,--target native 已被废弃

  • moonx 执行策略支持按命令前缀生成进程

  • moonx 执行策略可在 moonx 调用之间继承

  • moonx 支持运行 .mbtx 文件

  • .mbtx 文件实验性支持在文件头声明所需权限,并由运行时限制,如:

// policy:
//   fs:
//     read: []

///|
import {
  "moonbitlang/async@0.21.3",
  "moonbitlang/async@0.21.3/fs",
}

///|
async fn main {
  // Following operation will be rejected
  println(@fs.read_file("input.txt").text())
}
  • moon runwasm 已被废弃

  • 实验性预构建脚本支持 .mbtx,并增加脚本可接收的环境变量;原有的通过 stdin 方式的输入将会移除

  • 实验性预构建脚本限制在 native 后端运行。

编辑器支持​

  • 语言服务支持在同一工作区中同时处理多个后端。

  • moon ide 支持 .mbtx 文件,并修复脚本同级目录存在 moon.pkg 时导入别名可能被覆盖的问题。

标准库更新​

moonbitlang/core​

  • QuickCheck 增加无偏随机数生成支持,改进标量 Arbitrary 的生成范围和边界值覆盖; BigInt 生成器不再局限于 64 位,可以生成更大范围的数值及常见边界值,并补充收缩器的统一入口。

  • 新增 derive(Shrink),可自动生成反例收缩逻辑,帮助找到更小、更简单的失败用例;当前尚不支持基于子项的收缩。

  • 为 BigInt 增加使用宽位算术的 native 和 wasm1 后端实现;修复 immut/vector 中的多个溢出问题。

  • 调整 Array 的遍历与清理行为:遍历时先取得底层缓冲区; pop 、truncate 、clear 等操作先调整长度,原槽位仍可能保留元素引用。新增 fill_unused,可通过填充默认值释放这些引用。

  • 增加百分号编码辅助函数,将字符编码为 %XX 形式。

moonbitlang/x​

  • 补全并优化 crypto 。

  • 新增 jwt 包,支持 HS256 编码和解码。

  • 新增密码哈希包 bcrypt 。

  • 统一 Unicode 实现到 moonbitlang/x/unicode,旧 API 标记为弃用。

moonbitlang/async​

moonbitlang/async 目前最新版本为 0.22.1,自上次月报(0.21.0)以来的主要更新有:

  • [breaking] 取消语义调整。之前,当一个异步任务被取消时,它会收到一个特殊的 suberror 作为信号,以便被取消的代码进行清理。现在,取消信号不再是一个 suberror,而是通过特殊的编译器原语实现,相比 suberror:

    • 新的取消信号依然会像错误一样,自动向上传播

    • 取消信号可以触发 defer 与 errdefer

    • 新的取消信号不能被 catch 捕获

用户代码的迁移方式可参考 https://github.com/moonbitlang/async/releases/tag/v0.22.0

  • [breaking] @async.is_cancellation_error 现在只会返回 false 且被废弃。因为取消信号不再是一个特殊错误了。对于使用了 @async.is_cancellation_error 来特殊处理取消信号的 catch,如果取消相关的分支的作用是跳过对取消信号的处理,可以直接删除对应分支。否则,可以使用 @async.handle_cancellation 来对取消信号进行特殊处理

  • 一些 API 获得了更精确的类型。由于取消不再是一个特殊错误,在类型层面,可取消与 raise 也不再绑定了。在最新版本中,async 函数默认都是可取消的,只有显式添加了 nocancel 标记的异步函数,例如 @async.protect_from_cancel,才是不可取消的。同时,一些自身不会抛出错误但可以被取消的异步函数,例如 @async.sleep,现在可以在签名里写上 noraise 了

  • [breaking] @async.TaskGroup::add_defer 现在要求传入的回调函数必须是 nocancel 的

  • @async.with_cancellation_handler 被废弃,由一个新的 API @async.handle_cancellation 替代。@async.handle_cancellation 会运行一个异步的回调函数(运行时这个异步函数依然是可取消的),并在回调函数被取消时返回 None 。它可以用于对取消进行特殊处理。 需要注意 @async.handle_cancellation 不能撤掉当前任务被取消的状态。即使捕获一次取消信号,当前任务后续的可取消异步操作依然会被马上取消

  • @fs.remove 和 @fs.rmdir 变为不可取消(nocancel)

  • @socket.TcpServer(..) 新增了 reuse_port_lb?: Bool = false 选项,开启后该 TCP 服务器可以和其他服务器共享同一个监听地址,内核会自动在共享同一个端口的服务器间进行负载均衡。该选项仅支持 Linux,在其他操作系统上会被无视

  • 新增了 @async.platform,可以用于在运行时判断当前操作系统。由于同一份 Wasm 二进制可以在不同的操作系统上运行,对于 Wasm 后端的程序,当前操作系统只能在运行时获得, 无法在编译期判断

生态与开发工具更新​

mooncakes.io 包管理服务​

  • 搜索服务正式上线,加入包摘要索引和已有包的预索引。

  • 支持通过 moon deprecate 弃用模块,网站会展示弃用状态及原因。

  • 改进构建队列的构建速度。

SeekMoon​

  • 增加 web search 和 mbtx 工具。mbtx 工具允许智能体在沙箱中运行 mbtx 脚本。改进了 edit 工具失败时的错误报告。

  • 增加了 SSE 断流时候的重试功能,减少网络波动导致的智能体运行中止。

  • 集成了基于 MoonBit token 和 AST 的 diff 算法,支持忽略测试和注释差异。支持块级别的跳转和标记为已查看。增加 Git 提交历史。

  • 支持将 .mbti 渲染为 SVG,以及从 moon.mod 展示项目依赖图。

  • 集成 rg 文本搜索和 moongrep 搜索。moongrep 查询 pattern 可通过 AI 修复。

  • 增加右键菜单,Cmd+W 关闭标签,改进选中文本高亮,以及字体等 UI 上面的改进。

  • 改进 Windows 支持。

  • cmd/openseek 支持在 Wasm 上运行。

Rabbita 与全栈开发​

提供网页、桌面与后端的 MoonBit 全栈模板,调试和打包开箱即用,无需 Makefile 或其他语言脚本:https://github.com/moonbit-community/fullstack-moonbit。

Proton​

Moonback​

Web 后端框架 Moonback 现以 Apache 2.0 协议开源,并移动到 moonbitlang/moonback。 mooncakes.io 网站后端现在 powered by moonback。

cli 命令工具​

cli/<cmd> 在 mooncakes 发布,收录 moonbit-jq 已迁移的命令,并持续迁移常用命令、完成跨平台验证;0.1.2〜0.1.4 更新补齐常用参数,修复与上游工具不一致的行为。

moonbit-community/sqlite3​

增加实验性的异步支持,包括 native 和 wasm 后端

20260819 MoonBit v0.10.9

· 阅读需 13 分钟

对应 moonc 版本:v0.10.9

语言更新​

  • with pattern 现在要求使用 with 的分支显式加上括号,便于读者理解 with pattern 的优先级。可以使用 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_loop API,可以用于设置一个外部事件循环。moonbitlang/async 会将自己的事件循环运行在单独的线程里,并和主线程的外部循环整合。所有 MoonBit 代码依然会运行在主线程里。关于外部事件循环的实现需要为 moonbitlang/async 提供哪些 API,详见 @async.set_external_event_loop 的文档

    • 新增了 @process.pipe API,可以用于将一个子进程的输出重定向到另一个子进程的输入

    • @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 现在不会出现溢出误判和分母零问题。

20260713 MoonBit v0.10.4

· 阅读需 14 分钟

对应 moonc 版本:v0.10.4

语言更新​

  1. 新增 extend 语法,废弃 impl 隐式变为方法的行为

    原本,对于每个 impl Trait for Type 声明,如果 Type 是在当前包定义的,编译器会自动把 Trait 中的所有方法挂载到 Type 上,变为 Type 的方法,方便用户手动调用 impl 中的方法。但这一行为有诸多问题:

    • 破坏重构安全性:上游向 Trait 中添加一个新的、带默认实现的方法后,下游代码可能因为 dot syntax 的歧义而无法通过类型检查
    • 隐式且无法控制:如果不想把某些 impl 以方法的形式暴露、只想暴露 impl Trait for Type 这一关系,在原先的语义中无法做到

    因此,我们计划废弃这一自动挂载方法的行为。作为替代,我们提供了一个新的语法 extend Type with Trait::{f, g},语义是把 impl Trait for Type 中的方法 f、g 手动挂载到 Type 上。在 extend 前添加 pub 即可将 f、g 以公开方法的形式挂载。下面是 extend 语法在语义上一些需要注意的点:

    • 如果 f、g 是没有显式实现,而是采用的默认实现,挂载成方法时会把 Self 特化成 Type
    • 即使 Type 是私有的,也依然可以为其添加私有的 extend 声明,此时会将 f、g 以 local method 的形式挂载,可以在当前包内用 dot syntax 调用
    • extend 是一个新关键字,因此目前它是以软关键字的形式实现的:用户依然把 extend 当作变量名使用,但编译器会在用 extend 做变量名的地方报警告。后续 extend 将成为保留字并最终成为一个真正的关键字,届时 extend 将不再能当作变量名使用

    相比旧有语义,extend 语法可以显式控制哪些方法要挂载,因此没有旧语义的重构安全性和不可控问题。

    对于库作者,预期的迁移方式是:为每一个触发了之前的隐式挂载行为的 impl 添加对应的 extend 声明。如果不希望用户以方法的形式调用某些 impl,可以在 extend 声明上添加 #deprecated 等标记来帮助下游用户迁移。我们提供了一个新的警告 implicit_impl_as_method (79) 来帮助库的作者迁移,这个警告会在所有触发了旧的隐式挂载行为的地方提供警告信息。这个警告目前是默认关闭的,有需要可以手动开启。下个版本中,该警告将默认开启。

    除了普通的 impl,在 trait object 类型 &Trait 和类型参数上调用方法的语义也有相应的调整,以配合 impl 的语义调整:

    • 之前,可以在 &Trait 类型上用 dot syntax 调用 Trait 的 super trait 的方法。这一行为现在被废弃,未来只有 &Trait 自身的方法可以直接用方法形式调用。采用这种调用方式在本次更新中会收到警告。用户需要迁移成 Trait::f(..) 的调用形式,或者手动为 &Trait 类型添加 extend 声明
    • 对于只有一个约束的类型参数,例如 X : Hash,Hash 自身的方法依然可以直接用 dot syntax 调用,但 super trait 中的方法未来不再能用 dot syntax 调用,调用处目前会收到警告。用户应当将有警告的位置迁移成 Trait::f(..) 的调用形式
    • 对于有多个约束的类型参数,例如 X : Eq + Hash,由于不同约束之间可能有重名方法,未来将不能用 dot syntax 调用任意一个约束中的方法,调用处目前会收到警告。用户同样需要迁移至 Trait::f(..) 的调用形式
  2. or pattern 支持用 with 提供默认值

    or pattern 要求各分支绑定完全相同的变量。这导致一些处理逻辑相同的情况,需要重复编写代码或者提取一个局部函数:

    fn f(x : Int) {
      ... // 处理 x
    }
    
    match s {
      Some(x) => f(x)
      None => f(0)
    }

    现在可以用 with 为没有绑定某个变量的分支提供默认值,合并重复的处理逻辑:

    match s {
      Some(x) | None with x = 0 => ... // 处理 x
    }

    多个变量需要用括号:

    match t {
      A(a, b) | B with (a = 0, b = 0) => ...
    }
  3. 新增显式 Iter 字面量语法 [| .. |]:

    // 显式构造 Iter
    let xs : Iter[Int] = [| 1, 2, 3 |]
    // 内部可以使用 spread、list comprehension 等形式
    let ys = [| ..xs, 4, 5 |]

    Iter 字面量的求值顺序如下:

    • 对于普通字面量 [| x, y, z |],元素 x、y、z 会在构造字面量时就求值,和普通数组字面量一样
    • 对于插值 ..xs,xs 自身会在构造字面量时当场求值,但求值 xs 得到的迭代器内部的计算,会在外层迭代器 [| .. |] 遍历到对应的元素时才被计算
    • 列表插值语法 [| for .. |] 中,[| .. |] 内的所有计算都是惰性的:只有外层迭代器遍历到对应的元素时才会被计算

    之前,spread 字面量 [ x, ..xs, y ] 在预期类型为 Iter[_] 时,会基于类型进行重载自动构造 Iter。这一行为现在被废弃,使用该行为的代码会收到编译器警告,提示用户改用 [| .. |]。这一修改的动机是:我们不希望程序的求值顺序会由于类型隐式地发生改变

  4. 数组插值现在支持条件展开 ..if cond { expr }

    let xs = [1, 2, ..if cond { extra }, 3]
    // cond 为 true 时等价于 [1, 2, ..extra, 3]
    // cond 为 false 时等价于 [1, 2, 3]
  5. 新增 Bytes 字符串插值 b"...\{x}"

    Bytes 字符串插值的语义是将字符串插值的结果转换成 UTF-8 编码的 Bytes:

    let x = 42
    let b : Bytes = b"value=\{x}" // 相当于 @utf8.encode("value=\{x}")
    
    // 模板写入语法同样支持 Bytes
    buf <+ b"value=\{x}"
  6. 任意类型均可定义自定义构造器

    之前,仅有 struct 类型可以自定义构造器。现在,所有类型都可以用 fn Type::Type(..) 语法定义自定义构造器,标准库也借此统一了各容器类型的构造 API(见标准库更新)。需要注意的是,自定义构造器不能和类型已有的构造器重名。例如 struct Tuple(..) 就不能有自定义构造器,因为会和自带的 Tuple(..) 构造器冲突

  7. 空 {} 字面量将会触发歧义警告

    空的 {} 既可能是空 map 也可能是空 JSON object,现在编译器会对这种写法产生歧义警告,并给出建议的写法:

    let json : Json = { "object": {} }
                              //  ^^---- 推荐使用 `Json::empty_object()`
    let dict : Map = {}
                  // ^^--- 推荐使用 `Map([])`
    let result = { stmt1(); {} }
                         // ^^--- 推荐使用 todo 语法 `...` 或者移除这里的空 block
    let record = {}
              // ^^--- 推荐使用 `Record::{}`
  8. 实验性的 lexmatch 表达式升级为 lexscan 表达式

    fn find_eol(s : StringView) -> Int? {
      lexscan s {
        (re"\r?\n", before=line, after=_) => Some(line.length())
        _ => None
      }
    }
    
    enum Token {
      IDENT(String)
      NUMBER(String)
    }
    
    fn tokenize(s : String) -> Array[Token] {
      let tokens = []
      for curr = s {
        lexscan s with longest {
          (re"^[ \t\r\n]+", after=rest) => continue rest
          (re"^[A-Za-z][A-Za-z0-9_]*" as t, after=rest) => {
            tokens.push(IDENT(t.to_owned()))
            continue rest
          }
          (re"^[0-9]+" as t, after=rest) => {
            tokens.push(NUMBER(t.to_owned()))
            continue rest
          }
          _ => break
        }
      }
      tokens
    }

    Lexscan 表达式的 case patterns 和 regex match 表达式的右侧基本一致,但 lexscan 表达式额外支持 longest 匹配策略。

    原有的 lexmatch 表达式已被弃用,编译器会给出迁移提示。新的 lexscan 表达式不支持 Bytes/BytesView 和 guards,对于此情况需要手动改写。

    (lexscan 表达式即将支持 streaming 模式,敬请期待)

  9. 旧的 moon.pkg.json / moon.mod.json 支持即将移除

    我们计划在下个版本移除构建系统、编译器对 moon.pkg.json / moon.mod.json 的支持,推荐迁移到 moon.pkg / moon.mod。moon fmt 对旧格式的迁移仍然会保留。

  10. Warnings 配置的 @ 符号即将弃用

    我们计划简化 warnings 配置字符串的语法,移除 @ 开关。推荐在 CI 使用 moon 的 --deny-warn 代替。

工具链更新​

  1. 新 MoonBit native 后端扩展平台支持。上月发布时新后端仅支持 macOS Apple Silicon,本月新增:

    • x86-64 Linux (gnu) 支持
    • x86_64-pc-windows-msvc 支持,已进入 nightly。Windows 用户需要手动安装 MSVC Build Tools。
    • 构建策略调整为:debug 构建使用新 native 后端(编译更快),release 构建使用 C 后端并调用系统 C 编译器做 -O2 优化(运行更快)
  2. 新 MoonBit native 后端在 MacOS Apple Silicon 平台的 debug 模式下默认开启。设置环境变量 MOONBIT_NEW_NATIVE=0 时,禁用 MoonBit 新 Native 后端,debug 构建和 release 构建均使用 C 后端。其它平台,默认模式仍然为 C 后端,只在 MOONBIT_NEW_NATIVE=1 开启时,debug 构建使用新 Native 后端。

  3. Windows 开发体验改进

    • native 后端构建不再要求在 MSVC 环境中启动终端,moon 会自动查找 cl.exe / clang-cl.exe
  4. moon.pkg 新增 pkgtype 声明,配套新增 #export_name attribute

    // moon.pkg
    pkgtype(kind: "executable")      // 替代原先的 options("is-main": true)
    pkgtype(kind: "foreign_library") // 替代原先的 options(link: true)
    pkgtype(kind: "library")         // 缺省值

    #export_name 用于指定一个函数在生成代码中的名称,比如:

    #export_name("attr_add")
    pub fn add_by_attr(n : Int) -> Int {
      n + 42
    }

    在生成的 Wasm/JS/C 目标产物中会将该函数以指定的符号进行导出,#export_name 限制只能在 foreign library 的包中使用,并且只有 foreign library 中的函数会被导出,其上游依赖中的符号不会被导出。目前在 native 后端编译成静态/动态链接库的功能还在完善,所以该功能目前主要适用于 Wasm/JS 后端。

  5. source、formatter 等已稳定的配置项提升到 moon.mod / moon.pkg 顶层:

    // moon.pkg
    formatter(ignore: ["file1", "file2"])
    
    // moon.mod
    source = "src"
  6. 修复了若干 LSP 崩溃与偶发失败的问题

  7. moon 行为修复与改进

    • prebuild 与 test 的工作目录统一为模块根目录,prebuild 路径改为相对路径
    • macOS 文件监听切换到 FSEvents,修复 moon check --watch 崩溃问题
    • moon run 现在会正确传递被运行程序的退出码
    • moon bench 支持路径过滤
    • workspace 级的 preferred-target 被废弃,moon run 现在遵守模块自身的 preferred-target
  8. skills.mooncakes.io 市场

    页面链接现在可以直接被 npx skills 工具识别并安装,例如 npx skills@latest add https://mooncakes.io/skills/Milky2018/pptz@0.2.4

  9. 解析器错误恢复改进

    改进了解析器在处理语句时的错误恢复。

标准库更新​

  1. moonbitlang/async 目前最新版本为 0.20.2,自上次月报(0.19.2)以来的主要更新有:

    • 新增实验性的 Wasm1 后端支持,具体 API 的签名和行为均和 native 后端一致。使用了 moonbitlang/async 的项目构建出的 .wasm 文件目前必须使用最新的 moonrun 运行,暂不兼容其他 Wasm 运行时

      使用 moonbitlang/async 的程序构造出的 .wasm 二进制是跨平台的:同一份 .wasm 程序可以在任何有 moonrun 可用的硬件架构、操作系统上运行,无需重新构建

      目前 moonbitlang/async 的 Wasm1 支持暂处于实验性阶段,在未来我们将保证 .wasm 二进制的向后兼容性:使用旧版 moonbitlang/async 构建出的 .wasm 依然可以在最新的 moonrun 上运行

      目前 moonbitlang/async 的 Wasm1 暂不支持下列功能:

      • @websocket
      • @fs.Watcher 和 @fs.realpath

      但相关支持都会在近期添加

    • @fs.Watcher 新增了 .wait() 方法,能够返回一系列描述文件系统实际发生的变动的事件。@fs.Watcher(..) 构造器中也新增了一些控制事件汇报相关行为的选项,具体系列详见 API 文档。@fs.Watcher 提供的文件系统监视支持依然是完全跨平台的:相同的文件系统操作在所有支持的操作系统上都会得到相同的事件序列

    • 调整了 @fs.File 的读写语义,不再依赖语义操作系统维护的 file pointer,因为其语义在不同平台不一致且在部分平台有 bug。调整后的 @fs.File 的语义是:

      • 通过 @io.Reader/@io.Writer 进行的顺序读写现在是两个完全独立的流,互不干涉。基于 read_at 和 write_at 的随机读写和顺序读写完全独立,且可以在多个任务中并行进行(只要写入的区间不重叠)
      • 对于以 append 模式打开的文件(在 @fs.open 时提供 append=true),基于 @io.Reader 的顺序读取的语义不受影响,依然会从文件开头开始读取。基于 @io.Writer 的顺序写入则总是会在文件的最后追加内容。基于 read_at 的随机读取同样不受 append 模式影响,但对于 append 模式的文件,基于 write_at 的随机写入现在会立刻抛出错误。因为在 Linux 上相关系统调用有 bug,无法实现正确的语义

      这一改动是不兼容原有行为的。但大部分程序应该不会受到影响

    • 如果程序引用了 @stdio.stdin、@stdio.stdout 等标准输入输出,且 moonbitlang/async 内部初始化标准输入输出的处理时失败了(例如在 Windows 上,标准输入输出可能不存在),之前,程序会在初始化时直接崩溃。在最新版本中,程序遇到这种情况时不再会崩溃,而是会在第一次使用对应的标准输入输出管道时抛出错误。@stdio.{Input,Output}::fd 因此现在可能抛出错误了,这是一个不兼容的改动

    • @fs.open 的 sync 选项在 Windows 上也会生效了,sync=Data 和 sync=Full 都会映射到 Windows 的 FILE_FLAG_WRITE_THROUGH 选项,保证文件写入操作返回时,内容已经被同步到实际的文件系统,而不是只停留在缓存里

    • @fs.symlink 在创建的符号链接的对象是一个现有目录时,会在 Windows 上首先尝试创建 NTFS junction 而非 symbolic link,因为 Windows 上创建 symbolic link 需要管理员权限。这一行为可以通过 force_symlink=true 显式关闭(默认为 false)

    • 大量 bugfix 和一些性能优化

  2. moonbitlang/core

    • 新增实验性的 v128 SIMD package,并利用 SIMD 优化了多处热点:Bytes 查找与比较、UTF-16 解码等。此外 StringBuilder 写入、Array/FixedArray 常用操作、BigInt、strconv、JSON 解析等也做了大量性能优化
    • immut 各容器(HashMap / HashSet / SortedMap / SortedSet 等)新增与类型同名的构造器,from_array 被弃用,与语言层面的自定义构造器风格保持统一
    • argparse:解析错误时会给出子命令拼写建议,并支持默认子命令分发
    • 新增 Json::empty_object();String 新增 all / any / contains_code_unit 等便捷 API;Int16 / UInt16 新增 lnot
    • 取 view 操作符移除了负数索引支持
    • 清理弃用 API:移除了集合类型的 #alias(T)、IterResult 等
    • 修复了 @env 中的环境变量、命令行参数、当前目录等 API 在 Windows 下无法正确处理 unicode 字符串的问题

20260608 MoonBit v0.10.0

· 阅读需 8 分钟

对应 moonc 版本:v0.10.0+84519ca0a

需要说明的是,本次月报介绍的是 MoonBit 0.10 版本。它可以看作是 1.0 正式版发布前的一次关键更新,我们正在围绕语言稳定性、工具链完善和生态体验做最后阶段的打磨。按照目前规划,MoonBit 预计将在今年第三季度发布 1.0 正式版,后续进展也会通过月报持续同步给大家。

语言更新​

  1. trait 和 impl 语法现在需要添加 fn 关键字:

    trait I {
      fn f(Self) -> Unit
    //^^
    }
    
    impl I for Int with fn f(_) {}
    //                  ^^

    这一改动主要是为了方便为 trait 添加多态方法支持。现有代码只需要运行 moon fmt 即可自动完成迁移。目前旧的、没有 fn 的语法依然支持。下个版本中旧语法将开始产生警告,并会在未来被移除。

    .mbtp 文件中的 impl 现在也需要强制添加 fn 关键字。

  2. 多态 trait 方法支持

    trait 中的方法现在可以有自己的类型参数了:

    trait Logger {
      fn[X : Show] write_object(Self, X) -> Unit
    }
    
    impl Logger for StringBuilder with fn write_object(self, x) {
      self.write_string(x.to_string())
    }

    在实现一个多态的 trait 方法时,方法自身的类型参数无需显式标注。如果想要显式标注,需要将方法自身的类型参数标注在 fn 关键字后,而 impl 自身的类型参数依然标注在 impl 关键字后:

    trait Poly {
      fn[X] f(Self, X) -> Unit
    }
    
    impl[A] Poly for Array[A] with fn[X] f(self, x : X) {
    //  ^^^ impl 的类型参数           ^^^ 方法的类型参数
      ...
    }
  3. for .. in 循环支持状态变量的默认更新:

    for i in 0..<10; p1 = 1, p2 = 0; p1 = p1 + p2, p2 = p1 {
                                  // ^^^^^^^^^^^^^^^^^^^^^
                                  // 本次新增。语义和 `for` 循环的默认更新一致。
                                  // 没有显式调用带参数的 `continue` 时,
                                  // 每次循环结束后会根据这里的声明来更新循环变量的值
      println("fib#\{i + 1} = \{p1}")
    }
  4. List comprehension 支持 for .. in 的额外循环变量:

    let fibs = [
      for _ in 0..<10
          p1 = 1, p2 = 0
          p1 = p1 + p2, p2 = p1 => {
        p1
      }
    ]
  5. 使用 list comprehension 构建 Iter 以外的类型时,list comprehension 内部可以包含副作用。例如 raise 和 async。

  6. 字符串插值改进

    • 之前的字符串插值会编译成字符串拼接,现在改为编译成高效的 StringBuilder 写入。例如 let r = "a\{b}c":

      // 之前的编译结果
      let r = "a" + b.to_string() + "c"
      
      // 现在的编译结果
      let r = {
        let builder = StringBuilder(size_hint=2)
        builder.write_string("a")
        builder.write(b)
        builder.write_string("c")
        builder.to_string()
      }
    • 字符串插值 \{...} 内不再限制 {、}、" 的使用,字符串插值允许嵌套。

      let xs = ["cd", "ef"]
      let r1 = "ab\{xs.join(";")}"
      assert_eq(r1, "abcd;ef")
    • 字符串插值 \{...} 内支持嵌套匿名函数,这种情况会被特殊处理:

      let r = "a\{builder => builder.f()}"
      // 相当于
      let r = {
        let builder = StringBuilder()
        builder.write_string("a")
        (builder => builder.f())(builder)
        builder.to_string()
      }
  7. 新增模板写入语法 lhs <+ rhs

    我们发现在 web 后端开发时,经常出现用缓冲区拼凑字符串的模式:

    fn render(
      style : String,
      li_class : String,
      items : Array[String],
    ) -> String {
      let buf = StringBuilder()
      // <ul prop=foo>
      buf..write_string("<ul prop=foo>")
      //   <li class="{li_style}">{item}</li>
      for item in items {
        buf..write_string("<li class=\"")
           ..write(li_class)
           ..write_string("\">")
           ..write(item)
           .write_string("</li>")
      }
      // </ul>
      buf.write("</ul>")
      buf.to_string()
    }

    手动调用 StringBuilder::write_string 和 StringBuilder::write 非常繁琐。虽然这一场景也可以使用 HTML DSL 或者模板引擎代替,但它们会引入额外的内存分配和字符串替换开销。模板写入语法提供了一种更轻量、易读、零额外开销的解决方案,下面是使用新特性的等价代码:

    fn render(
      style : String,
      li_class : String,
      items : Array[String],
    ) -> String {
      let buf = StringBuilder()
      buf <+ "<ul prop=foo>"
      for item in items {
        buf <+ $|<li class="\{li_class}">\{item}</li>
      }
      buf <+ "</ul>"
      buf.to_string()
    }

    对于 buf <+ "a\{b}",它会直接被分解为 buf..write_string("a")..write(b)。

    操作符 <+ 右侧允许下面的表达式:

    • 字符串 "abc\{x}"

    • 多行字符串 #| multiline string...

    • 多行字符串插值 $| multiline string with \{x}

    • map 字面量 {"k1": v1, "k2": v2}

    操作符 <+ 左侧表达式的类型不要求是 StringBuilder,它可以是任意实现了下面方法的类型 T:

    • T::write_string(T, String)

    • T::write(T, X)

    • (可选)如果要支持写入 map 字面量,需要实现 T::write_object_begin(T)、T::write_object_field(T, String, X) 和 T::write_object_end(T)

    字符串插值 \{...} 的语法规则和普通字符串插值一致。

  8. 新增条件模板写入语法 lhs <? rhs

    在模板写入语法的基础上,写入可以带前置条件。这在 debug logging 等场景非常有用:我们希望程序在调试模式才输出 debug 信息,而正常模式下不会付出 logging 开销。下面的例子中,logger 类型为 Option[T],调试模式下 logger 的值是 Some(...),正常模式为 None。

    logger <? "[tag] message \{x}..."
    // 等价于
    if logger is Some(x) {
      x <+ "[tag] message \{x}..."
    }
  9. 移除旧版 struct constructor 语法

    此前已经废弃的 fn new(..) 自定义构造器写法不再被支持,新的构造器写法统一为 fn Type::Type(..)。

  10. 弃用 try? 语法

    编译器检查时会给出迁移建议。

  11. 添加实验性内置 V128 类型

    目前它在 wasm/wasm-gc 后端会编译成 v128 类型;在生成 C 代码的 native 后端中,会在支持 NEON 的 Arm 架构上编译为 uint8x16_t,在支持 SSE2 的 x86 架构上编译为 __m128i。其他情况下会使用两个 uint64 进行模拟。以上内容不作 ABI 承诺,后续会通过标准库提供高效的 V128 操作。

工具链更新​

  1. 新的 MoonBit native 后端已经发布,并可通过 MOONBIT_NEW_NATIVE=1 环境变量试用,仅支持 macOS Apple Silicon;Linux 和 Windows 的支持将会逐步推出。

  2. 新增 moon test --profile 和 moon run --profile 功能。macOS 上会调用 xctrace 对 native 程序进行 profile,Linux 平台支持也已经完成。

  3. 新增 --diagnostics-limit 来限制 error 和 warning 数量,并减少失败时直接打印底层 moonc 命令带来的噪音。

  4. Wasm 后端的 println 已替换为基于 WASI 的实现。

  5. 实验性 moon runwasm 命令和 SKILL 市场

    使用 moon runwasm Yoorkin/cowsay -- hello 可以直接运行在 mooncakes.io 发布的、支持 Wasm target 的包。mooncakes.io 会自动为支持 Wasm 编译目标的包构建经过 wasm-opt 优化的 *.wasm,并将同目录的 SKILL.md 放在 mooncakes.io/skills 页面展示:

    mooncakes.io skills page

    moon runwasm example

  6. 默认使用 moon.mod 代替原 moon.mod.json

    moon.mod 已支持 supported_targets、preferred_target、readme、license、keywords、repository、description 等顶层字段;moon fmt 默认启用从 moon.mod.json 到 moon.mod 的迁移,遇到问题时可通过 NEW_MOON_MOD=0 关闭。

  7. moon add path/to/mod 的行为做了调整:再次添加已有依赖时只给出警告,不再隐式更新版本;需要更新时使用 moon add --upgrade path/to/mod。

  8. Native LSP 已成为默认 LSP,并且新增 moon.mod 和 moon.pkg 的 LSP 支持。

  9. 新增实验性的 moon cram test 命令,用于 MoonBit CLI 应用测试。该命令会首先使用 native 后端构建项目中的 CLI 程序,然后将它们放进 PATH 变量,之后运行 cram test。基本的使用例子请见 moonbit-community/cram_test_poc。

  10. 新增实验性的 moon run --profile 和 moon test --profile 工具,目前仅支持 macOS 和 Linux 平台下的 native 后端,会分别使用 xctrace 和 perf 进行 profile,并对结果中的符号进行 demangle。

标准库更新​

  1. moonbitlang/async 目前最新版本为 0.19.2,自上次月报(0.19.0)以来的主要更新如下:

    • 添加了监听文件系统变动的功能。可以通过 @fs.Watcher(..) 来监听某个目录下的所有子文件/子文件夹中的变动。目前 @fs.Watcher 仅提供了 wait_any 方法,暂不支持汇报具体的变动内容。汇报具体变动内容的功能将在未来添加。更多细节详见 @fs.Watcher 的文档。

    • 添加了 @raw_fd.RawFdStream 类型,该类型类似 @raw_fd.RawFd,但实现了 @io.Reader/@io.Writer。可以用于更方便地操作具有流式语义的 file descriptor。

    • 添加了 @async.Mutex 类型,可以用于在多个并行任务间进行同步。

    • 加入可选的 fd 泄漏检查,可通过 MOONBIT_ASYNC_CHECK_FD_LEAK=1 开启。开启后,如果程序忘记释放一些由 moonbitlang/async 管理的资源(例如文件),程序退出前会 abort 并提示有泄露问题。

20260513 MoonBit v0.9.2

· 阅读需 12 分钟

发布于: 2026年5月13日

对应 moonc 版本:v0.9.2+bbe2b338f

语言更新​

  1. 新增 list comprehension 语法:

    // 可以用 `if` 来对元素进行筛选
    let even_numbers = [
      for i in 0..<100 if i % 2 == 0 => i
    ]
    
    // 如果创建的是 `Iter`,可以创建无穷序列
    let fib_numbers : Iter[Int] = [
      for p1 = 1, p2 = 0;; p1 = p1 + p2, p2 = p1 => p1
    ]

    语法是 [ for .. => <body> ],在 => 前可以添加可选的 if <guard> 来对元素进行筛选。guard 内可以用 is 绑定变量,并且绑定的变量可以在 body 中使用。所有 for 循环,包括 for .. in 循环和普通 for 循环,都能够在 list comprehension 中使用。

    List comprehension 可以构造所有内建的类数组类型,包括 Array/FixedArray/ReadOnlyArray/Bytes/String 以及它们对应的 view 类型。除此之外,list comprehension 还可以用于构造 Iter 类型,此时整个序列是惰性求值的,只有当 Iter 遍历到对应的元素时,才会计算对应的元素,因此可以创建无穷的序列。一个 list comprehension 表达式具体构造何种类型的值是由上下文的类型决定的,可以通过类型标注来显式控制要构造的类型

    List comprehension 的 body 内部目前不支持任何控制流操作,包括 return/break/continue、抛出错误和异步操作

  2. 支持用数组字面量构造 Iter 类型。现在,当一个数组字面量的预期类型可以是 Iter,此时它会变成一个包含对应元素的迭代器。字面量中的元素依然会在构造字面量时立刻求值,而不是被包裹在 Iter 中进行惰性求值。因此数组字面量的求值顺序不会随着类型隐式改变

  3. 调整数组插值构造 Iter 时的求值顺序。数组插值语法 [ a, ..it, b ] 之前就支持构造 Iter 类型。但之前的语义里,求值顺序并不自然。具体来说,it 对应的迭代器内部的副作用会在构造字面量时就计算完毕,而不是惰性求值。这导致数组插值语法不能用于组合可能无穷的迭代器。

    现在,以 ([a, ..it, b] : Iter[_]) 为例,数组插值构造迭代器的求值顺序为:

    • 表达式 a、it 和 b 会在构造迭代器时马上按从左到右的顺序求值

    • 迭代器 it 内部的副作用,会在遍历到对应的元素时才触发

  4. struct 的自定义构造器得到了调整和简化。新的语法如下:

    struct Point {
      x : Int
      y : Int
      // 结构体内不再需要写任何东西
    }
    
    fn Point::Point(x : Int, y : Int) -> Point {
      { x, y }
    }
    
    test {
     let _ = Point(1, 2)
    }

    相比旧语法,新的语法不再需要把构造器的签名重复两遍,并且不再需要定义一个 new 方法来实现构造器,语法和语义上都更简单。对于库作者,如果想在提供构造器的同时保留 new,可以在 fn Type::Type 声明上标注 #alias(new),也可以用 #alias(new, deprecated)/#alias(new, deprecated="msg")把 new 方法废弃,供下游迁移。

    旧的自定义构造器语法目前仍然保留,但编译器会对旧语法给出废弃警告。旧语法会在近期被移除

  5. 新增 extensible enum

    extensible enum 主要用于允许其他包给当前包中的 enum 拓展新的构造器,比如这里在 base 这个包中使用 extenum 关键字声明 LogEvent 类型,表示其可以被拓展:

    // base package
    pub(all) extenum LogEvent[T] {
      Info(T)
      Warning(T)
    }

    在 plugin 包里可以使用 extenum 和 += 关键字对 base 包中的 LogEvent 类型进行拓展,比如这里添加了 Debug 这个新的构造器

    // plugin package — extends a type from another package!
    pub(all) extenum @base.LogEvent[T] += {
      Debug(T)
    }

    在 app 这个包中对 @base.LogEvent 这个类型进行模式匹配,这里可以同时匹配来自 base 包的构造器,也可以匹配来自 plugin 包中的构造器,在匹配构造器的时候需要使用 @pkg.C 的语法来区分来自不同包的构造器,并且因为 LogEvent 类型是可拓展的,所以必须使用 wildcard 来确保其完备性

    
    // app package — both variants share one type
    fn[T : Show] use_event(event : @base.LogEvent[T]) -> String {
      match event {
        @base.Info(msg)    => "info: \{msg}"
        @base.Warning(msg) => "warn: \{msg}"
        @plugin.Debug(msg) => "debug: \{msg}"
        _                  => "unknown"
      }
    }
  6. 新增 E::@pkg.C 的语法

    这一语法主要用于支持上面提到的 extensible enum,因为普通的 enum 可以确保类型和构造器来自同一个包,所以上述语法和 @pkg.E::C 等价,但是在考虑到 extensible enum 的情况下,类型和构造器可能来自不同的包,所以显式写明构造器来自哪个包就有其必要性了。

    支持了这一语法之后构造器的语法可以概括为以下四种情况:

    • C —— 当前包声明的构造器

    • @pkg.C —— pkg 包中声明的构造器

    • @pkg.E::C —— E 是普通 enum 的话则 E 和 C 都来自 @pkg,如果是 extensible enum 的话则 E 来自 pkg 而 C 来自当前包

    • @pkg1.E::@pkg2.C —— E 和 C 分别来自 @pkg1 和 @pkg2

  7. 反向管道语法 <| 支持了方法调用

    obj.method(args) <| last_arg
    obj.method() <| last_arg
    obj.method(args) <| (x) => {
      x.do_something()
    } 

    注意只有 final_arg 一个参数时,<| 左侧依然需要写成 obj.method() 的形式,而不能直接写 obj.method <| final_arg。

  8. for 循环的更新部分现在可以使用条件部分引入的变量了。例如:

    for sum = 0; queue.next() is Some(elem); sum = sum + elem {
    //                                       ^^^^^^ 这里可以使用 elem 了
    } nobreak {
      sum
    }
  9. 下列已经废弃一段时间的旧语法被移除:

    • 旧的 newtype 语法 type T UnderlyingType,新语法是 struct T(UnderlyingType)

    • 连续多个局部 fn 不再能互相递归。互递归的本地函数需要使用 letrec f = .. and g = ..

工具链更新​

  1. Workspace 中会采用 preferred target

    对于对工作区全局生效的指令,如 moon build moon check moon test,对于每一个工作区的成员,将使用该成员声明的 preferred-target对该成员进行操作。这样一来,混合项目,如前后端一体项目,可以通过一行指令完成检查、构建、测试等。

  2. 新增 moon run -c 的功能

    moon run -c <script>允许直接执行临时脚本,例如:

    $ moon run -c 'fn main { println("hello") }'
    hello
  3. moon run 改为从选定路径开始解析项目

    通常,我们会使用一个本地的项目作为工具。这种时候,源代码路径和工作路径不同。以往,moon run 必须在项目内执行,或需要额外提供 --manifest-path。现在这一限制已解除,来简化使用。

  4. 废弃--manifest-path

    如上所述,--manifest-path的使用场合是源代码路径和工作路径不同,通常只出现在 moon run的情况,而目前moon run的限制已经解除,因此此参数不再有必要。

  5. moon.mod 实验性配置

    moon.mod 是实验性的模块配置文件,用于替代moon.mod.json, 提供和 moon.pkg 一致的编写体验。目前构建系统已经支持了 moon.mod, LSP 还在适配中。

    // 模块名
    name = "moonbit-community/mod"
    // 版本
    version = "0.1.0"
    // 模块的依赖
    import {
      "moonbitlang/async@0.19.0",
      "moonbitlang/x@0.4.43",
    }
    
    // 原有的其他配置
    options(
      readme: "README.mbt.md",
      repository: "",
      license: "Apache-2.0",
      keywords: [ "keyword1", "keyword2" ],
      ...
      description: "",
    )

    新的 moon.mod 弃用了本地依赖配置,推荐使用moon.work替代。

    使用旧配置的项目不受影响,而且目前默认不会进行迁移。可以通过设置环境变量NEW_MOON_MOD=1来让 moon 自动迁移旧的 moon.mod.json 配置到新的 moon.mod。

  6. 构建系统 rule/dev_build 配置

    构建系统弃用了之前moon.pkg的options("pre-build": ...)配置,改进后的配置如下:

    // 定义 rule1 
    rule(name: "rule1", command: "exe $input -o $output")
    // 使用 rule1,并设置要用的输入和输出
    dev_build(rule: "rule1", input: "input.txt", output: "output.mbt")

    在 moon.pkg 中定义的rule只有在同一个配置中可见。rule 也可以添加到新的 moon.mod 中,此时整个项目的 moon.pkg 都可以使用这条 rule,减少配置上的重复。

  7. 新增 native lsp。这是我们使用ocaml重新实现的moonbit lsp,它直接编译到二进制,计划在未来取代现在的使用ts实现的lsp。通过moon lsp使用。vscode 插件用户可以通过设置 "moonbit.nativeLsp": true使用。欢迎大家试用并给我们提供反馈。

  8. 新增MOON_WORK环境变量指定moon.work文件位置,或通过MOON_WORK=off关闭 workspace 行为

标准库更新​

  1. Show相关实现的弃用和迁移

    我们正在将大多数容器类型的调试接口从 Show 迁移到 Debug。Debug 旨在提供更好的调试体验:它会为数据结构生成结构化、带缩进、便于人类阅读的信息。Show trait 将专注于生成专门格式的字符串,例如让 Json 类型直接输出 Json 格式的文本。区分Show和Debug也能够避免在字符串插值中错误地使用未处理的数据。

    这次更新:

    • 弃用 标准库 容器相关类型的 Show 实现,包括元组、Array、Map、Set、Option、Result

    • 变更了 Show::output 关于 String 和 Char 两个类型的行为

      Show::output原先在处理String和Char时和Show::to_string不同:它会处理字符串和字符的内容,输出带引号和转义序列的文本。变更后,Show::output和Show::to_string保持一致,String和Char都按原样输出成文本。例如:

      //旧的行为
      assert_eq(Show::output("\n"), "\"\\n\"") 
      assert_eq(Show::output('\n'), "'\\n'")
      assert_eq(Show::to_string("\n"), "\n") 
      assert_eq(Show::to_string('\n'), "\n")
      //现在的行为
      assert_eq(Show::output("\n"), "\n") 
      assert_eq(Show::output('\n'), "\n")
      assert_eq(Show::to_string("\n"), "\n") 
      assert_eq(Show::to_string('\n'), "\n")

    迁移时,请将 Debug 用于测试快照、测试断言、以及类似日志的输出。

    一些常见情况可以按如下方式迁移:

    • 自定义类型使用 derive(Debug)生成实现。只有在类型存在有意义的特定文本表示时,才手动实现 Show,例如 Json 类型、Html类型、SqlStatement类型。

    • 使用 debug_inspect(value, content=...) 替代 inspect(value, content=...)

    • 使用 @debug.assert_eq(a, b) 替代 assert_eq(a, b)

    • 在插值时, "\{x}"将插入通过Show的到的结果;"\{to_repr(x)}"将插入通过Debug得到的结果。在大部分调试场景你需要使用to_repr。

    • 使用 @debug.to_string(value) 替代 value.to_string(),前提是该字符串仅用于调试。

  2. moonbitlang/async 目前的最新版本为 v0.19.0,自上次月报(v0.17.0)以来的主要新增功能和 API 变动如下:

    • 新增了 moonbitlang/async/gzip 包,可以对任何 reader/writer 进行 gzip 解压缩/压缩变换

    • moonbitlang/async/tls 包新增了下列功能:

      • get_peer_certificate 方法可以用户在 client 处获取 DER 格式的 server 证书

      • unique_channel_binding 和 server_endpoint_channel_binding 方法可以用于获取 RFC 5929 定义的两种 TLS channel binding 数据

      • @tls.Tls::client 的 verify 参数被废弃,替代品是 trust? : TrustedRoot 参数。TrustedRoot::SystemRoot 和 TrustedRoot::NoVerification 对应原本的 verify=true 和 verify=false,TrustedRoot::CustomPemFile(filename) 是新增功能,可以用一个自定义的 PEM 证书文件作为受信根证书,从而利用私有的自签名证书进行 TLS 验证

      • @http.Client(..) 现在也能接受 trust 参数,会在进行 https 通信时将该参数转发给 @tls.Tls::client(..)

    • 新增了 moonbitlang/async/raw_fd 包,可以将任意 file descriptor 整合进 moonbitlang/async 的事件循环,方便用户操作 moonbitlang/async 不直接支持的特殊 file descriptor

    • 新增了 signal 支持。现在,async fn main 程序在接受到 ctrl+C 等信号时,会自动取消整个程序(通过 moonbitlang/async 自带的取消机制),便于实现进程级别的清理。默认下列信号会触发全局取消:

      • Linux/MacOS:SIGINT、SIGTERM、SIGHUP

      • Windows:CTRL_C_EVENT、CTRL_BREAK_EVENT、CTRL_CLOSE_EVENT

      可以通过 moonbitlang/async/signal 包的 set_global_cancellation_signals 函数控制哪些信号会触发全局取消行为

    • 新增了 UDP multicast 支持。详细内容见 https://github.com/moonbitlang/async/pull/354

    • @socket.Addr::parse 现在能正确处理 IPv6 zone suffix 了

    • moonbitlang/async/http 修改了处理 cookie 的方式。Set-Cookie header 的接受和发送现在需要通过 @http.Response.cookies 字段完成,而不是直接通过 headers。这是因为 Set-Cookie header 可能在同一个 response 中出现多次,并且不遵循 RFC 的一些规则,无法被放入一个 Map。moonbitlang/async/http 会对 Set-Cookie 的内容做基本的解析,方便用户读取

    • 当用户没有显式提供 Accept-Encoding 时,@http.Client 现在会自动向服务器请求 gzip 压缩,并在用户读取 response body 时自动进行解压缩。如果用户显式提供了 Accept-Encoding,则目标服务器发送的 response body 会被原样读取出来

    • 在发送 HTTP request/response 时现在可以显式提供 Content-Length。此时用户依然可以增量地分批发送数据, moonbitlang/async/http 会自动检查用户最终发送的总数据长度是否正确,不正确则报错。这一功能可以帮助文件下载服务器等应用给客户端提供下载进度支持

    • @fs.open 等 API 现在通过 create_mode? : CreateMode 来控制是否要创建新文件和是否要 truncate 现有文件,通过 permission? : Int来控制新建的文件的访问权限(默认 0o644)。旧的 create 和 truncate 参数被废弃。@fs.mkdir 的权限参数变为可选,默认是 0o755

    • 新增了 @fs.rename,可以用于异步地执行文件重命名操作

    • 新增了 @fs.Directory::next,它会返回一个 @fs.DirectoryEntry 结构体,其中包含文件名和一些额外信息,例如文件是否是一个子目录

    • @fs.File::as_dir 不再被废弃,后续可以正常使用

20260407 MoonBit v0.9

· 阅读需 6 分钟

发布于: 2026年4月7日

对应 moonc 版本:v0.9.0+9f1423bcb

语言更新​

  1. 废弃 derive(Show)

    调试数据结构的接口已经迁移到 Debug 特征。Show 特征将专注于提供 Json::stringify 等自定义输出格式的接口,不再只对 String 和 Char 做特殊处理。手动实现 Show 特征仍然受支持。

  2. 增加形式化验证支持

    例如,我们可以通过 proof_ensure 写明函数运行结果应该满足的条件:

    // Int is assumed to be unbounded integers
    // We will provide a config option to change it to 32-bit machine integers
    // in later releases
    pub fn abs(x : Int) -> Int where {
      proof_ensure: result => result >= 0,
      proof_ensure: result => (result == x) || (result == -x)
    } {
      if x >= 0 {
        x
      } else {
        -x
      }
    }

    并且在 moon.pkg 文件中启用证明功能:

    options(
      "proof-enabled": true
    )

    再执行 moon prove 即可看到证明结果。更多关于验证的支持可以参考另一篇文章。

  3. 废弃 loop 语法

    为了简化语言,loop 语法已被废弃。对于已经使用 loop 的代码,可以使用 for + match 替代:

    loop value {
      Pattern1 => continue value1
      Pattern2 => ...
    }
    for x = value {
      match x {
        Pattern1 => continue value1
        Pattern2 => { ...; break }
      }
    }
  4. 新增稳定的 regex 支持,以及新的 regex match 表达式 s =~ re"..."

    新的 regex match 表达式和之前实验性的 lexmatch? 有一些相似之处,但语义上并不完全相同。具体使用方式请参考 regex literal expression 和 regex match expression。

    我们计划之后废弃实验性的 lexmatch 和 lexmatch? 表达式。

    const PREFIX = re"a"
    const ALT = re"b"
    const SUFFIX = re"bc"
    
    fn main {
      let s = "==abc=="
      let _ = s =~ re"abc"
      let _ = s =~ (PREFIX + SUFFIX, )
      let _ = s =~ (((re"x" as x) | ALT) + SUFFIX, before=y, after~)
    }
  5. JavaScript 后端下,Int64 和 UInt64 现在会被编译成 BigInt

    UInt64 和对应的 JavaScript BigInt 值保持一致。对于 Int64,需要使用 BigInt.asIntN(64, x) 来获取对应的有符号 64 位值。

    改用 BigInt 的主要原因有两个:

    • 由于 JavaScript 引擎的持续优化,基于 BigInt 的 Int64 性能已经明显超过此前基于两个 Int 字段模拟的实现。
    • 这也和现代 JavaScript 中处理 64 位整数的自然方式对齐。
  6. 新增反向 pipeline 语法 div(id="...") <| []

    反向 pipeline 为 UI 库提供了新的 DSL 写法,并通过减少嵌套括号提升了可读性:

    fn view() -> Html {
      div([
        text("hello"),
        ul([
          li("item 1"),
          li("item 2"),
        ]),
        h1(id="...", [
          a(href="..."),
        ]),
      ])
    }
    fn view() -> Html {
      div <| [
        text("hello"),
        ul <| [
          li("item 1"),
          li("item 2"),
        ],
        h1(id="...") <| [
          a(href="..."),
        ],
      ]
    }

    反向 pipeline 的右侧也支持 lambda,因此可以更自然地编写类似 Compose UI / SwiftUI 风格的界面 DSL:

    list(album.songs) <| song => {
      hstack <| () => {
        image(album.cover)
        vstack(alignment=Leading) <| () => {
          text(song.title)
          text(song.artist.name)
            .foreground_style(Secondary)
        }
      }
    }
  7. 统一返回值为 Unit 的 FFI 函数 ABI

    现在,在 native / wasm / wasm-gc 后端,返回值为 Unit 的 FFI 函数在 ABI 层面统一变为“不返回值”。例如在 native 后端,一个返回 Unit 的 MoonBit extern "C" 声明现在会对应 C 中一个返回 void 的函数。

  8. 增加 impl 的 #deprecated 支持

    目前,#deprecated 只能标在没有 with 的 impl Trait for Type 声明上。所有使用了 #deprecated 的 impl 的地方,包括调用多态函数、直接调用该 impl 中的方法,都会产生警告:

    struct T(Int)
    
    #deprecated
    impl Eq for T
    
    impl Eq for T with equal(x, y) {
      x.0 == y.0
    }
    
    test {
      let _ = T(1) == T(2) // deprecated warning
    }
  9. using 和 #alias 现在会自动创建构造器别名

    对于 tuple struct、带构造器的 struct 以及只有一个构造器的 error,现在都可以自动创建构造器别名,使得对应类型可以通过别名构造或匹配:

    using @ref {type Ref}
    
    test {
      let _ = Ref(42) // 现在可以这样写,不再需要写成 `@ref.Ref(42)`
    }
    
    #alias(Alias)
    struct MyType(Int)
    
    test {
      let _ = Alias(42) // 现在可以这样写,不再需要写成 `MyType(42)`
    }

    这一改动使得上述三种构造可以更自然地通过 using 重新导出,或通过 #alias 进行改名。

工具链更新​

  1. 增加工作区支持

    包括用于创建和管理工作区的 moon work init 与 moon work use。moon work sync 用于同步工作区内包的版本。

    check、test、format 等命令现在都支持工作区。publish 仍只支持模块级发布,需要使用 moon -C module publish 对单模块进行发布。

  2. 废弃 moon doc 中的符号查找功能

    这一能力已经统一收敛到 moon ide doc。

  3. 下载脚本增加 Windows aarch64 支持

  4. 下线 try.moonbitlang.cn 和 oj.moonbitlang.com

  5. 调整 pre-1.0 版本的解析策略

    在 1.0 正式版发布前,pre-1.0 版本现在会被视为兼容版本。也就是说,依赖图中的 moonbitlang/async@0.15.0 和 moonbitlang/async@0.16.0 现在会解析为 moonbitlang/async@0.16.0。

  6. 改进 mooncakes.io 的界面和文档浏览体验

标准库更新​

  1. 新增 immut/vector

    immut/vector 替换了原有的 immut/array,同时性能更优。

  2. 调整 SourceLoc 的 Show 输出格式

    文件名从相对于包变为相对于模块。这一改动使得项目的 source 设置能够体现在 SourceLoc 中。如果测试中输出了 SourceLoc,可能需要通过 moon test -u 更新预期输出。

  3. moonbitlang/async 更新(最新版本 v0.17.0)

    • 新增 @async.Timer 类型,可以用于实现空闲超时等抽象。
    • 许多类型如 @async.Queue 提供了 struct 构造器 API,可以通过 @async.Queue(..) 的方式创建。
    • 为了适应上游的类型变动,@async.spawn_loop 的签名有一些 breaking changes。
    • 允许直接获取 socket 的 fd,便于高级用户自行绑定一些平台相关的 socket API。

20260310 MoonBit v0.8.3

· 阅读需 7 分钟

发布于: 2026年3月10日

语言更新​

  1. 使用#alias和#as_free_fn标记的函数,将不再继承不应该继承的属性,如#deprecated属性。现在,#alias 声明的别名和函数本体的各种属性都可以独立地控制:

    // 本体和别名都不 deprecate
    #alias(g1)
    fn f1() -> Unit
    
    // 只 deprecate 别名
    #alias(g2, deprecated)
    fn f2() -> Unit
    
    // 只 deprecate 本体
    #alias(g3)
    #deprecated
    fn f3() -> Unit
    
    // 本体和别名都 deprecate
    #alias(g4, deprecated)
    #deprecated
    fn f4() -> Unit
  2. const声明支持了字符串拼接和字符串插值:

    const Hello : String = "Hello"
    const HelloWorld : String = Hello + " world"
    const Message : String =
      $|========
      $|\{HelloWorld}
      $|========
  3. 让for .. in循环支持了额外的循环变量:

    // 对数组 xs 求和
    for x in xs; sum = 0 {
      continue sum + x
    } nobreak {
      sum
    }

    利用这一新特性,for .. in 循环可以用函数式的方式维护额外的状态,无需使用 let mut

  4. 废弃无方法类型被所有类型隐式实现的行为。 之前,一个没有任何方法的 trait 会被所有类型隐式实现,无需显式写 impl Trait for Type。这一行为已被废弃,使用这种隐式的实现时会收到警告。未来,这一行为将被彻底移除,届时所有 trait 都统一需要显式实现

  5. 废弃for { ... }无限循环的用法。之前,可以使用 for { ... } 来写一个没有终止条件的无限循环。这一语法已被废弃,需要将循环写成 for ;; { ... } 或 while true { ... }。这一改动可以使用 moon fmt 自动完成迁移。这一改动的动机是:我们未来可能会为 for .. in 循环添加模式匹配的支持,如 for (x, y) in array_of_tuple。而 for { .. } 语法和模式匹配 struct 或 Map 有语法冲突

  6. 无更新部分的for循环允许省略分号。for i = 0; i < 10; { ... }(没有更新部分)的 for 循环,现在可以省略循环条件后的分号,写成 for i = 0; i < 10 { ... }

  7. 正式移除impl总是可以通过.调用的行为。 在 MoonBit 中,只有当 impl 和类型定义在同一个包内时,impl 才可以通过 x.f(..) 语法调用。但在 MoonBit beta 版本前,当前包内的 impl 总是可以通过 . 调用。这一行为已在 beta 版本以警告的形式废弃,现在,这一行为被正式移除

  8. FFI 参数未标注生命周期管理方式默认状态下变成了一个错误而非警告。未来,我们将正式把FFI 参数默认的生命周期管理方式从 #owned 改为 #borrow,当前未标注生命周期管理方式的FFI函数,编译器将会报一个错误。

  9. 修复了for i in x..<y循环的nobreak块中依然可以引用循环变量i的问题。一些意外依赖了这一行为的代码可能会编译失败。

  10. 改进了一些顶层函数签名不匹配的错误信息,在错误信息中只输出签名不一样的部分,方便定位错误。例如:

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

    原本的报错信息是:

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

    改进后的报错信息是:

    ...
      expected: (.., flag2~ : _, ..) -> Unit
      actual:   (.., flga2~ : _, ..) -> Unit
  11. 新增了#unsafe_skip_stub_check属性,它可以用于跳过 FFI 签名中对类型是否具有稳定 ABI 的检查。该属性可以用于高级用户在 wasm 后端进行较复杂的 FFI 实验。需要注意,跳过检查后 FFI 的行为是未定义的且随时可能发生变动,因此该属性只应用于实验

工具链更新​

  1. moon ide新增analyze命令用于分析一个包的公开API的使用情况。它会以mbti的格式将一个包的公开API打印出来,并在每个API的后面用注释写明它的使用情况,包括总使用次数,在测试中的使用次数和该API是否在exports.mbt中被定义。下面是一个输出的例子:

    $ 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两种参数传递方式:

    • moon ide analyze 分析当前模块中所有的包

    • moon ide analyze path/to/pkg1 path/to/pkg2 ... 分析所有传入的包,可以和shell中的glob pattern配合使用,例如 moon ide analyze internal/* 可以用来分析所有internal的包。

    这个命令可以配合 AI 重构,快速删除模块内未使用的公开 API。不过,由于非 internal 包的公开 API 可能被模块外用户使用,这种重构原则上只对 internal 包安全。为了区分非 internal 包中“对内使用”和“对外使用”的 API,我们引入了一项约定:凡是预期供模块外用户使用的公开 API,都应定义在 exports.mbt 中。对于这类 API,即使模块内没有任何使用,也不应删除。moon ide analyze 会在输出中对 exports.mbt 中定义的 API 进行特别标注,例如 build_report 和 never_called_pub。

  2. supported-targets支持完善

    • 启用新语法,可通过"+js+wasm+wasm-gc"显式声明支持哪些后端,或用"+all-js"表示不支持哪些后端

    • 在moon.mod.json与moon.pkg中均可定义,对一个包来说,支持后端为两者交集

    • 当无法构造依赖图时,有更好的错误信息

  3. 构建系统现在会追踪编译器,以减少由于编译器版本更新、缓存不匹配导致的 segfault 行为

  4. mbtx脚本模式支持从stdin输入

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

标准库更新​

  1. 增加 argparse 库,提供基础的命令行参数解析功能

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

    • JavaScript 后端添加了基于 fetch API 的 HTTP client 支持。moonbitlang/async/http 中的所有 HTTP client API(除 HTTP proxy 支持)均在 JavaScript 后端可用(包括浏览器环境)

    • moonbitlang/async/js_async 添加了与 WebAPI ReadableStream 交互的支持

MoonBit 0.8.0发布

· 阅读需 15 分钟

我们很高兴正式发布 MoonBit 0.8.0。 MoonBit是一门AI原生的编程语言,它的主要特点是高可靠,易读和高性能。0.8 版本是MoonBit 迈向稳定、可用于生产环境的重要里程碑版本。

这次发布并非一系列零散改动的简单集合。MoonBit 0.8 标志着项目从实验性语言,明确迈入工程级语言与工具链阶段:在调试能力、错误处理、包管理以及开发者工具等方面都有了显著提升,尤其更适合支撑大规模代码库和以 Agent 为核心的开发工作流。

为什么 MoonBit 0.8 很重要?​

正如许多开发者所观察到的,Rust 通过其严格的语义和可验证性,为 AI 辅助开发提供了坚实的基础。MoonBit 在继承类似可靠性目标的同时,更加注重 显著更快的编译速度(在实际使用中通常比rust快一个到两个数量级),以及 面向 Agent 工作流深度集成的开发工具体系。

随着 0.8 版本的发布,这些设计目标已不再停留在抽象理念层面,而是 在语言、编译器、运行时以及 IDE 等各个层面得到一致体现。

0.8版本重点更新:​

WasmGC/LLVM/Native 后端 Backtrace 支持

MoonBit 的 wasmGC/native/LLVM 后端现支持在程序崩溃时,自动打印崩溃处的调用栈。并且能直接输出对应的 MoonBit 源码的位置,极大改善了调试体验(以下是Native后端的调用栈示例):

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 原生的面向 specification 支持

MoonBit 新增了 declare 关键字,可以用于声明需要实现的类型、函数、方法等。如果 declare 的声明没有对应的实现,MoonBit 编译器会报一个警告。declare 关键字提供了面向 AI 的原生 specification 支持:可以用 declare 的形式指定需要 AI 实现的接口,并根据接口提前编写测试。只需要把 declare 和测试所在的文件标记为只读,就能防止 AI “作弊”。随后,MoonBit 编译器的警告信息能辅助 AI 正确地实现所有必要的接口。由于没有实现 declare 只是一个警告,AI 可以渐进式地编写、测试代码。

社区动向

MoonBit 0.8 完整技术更新一览​

语言更新​

  1. suberror Err PayloadType 语法被废弃,用户需要将这种定义修改成类似 enum 的形式:

    suberror Err {
      Err(PayloadType)
    }

    这一改动的动机是 suberror Err PayloadType 语法容易产生 Err 和 PayloadType 有相同 ABI 的误解,但实际上 error type 都有自己特殊的 ABI。这一改动可以通过 moon fmt 自动完成迁移。

  2. 废弃了推导内建 error 构造器(目前主要是 Failure)的行为。

    类型未知时,需要将 raise Failure(..) 替换成 raise Failure::Failure(..),catch 时同理。

  3. 支持了在 MoonBit 中直接调用 FuncRef[_] 类型的值。 这一功能可以用于在 native 后端实现动态加载函数或 JIT。

  4. WasmGC/LLVM/Native 后端 Backtrace 支持:现在,使用wasm-gc,native后端或者llvm后端时,如果触发panic,例如数组下标越界,对为None的Option[T]进行unwrap,try!一个会抛出错误的函数,或者手动调用panic函数时,在debug模式下会打印出调用栈,例如下方的函数:

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

    以native后端为例,使用moon run main --target native,将会看到下面的调用栈:

      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

    注:目前Windows系统上native和LLVM后端暂不支持此项功能。

  5. 新增了 declare 关键字,用于替代原本的 #declaration_only 属性。declare 新增了 trait 实现的支持。比如:

    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

    declare impl 和直接写 impl 的主要区别在于 declare impl 在缺少 implementation 的情况下只会报警告,不影响代码执行,所以可以跑其他功能的测试。

  6. 新增了反向的 range 表达式 x>..y 和 x>=..y,用于在 for .. in 循环中进行反向的迭代:

    ///|
    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]")
    }

    为了让语法更一致,正向的两侧闭合的 range 表达式的语法从 x..=y 迁移至 x..<=y。这一改动可以通过 moon fmt 自动迁移。

  7. 禁用了在外部使用 { ..old_struct, field: .. } 语法更新一个带有 priv 字段的结构体的行为。

  8. lexmatch 表达式 first match 下新增 guard 支持。 包含 guard 的 lexmatch 性能会有损失,因此推荐在快速开发过程中使用,之后再考虑是否改写。其语法和 match 表达式中的 guard 一致,可查看 https://github.com/moonbitlang/lexmatch_spec 了解更多:

    lexmatch input {
      ("#!" "[^\n]+") if allow_shebang => ...
      ...
    }
  9. struct 新增了自定义构造器的支持,语法如下:

    struct S {
      x : Int
      y : Int
    
      // 为 `struct` 声明一个构造器
      fn new(x~ : Int, y? : Int) -> S
    }
    
    // 实现 `struct` 的构造器
    fn S::new(x~ : Int, y? : Int = x) -> S {
      { x, y }
    }
    
    // 使用 `struct` 的构造器
    test {
      let s = S(x=1)
    }

语义上:

  • struct 中声明 fn new 即可给这个 struct 定义自动构造器。除了必须返回 struct 自身之外,自定义构造器的签名没有其他限制。可以使用 optional argument、抛出错误等。struct 中的 fn new(..) 的参数不能写默认值,但可以省略参数名字
  • 对于有类型参数的 struct,fn new 可以特化类型参数,也可以给类型参数添加 trait 约束。语法和普通的顶层函数声明一样
  • 如果在 struct 中声明了 fn new,则必须定义一个方法 fn S::new 来实现这个构造器。S::new 的签名必须和 struct 中的 fn new 完全相同
  • 使用 struct 构造器的方式和使用一个 enum 构造器完全一样。比如,在类型已知的时候,可以直接写 S(..),无需写成 @pkg.S(..) 或者 @pkg.S::S(..)。不过,struct 的构造器不能用于模式匹配
  • struct 构造器的可见性和 struct 字段相同。也就是说,pub struct 和 pub(all) struct的构造器可以在当前包外调用,struct 和 priv struct 的构造器则是私有的。
  1. using 声明上现在可以添加 #deprecated 标注来废弃 using 创建的别名。

  2. 增加了 Debug 特征和自动 derive 相关支持。 Debug 特征是Show 特征的改进版本,用于提供更结构化和可读的打印信息。

    ///|
    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) 支持额外的 ignore 参数,它接受一个或者多个类型构造器名。在实现类型本身的打印逻辑时,它会过滤语法上相同的类型构造器,相关部分将会打印成...。这在内部类型来自第三方包,并且没有提供 Debug 特征的实现时非常有用。

    ///|
    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: ...,
          #|}
        ),
      )
    }

    @moonbitlang/core/debug包还提供了专门的assert_eq(a,b),在断言失败时,找出 a 和 b 的差异并打印在命令行中。 在未来我们将逐步迁移到Debug并弃用derive(Show),Show 特征则专注于手动实现特殊的打印逻辑,如Json::stringify。

  3. 移除了将带参数的构造器直接当作高阶函数使用的行为,如果需要把构造器用作高阶函数,需要写一个匿名函数:

    test {
      let _ : (Int) -> Int? = Some // 已被移除
      let _ : (Int) -> Int? = x => Some(x) // 正确的写法
      let _ : Int? = 42 |> Some // 管道不受影响
    }

    这一行为之前已通过警告的形式废弃。注意管道运算符右侧依然可以直接写构造器,不受影响 。

  4. 废弃了 fn 上的副作用推导。 如果一个 fn 实际上可能抛出错误或者调用 async 函数,就必须加上 raise / async 标记,否则编译器会报一个警告。箭头函数语法 (..) => .. 不受影响。因此,未来对于回调函数类的匿名函数,建议使用箭头函数而非 fn。fn 可以在需要显式标注以改善可读性的时候使用

  5. 调整了 x..f() 的语义,将其调整回最简单的语义:x..f() 等价于 { x.f(); x }。 之前,x..f() 表达式的结果(x)可以被直接忽略。现在,编译器会对这种情况报一个警告,需要把最后一个 ..f() 替换成 .f() 或者显式忽略结果。

  6. 循环的 else 块关键字改为 nobreak,for/foreach/while 循环中此前可以用 else block 来写明在循环正常退出时的计算结果,为了更加直观,这一关键字被改成了 nobreak,比如:

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

    这一改动可以使用 moon fmt 自动迁移。

  7. 新增了一个默认关闭的警告 unnecessary_annotation,它会标记出代码中的结构体字面量和构造器上不必要的类型标注,即那些编译器可以通过上下文推断出正确的类型、无需显式指定类型的代码。

工具链更新​

  1. 正式启用 moon.pkg,在对 moon.pkg 进行了一段时间的测试和改进后,我们正式启用了 moon.pkg。旧的项目在执行 moon fmt 时将会被自动迁移到新的格式。新的项目也会直接使用 moon.pkg 作为包的配置。下面是常用配置的例子:

    import {
      "path/to/pkg1",
      "path/to/pkg2" @alias,
    }
    
    warnings = "+deprecated-unused_value"

    更多详细信息请见 moonbit 语言文档。

  2. moon test 支持通过 -j 参数并行地运行测试。

  3. moon test 支持通过 --outline列出所有待运行的测试。

  4. moon test --index支持指定特定范围的测试(左闭右开),如moon test --index 0-2会运行前两个测试(--index需事先指定测试的文件)。

  5. moon install支持从MoonBit 项目全局安装可执行程序",因为 moon check 与 moon build都可以自动安装依赖。 moon install的新行为类似 cargo install 或 go install,支持用户从包管理平台、git 源或者本地安装一个或多个二进制文件到全局(对应包需要支持 native 后端且 is-main 为 true),如:

    moon install username/package (root 为 package 时)
    moon install username/cmd/main (安装某一个包)
    moon install username/... (前缀开始所有的包)
    moon install ./cmd/main (local path)
    moon install https://github.com/xxx/yyy.git (自动识别 git 链接)

    更多用法可以使用 moon install --help查看。

  6. 现在可以在 moon.pkg 中配置regex_backend选项来指定 lexmatch 表达式的正则使用什么后端:

    options(
      // 默认为 "auto",其他可选项分别为 "block", "table", "runtime"
      // auto 由编译器自主决定采用哪个后端
      // block 后端性能最好,但代码体积可能产生膨胀
      // table 后端生成查表解释执行的代码,兼顾代码体积和性能
      // runtime 后端生成依赖标准库中 regex_engine 的代码,在大量使用正则的情况下,能大幅减少生成的代码体积
      regex_backend: "runtime",
    )
  7. moon -C <path>以前会从对应路径开始查找 MoonBit 项目,但是不会改变工作目录;这与一般构建系统传统不符。现在moon -C <path>会改为改变工作目录,并且需要出现在任何子命令或参数前;同时添加了--manifest-path指向moon.mod.json用于运行路径与源代码路径不同的情况.

  8. moon run 和 moon build 默认使用 --debug。

  9. 更新了 .mbt.md 文件在 front matter 声明依赖的形式。之前在 front matter 中只能声明 module dependency,并且会将被依赖的 module 中的 package 全部导入,这会导致无法更细粒度地写明 import 以及 package alias 会冲突的问题。在新版本中,front matter 声明依赖的形式改成了直接写明具体依赖的包,并且可以声明 alias,并且需要在 module 后面写明版本号,多次出现的 module 只需写一次版本号即可,对标准库的依赖不需要写版本号。

    ---
    moonbit:
      import:
        - path: moonbitlang/async@0.16.5/aqueue
          alias: aaqueue
      backend:
        native
    ---
  10. moon new简化了模板,更新了关于 skills 的简单介绍。

  11. moon fetch提供了一个简单的获取已发布包源代码的方式,默认会保存至项目根目录或当前路径下的.repos,方便 Agent 阅读源代码学习使用方式。

  12. moon fmt支持保留和折叠{ statement1; statement2 }语句之间的空行。例如:

    // 格式化前
    fn main {
      e()
    
      // comment
      f()
    
    
      g()
      h()
    }
    // 格式化后
    fn main {
      e()
    
      // comment
      f()
    
      g()
      h()
    }
  13. moonbit 现在会被自动格式化成 moonbit nocheck 在 *.mbt.md文件或者文档注释中,对于被设置为跳过检查的 ```moonbit 代码块,格式化器会自动加上更显式的 nocheck 标记 。

标准库和实验库更新​

  1. moonbitlang/async 改动:
  • 新增了 @process.spawn,可以直接在一个 TaskGroup 中创建一个外部进程,并获取该进程的 PID。TaskGroup 在默认状态下会等待该外部进程结束,在需要提前退出时会自动中止这个外部进程
  • 新增了 @fs.File::{lock, try_lock, unlock} 方法,提供文件锁的支持。普通的文件 IO 不受文件锁的影响
  • 新增了 @fs.tmpdir(prefix~) ,提供创建临时文件夹的支持
  • 新增了 @async.all 和 @async.any,语义类似 Promise.all 和 Promise.any
  • 在 examples 文件夹下新增了更多简单示例和对每个示例的介绍
  1. @json.inspect 迁移至 json_inspect

IDE 更新​

  1. 优化alias定义跳转:查找alias定义时,现在除了会显示alias定义的位置外,还会一并显示 alias target 定义的位置: alt text

  2. moon ide hover:moon ide新增 hover 子命令,用于显示源代码中某个符号的类型和文档:

    $ 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 rename: moon ide新增 rename 子命令,用于生成符合codex apply_patch 工具格式的重命名patch,方便agent更准确快速地重构代码。例如:

    $ 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 月报 Vol.7

· 阅读需 7 分钟

对应moonc版本:v0.7.1

语言更新​

  1. 添加了 unused async 的警告。这有助于清理未使用的 async 标记,从而提升代码可读性、可维护性,并且能够避免潜在的爆栈问题。

    pub async fn log_debug(msg : String) -> Unit {
      //^^^^^ Warning (unused_async): This `async` annotation is useless.
      println("[DEBUG] \{msg}")
    }
  2. Optional argument 的默认值表达式支持抛错。如果 optional argument 的默认值能够抛错误,则该函数签名本身需要支持抛错误。

    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. 新增 #declaration_only attribute,支持函数/方法/类型。可用于 spec-driven development,允许先定义函数签名和类型声明,后续再补充实现。在函数/方法上使用 #declaration_only 时,需要使用 ... 填充函数/方法体。以下是一个简单的 TOML parser 功能的声明:

    #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的显示迁移为相对路径。例如:

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

    运行 moon run . 输出:

    main.mbt:9:3-9:20@username/test
  5. 对于只进行读写操作的 array literal 添加了警告,提示可以改用 ReadOnlyArray 或者 FixedArray 以获得更好的编译优化,比如:

    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
    }

    目前该警告默认关闭,需要用户手动在 moon.pkg 中通过 warn-list 中添加 +prefer_readonly_array 和 +prefer_fixed_array 打开

  6. 管道语法支持改进

    支持了e1 |> x => { e2 + x }的语法,这个语法可以简化原先的e |> then(x => e2 + x)。

  7. 支持了给 for 循环标注 loop invariant 和 reasoning 的功能,比如:

    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. 废弃了在类型未知的情况下,通过结构体字面量推导出 Ref 类型的行为:

    let x = { val: 1 } // 之前会自动推导出 `Ref[Int]` 类型,
                      // 该行为已被废弃,目前会报警告,以后会移除
    let x : Ref[_] = { val: 1 } // 类型已知时没有问题
    let x = Ref::{ val: 1 } // 有类型标注时没有问题
    let x = Ref::new(1) // 也可以使用 `Ref::new` 而非字面量构造

工具链更新​

  1. 实验性 moon.pkg 支持

    // moon.pkg
    // 导入包
    import {
      "path/to/package1",
      "path/to/package2" as @alias,
    }
    
    // 黑盒测试的导入
    import "test" {
      "path/to/test_pkg1",
      "path/to/test_pkg2" as @alias,
    }
    
    // 白盒测试的导入
    import "wbtest" {
      "path/to/package" as @alias,
    }
    
    // 兼容原先moon.pkg.json的所有选项
    options(
      warnings: "-unused_value-deprecated",
      formatter: {
        ignore: ["file1.mbt", "file2.mbt"]
      },
      // 兼容旧的带“-”命名的选项,可以使用双引号
      "is-main": true,
      "pre-build": [
        {
          "command": "wasmer run xx $input $output",
          "input": "input.mbt",
          "output": "output.moonpkg",
        }
      ],
    )

    支持了实验性的 moon.pkg 配置文件来代替 moon.pkg.json。moon.pkg 使用接近于 MoonBit Object Notation 的语法,在简化书写配置的同时尽可能保持简单。

    • 兼容旧的格式,当一个包内存在moon.pkg文件时,moonbit将使用它作为这个包的配置。
    • 支持格式化,当设置了环境变量NEW_MOON_PKG=1时,moon fmt 将自动迁移项目中旧的 moon.pkg.json 配置,生成新的文件。
    • 支持注释和空的 moon.pkg 配置。

    options(...)声明内兼容了所有moon.pkg.json提供的配置,我们后续会对已经稳定和常见的配置提供和它平级的声明支持。详细语法以及后续改进见 https://github.com/moonbitlang/moonbit-evolution/pull/17

  2. 现在 moon add 会先自动执行 moon update,简化了工作流程.

  3. 重构后的 moon 默认开启,可以用 NEW_MOON=0手动切换回更老版本的 moon 实现。

  4. 增加了间接依赖的支持。现在可以不用显式在 moon.pkg.json/moon.pkg中导入一个包的情况下,使用这个包里面的方法和 impl 。

    // @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()
    }
  5. moon fmt 不再格式化 prebuild 的输出。

  6. moon check --fmt 支持检测未格式化的源文件。

  7. moon在 target 为 js 时运行测试与代码不再受到项目中的 package.json 影响。

  8. 构建产物的目录从 target 切换到了 _build,目前还会生成 target 作为 symlink 指向 _build,以向后兼容

标准库和实验库更新​

  1. moonbitlang/async 改动:

    • 新增 Windows 支持。目前仅支持 MSVC,因此使用时需要在系统上安装 MSVC。除符号链接与文件系统权限外的功能已全部在 Windows 上实现

    • @fs.mkdir 新增了 recursive? : Bool = false 参数,recursive=true 时,如果目标路径的父文件夹不存在,会递归创建父文件夹

    • 改进了 @process.run 被取消时,自动终止外部进程的设计。现在,@process.run 会先尝试通知外部进程主动退出,超时后再强制终止外部进

    • @process.read_from_process 与 @process.write_to_process 现在会返回专门的临时管道类型 @process.ReadFromProces 和 @process.WriteToProcess,而非 @pipe 中的通用管道类型

  2. 标准库中的迭代器类型正式迁移到外部迭代器。对用户来说的改动是:

    • Iter::new的签名发生改变,从内部迭代器变成外部迭代器。此前 Iter::new 已通过警告弃用,并提示用户通过 https://github.com/moonbitlang/core/pull/3050 进行迁移
    • 外部迭代器类型 Iterator 和 Iter 类型合并,此后只有一种迭代器类型。如果同时给 Iter 和 Iterator 实现了 trait,需要删除 Iterator 上的实现
    • Iterator 这个名字被弃用,用户应使用 Iter。此外,标准库中的各种容器类型的 .iterator() 方法也被弃用,应改为 .iter()

    对大部分用户(没有手动构造迭代器)来说,上述改动不会影响现有代码。但需要注意,原本的 Iter 类型可以多次重复遍历,重复遍历会导致重复计算。现在,Iter 只能遍历一次。这是一项行为上的不兼容改动。重复遍历同一个迭代器是不被鼓励的用法,用户应当避免重复遍历同一个迭代器

  3. 实验性 lexmatch 表达式支持 POSIX character classes(例如 [:digit:]),同时开始弃用 \w/\d/\s等转义序列。POSIX character classes 仅可在方括号表达式内使用,例如:

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

    输出:

    1234

IDE 更新​

  1. LSP 修复了 .mbti 不工作等问题。

  2. moon ide 命令行工具,文档可参见https://docs.moonbitlang.com/en/latest/toolchain/moonide/index.html

    • 支持 moon ide peek-def,能够根据位置和 symbol 名字找到对应的定义。例如:
    ///|
    fn main {
      let value = @strconv.parse_int("123") catch {
        error => {
          println("Error parsing integer: \{error}")
          return
        }
      }
      println("Parsed integer: \{value}")
    }

    运行 moon ide peek-def -loc main.mbt:3 parse_int, 输出:

        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。该命令以简略的方式列出指定包的大纲。
    • 之前 moon doc <符号或包名>迁移到moon ide doc <符号或包名>。
  3. Doc test 支持 mbt check 。现在,你可以在 doc test 里面写 test block 并获得运行/调试/更新测试的 codelens : alt text

20251202 MoonBit 月报 Vol.06

· 阅读需 9 分钟

对应moonc版本:v0.6.33

语言更新​

  • ReadOnlyArray 的功能完善。上个版本中引入了 ReadOnlyArray ,它主要用于声明查找表并且编译器会针对 ReadOnlyArray 做更多的性能优化。 在这个版本中,ReadOnlyArray 相关的特性支持得到了完善,使其使用体验和其他数组类型基本一致,比如对其进行模式匹配,取切片,和 splice 等操作。

    fn main {
      let xs: ReadOnlyArray[Int] = [1,2,3]
      let _ = xs[1:]
      match xs {
        [1, .. rest] => ...
        ...
      }
      let _ = [..xs, 1]
    }
  • bitstring pattern 支持 signed extraction,可以将取出的 bits 当作有符号整数进行解释,比如

    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 函数调用改进 以前在x..f()..g()这种形式的函数调用中,要求f的返回类型必须是Unit。现在解除了这个限制,当f会返回一个Unit以外类型的值时,将触发invalid_cascade警告,在运行时这个返回值会被隐式地丢弃:

    struct Pos {}
    fn Pos::f(_ : Self) -> Int  { 100 }
    fn Pos::g(_ : Self) -> Unit { ()  }
    fn main {
      let self = Pos::{}
      self
      ..f() // warning, 返回值 100 被丢弃
      ..g()
    }

    如果希望在项目中禁止这样的隐式弃用,可以通过配置"warn-list": "@invalid_cascade"将这种警告视为错误。

  • 语法解析改进

    • 改进了StructName::{ field1: value }漏写::时的错误恢复
    • 改进了for x in a..=b {}和match e1 { a..=b => e2 }写错 range 语法时的错误恢复
  • .mbt.md代码块支持改进, 我们决定将参与编译的markdown代码块变得更显式,具体的变化如下:

    • 不再编译只标记了mbt或moonbit的代码块,需要将这些代码块显示标记为 check,也就是 mbt check 或 moonbit check 后才会和以前一样编译。
    • 新增 mbt test 和 mbt test(async)代码块,这些代码块除了会参与编译之外,还会在将代码块裹在一个 test 或 async test 里面,在markdown中使用这两种代码块的时候用户不需要再手动写 test {}或 async test了。
      一个 Markdown 示例
    
          只有高亮:
    
          ```mbt
          fn f() -> Unit
          ```
    
          高亮并检查:
    
          ```mbt check
          fn f() -> Unit {...}
          ```
    
          高亮、检查并当作测试块:
    
          ```mbt test
          inspect(100)
          inspect(true)
          ```

    docstring 中的markdown也同样做了以上变更,不过目前尚不支持 mbt check,将来会支持。

  • #label_migration 属性

    #label_migration 属性支持给参数 label 声明别名,主要有两种用途:

    • 一是可以给同一个参数两个不同的 label,
    • 二是当额外提供 msg 的时候可以用于 deprecate 某个参数 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 默认行为改进

    deprecated默认状态下的行为改为 skip_current_package=false,即对当前包内的使用也会报警告。如果递归定义或者测试上出现了预期外的警告,可以用 #deprecated(skip_current_package=true) 显式对当前包关闭警告,或是使用新增的 #warnings属性来临时关闭警告。

  • warnings 和 alerts 改进

    • 给 warnigns 增加助记词 现在你可以通过它们的名字而非编号配置警告:"warn-list": "-unused_value-partial_match"

    • #warnings 属性支持

      现在支持通过#warnings属性来局部地开关警告。属性内部的参数是和warn-list配置相同的字符串,字符串内有多个警告名,每个警告名之前用一个符号表示对该警告的配置:-name表示关闭该警告;+name表示打开该警告;@name表示如果警告已经打开,调整成错误。

      例如,下面的例子中关闭了整个函数 f 的unused_value警告,把默认打开的deprecated警告调整为错误。现在它不会提示变量未被使用,而如果 f 内使用了弃用的 API,编译会不通过:

      #warnings("-unused_value@deprecated")
      fn f() -> Unit {
        let x = 10
      }
    • 合并 alerts 和 warnings

      弃用 alerts 相关配置,现在 alerts 成为了 warnings 的子集。使用-a关闭所有警告时,会将所有 alert 一同关闭。特别的,在 warn-list中,可以用alert指代所有的alert,alert_\<category\>指代某一类别的 alert:

      #warnings("-alert")
      fn f() -> Unit { ... } //关闭所有 alert 警告
      
      #warnings("@alert_experimental")
      fn g() -> Unit { ... } //关闭被#internal(experimental, "...")标记的 API 相关
    • test_unqualified_package 警告

      增加了test_unqualified_package警告,它默认是关闭的。启用时,会要求黑盒测试使用@pkg.name的形式引用被测试的包的 API,否则触发该警告。

  • Lexmatch 改进

    实验性lexmatch 表达式支持 first(默认)匹配策略,该匹配策略下,支持 search 模式和 non-greedy quantifiers。具体细节请查看提案文档。

    // 查找第一个块注释,并打印注释内容
    lexmatch s { // 可以省略 `with first`
      (_, "/\*" (".*?" as content) "\*/", _) => println(content)
      _ => ()
    }
  • 类型推导改进

修复了预期类型是 ArrayView[X] 时,X 中的类型信息无法传播到表达式中的问题,以及预期类型是带参数的新类型时,类型参数无法传播到表达式中的问题。

  • 添加了 #module 属性用于导入 JS 模块。比如可以使用如下代码导入 "path" 这个第三方 JS module 中的函数:
#module("path")
extern "js" fn dirname(p : String) -> String = "dirname"

这段代码会生成如下的 JS 声明(改示例被简化过,实际代码会有一些 name mangle):

import { dirname } from "path";

工具链更新​

  • IDE 补全改进 IDE 现在会以删除线的形式显示弃用的 API: alt text

  • 构建系统改进

    • 增强了 expect/snapshot test diff 的可读性。现在,这些地方使用 unified diff 格式展示期望和实际值的差异,使得在 CI、文件等不输出颜色的场景下依然可读,同时在可以展示颜色时可以看到着色、划重点的对比结果。 alt text

    • 我们基本完成了构建系统后端的完整重写,可以通过 NEW_MOON=1 环境变量打开试用。

      新后端在构建过程中更不容易因为各类边界情况出错,稳定性更强,同时相对于当前实现的性能也有所提升。新后端现已支持了绝大部分现有的功能,且与当前实现的行为完全一致,但是在某些情况(如并行运行测试)中可能欠缺一部分的优化。

      如果在运行新后端时出现问题,包括性能问题和行为不一致的问题,请在 https://github.com/moonbitlang/moon/issues 反馈。

    • 我们为 moon {build,check,info} 添加了基于文件路径的筛选方式 moonbuild#1168。

      在运行 moon build、moon check、moon info 时,将需要处理的包所在的文件夹路径或者其中的文件路径传入,就可以只运行对应包的对应指令。这一使用方式类似于 -p <包名>,但是不需要输入完整的包名。这一功能与 -p 不能同时使用。例如:

      # 只构建 path/to/package 路径对应的包
      moon build path/to/package
      
      # 只检查 path/to 路径对应的包
      moon check path/to/file.mbt
  • moon doc符号查找 我们做了一个类似 go doc 的符号搜索命令行工具,方便AI agent或开发者快速搜索可用的API。目前支持了以下功能:

    • 在 module 里查询可用的包
    • 在 package 里查询所有可用的东西(值、类型、trait)
    • 查询一个类型的成员 (method, enum variant, strcut field, trait method)
    • 在查询alias一直到最终定义
    • 查询内建类型
    • 支持 glob pattern

    直接运行 moon doc <符号名或者包名> 即可查询对应符号或包的文档。

  • moon fmt改进

    • 支持格式化文档注释中标记为 moonbit 、moonbit test 的代码块
    • moon.pkg.json支持配置忽略列表
      { // in moon.pkg.json
        "formatter": {
          "ignore": [
            "source1.mbt",
            "source2.mbt"
          ]
        }
      }
  • async test 现在支持限制同时运行的测试的最大数量,默认值为 10,可以通过 moon.pkg.json 中的 "max-concurrent-tests": <number> 来修改

标准库和实验库更新​

  • 弃用Container::of函数

    现在ArrayView是统一的不可变切片,可以从Array FixedArray ReadonlyArray创建,因此将of from_array等初始化函数(of)统一为Type::from_array,其参数从接受Array改成ArrayView。现在推荐使用Type::from_array从数组字母量创建容器。

  • 添加了 MutArrayView 作为统一的可变切片

    ArrayView 在之前的版本中由可变类型变成了不可变类型,但有些时候又需要通过切片修改原有数组的元素,所以引入了 MutArrayView 作为补充。 MutArrayView 可以从 Array FixedArray创建。

  • @test.T改名为@test.Test、@priority_queue.T改名为 @priorityqueue.PriorityQueue

  • 字符串索引改进

    string[x]将会返回 UInt16。请通过 code_unit_at进行迁移

  • moonbitlang/x/path 实验库改进

    支持 Windows 路径和 POSIX 路径的处理动态切换, Python os.path 风格的 API 设计.

  • moonbitlang/async更新

    • moonbitlang/async实验性地支持了 js 后端。目前覆盖的功能有:
      • 和 IO 无关的所有功能,包括 TaskGroup、@async.with_timeout 等控制流构造、异步队列等
      • 提供了一个和 JS 交互用的包 moonbitlang/async/js_async,支持 MoonBit async 函数和 JavaScript Promise 的双向互转,支持基于 AbortSignal 的自动取消处理
    • 支持了 WebSocket,可以通过 moonbitlang/async/websocket 包引入
    • moonbitlang/async/aqueue 现支持固定长度的异步队列。在队列已满时,支持阻塞写入者/覆盖最老元素/丢弃最新元素三种不同的行为,可以通过创建队列时的参数来控制
    • moonbitlang/async/http 中的 HTTP client 和发起 HTTP 请求 API 现支持指定 HTTP CONNECT 代理。能够支持全流程加密的 HTTPS 代理和需要登录的代理
    • 改进了moonbitlang/async 的 HTTP 服务器 API,现在用户的回调函数可以一次只处理一个请求,不需要手动管理连接