跳到主要内容

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 { .. } 语法和模式匹配 structMap 有语法冲突

  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_reportnever_called_pub

  2. supported-targets支持完善

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

    • moon.mod.jsonmoon.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 语法容易产生 ErrPayloadType 有相同 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,例如数组下标越界,对为NoneOption[T]进行unwraptry!一个会抛出错误的函数,或者手动调用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>..yx>=..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(..) 的参数不能写默认值,但可以省略参数名字
  • 对于有类型参数的 structfn 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 structpub(all) struct的构造器可以在当前包外调用,structpriv 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 标记,否则编译器会报一个警告。箭头函数语法 (..) => .. 不受影响。因此,未来对于回调函数类的匿名函数,建议使用箭头函数而非 fnfn 可以在需要显式标注以改善可读性的时候使用

  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 checkmoon build都可以自动安装依赖。 moon install的新行为类似 cargo installgo 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 runmoon 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.allPromise.any
  • examples 文件夹下新增了更多简单示例和对每个示例的介绍
  1. @json.inspect 迁移至 json_inspect

IDE 更新

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

  2. moon ide hovermoon 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 进行迁移
    • 外部迭代器类型 IteratorIter 类型合并,此后只有一种迭代器类型。如果同时给 IterIterator 实现了 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代码块变得更显式,具体的变化如下:

    • 不再编译只标记了mbtmoonbit的代码块,需要将这些代码块显示标记为 check,也就是 mbt checkmoonbit check 后才会和以前一样编译。
    • 新增 mbt testmbt test(async)代码块,这些代码块除了会参与编译之外,还会在将代码块裹在一个 testasync 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 buildmoon checkmoon 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,现在用户的回调函数可以一次只处理一个请求,不需要手动管理连接

20251103 MoonBit 月报 Vol.05

· 阅读需 8 分钟

版本号 v0.6.30+07d9d2445

编译器更新

1. 对于 alias 系统的更新。

过去在 MoonBit 中我们针对 trait、fn 和 type 有三种不同的别名语法:

  • traitalias @pkg.Trait as MyTrait

  • fnalias @pkg.fn as my_fn

  • typealias @pkg.Type as MyType

这种区分带来了不必要的复杂性和限制,例如我们无法为 const 值创建别名。在接下来的版本中,我们将引入新的 using 语法来统一这些别名的创建方式。

using { ... } 的语法用于统一 traitaliasfnalias,和简单typealias,其具体语法如下:

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

该语法会创建重名别名,使得在当前包内可直接使用 valueCONSTTypeTrait 引用 @pkg 的内容。

using 前面可以增加 pub 使得这些别名对外可见。

using 语句中也可以使用 as 关键字进行重命名,例如:

using @pkg {
 value as another_value
}

这样就会创建一个名为 another_value 的别名指向 @pkg.value。但我们并不鼓励使用 as 进行重命名,因为这会降低代码的可读性,所以目前编译器会对使用 as 的地方报一个警告。我们会在将来移除这个语法,目前保留它是为了让大家可以使用 using 来迁移 fnalias

除了 using,本次更新还新增了 type/trait 上的 #alias 属性支持。因此,现在所有顶层定义都可以用 #alias 来创建别名了。

前面提到的简单的 typealias 是指只单纯的创建了类型的别名,例如 typealias @pkg.Type as MyType,对于更复杂的类型别名,它们无法用 using 来创建,于是我们引入了 type AliasType = ... 的语法来创建复杂类型的别名,例如:

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

总体来说,将来我们会彻底删除 traitaliasfnaliastypealias,并用 usingtype Alias = ...#alias 来替代它们。其中:

  • using 用于在当前包内创建另一个包的别名,例如用于引入其他包中的定义

  • type Alias = ... 用于创建复杂类型的别名

  • #alias 用于为当前包的内容创建别名,例如用于进行命名修改的迁移

使用 moon fmt 可以自动完成大部分迁移。后续我们会对旧的 typealias/traitalias/fnalias 语法汇报警告,并最终移除它们

2. 测试名重名检查。现在编译器将对重名的测试名报告警告。

3. 实验性 lexmatch 更新

实验性 lexmatch 更新支持了类似is表达式的 lexmatch? 表达式,和 case-insensitive modifier 语法(?i:...)。详情请查看 proposal

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

4. 增加了对anti-pattern的linting检查

编译器增加了对一些 anti-pattern 的 linting 检查,会在编译时报告警告。目前包括以下检查:

anti-pattern:

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

修改建议:

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

anti-pattern:

"<string literal>" * n

修改建议

"<string literal>".repeat(n)

5. 新增了ReadOnlyArray 内置类型

编译器中新增了ReadOnlyArray 内置类型,ReadOnlyArray 表示定长不可变数组,它主要应用场景是作为 lookup table 的类型使用,使用 ReadOnlyArray 时,如果其中元素全部都是字面量,可以保证其在 C/LLVM/Wasmlinear 后端被静态初始化。其使用方式与 FixedArray 基本一致,比如:

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

fn main {
  println(int_pow10[0])
}

6. bitstring pattern 语法形式的调整。

之前 bitstring pattern 支持使用 u1(_) 这种 pattern 来匹配单个 bit,与此同时,MoonBit 中在使用构造器进行模式匹配时允许省略 payload,比如:

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

这会导致当使用 u1 的时候,会比较困惑于它是个 bitstring pattern 还是单个 identifier,于是为了避免与普通 identifier 产生混淆,在新版本中,我们要求 bitstring pattern 通过后缀的形式写明,比如 u1be(_)。(bit 的读取顺序永远是高位优先,所以端序本身对于读取长度不超过 8 bit 的数据时没有意义,这里只在更为显示地提示当前 pattern 使用 bitstring pattern。)

另外因为小端序的语义比较复杂,所以只有当读取的 bit 数量为 8 的整数倍时才可使用小端序,比如 u32le(_)

总的来说,当前 bitstring pattern 的语法是 u<width>[le|be] 其中:

  • width 的范围是 (0..64]

  • lebe 的后缀是要写明的,所有 width 都支持 be,只有 width 是 8 的整数倍的时候支持 le

7.#deprecated 新增了一个参数skip_current_package

之前,使用某个当前包内定义的、通过 #deprecated 废弃的定义时,编译器不会报警告。这是为了防止测试/递归定义中出现无法消除的警告,但有时候也需要检测当前包内对某个废弃定义的使用。因此,#deprecated 新增了一个参数skip_current_package ,它的默认值是 true,可以用 #deprecated(skip_current_package=false) 来覆盖。当 skip_current_package=false 时,在当前包内使用这个定义也会报警告

8. moon.pkg DSL 计划

我们计划设计一个新的moon.pkg语法来代替原有的moon.pkg.json 。新的DSL将会在三个前提下改进import信息的编写体验:

  • 兼容原有的选项

  • 对构建系统的构建速度友好

  • 保留import"对整个包内有效"的语义

目前提案的moon.pkg语法概览(非最终效果):

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

    // 黑盒测试的import
    import "test" {
      "path/to/pkg5",
      "path/to/pkg6" as @alias3,
    }

    // 白盒测试的import
    import "wbtest" {
      "path/to/pkg7",
      "path/to/pkg8" as @alias4,
    }

    config {
      "is_main": true, // 允许注释
      "alert_list": "+a+b-c",
      "bin_name": "name",
      "bin_target": "target",
      "implement": "virtual-package-name", // 允许尾随逗号
    }

提案讨论区和详细内容:https://github.com/moonbitlang/moonbit-evolution/issues/18

构建系统更新

1. 弃用 moon info --no-alias。在上次月报中,我们增加了 moon info --no-alias的功能,用于生成不包含默认 alias 的 pkg.generated.mbti 文件:

moon info

    package "username/hello2"

    import(
    "moonbitlang/core/buffer"
    )

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

moon info --no-alias

    package "username/hello2"

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

在实践中我们发现该功能并没有太多用处,反而增加了维护成本,因此我们决定弃用该功能。

2.更新 moon new 生成的模版项目

我们在 moon new 生成的模版项目中新增了 .github/workflows/copilot-setup-steps.yml,用于帮助用户快速在 MoonBit 项目中使用 GitHub Copilot。

IDE更新

1. mbti 文件支持引用查找

用户可以在mbti文件中查找某个定义在项目中的引用情况:

如图所示,我们目前支持了三种不同的查找引用的方式:

  • Go to References:在该包的所有反向依赖中查找

  • Go to References (current package only):只在该包内部进行查找,包含所有的测试

  • Go to References (current pacakge only, excluding tests):只在该包内部进行查找,并跳过所有的测试

2.mbti 文件支持 Hide 和 Unhide code action,便于重构包的 API

对于 mbti 中的定义,LSP会提供Hide ...的code action用于将其变成private,在执行了上面code action后,Manager将会变成一个私有定义:

如果执行code action之后没有报错,说明这是一个冗余的API,可以删除;如果有报错,用户可以通过 Unhide ...的code action撤销刚才的操作

3. 更新测试后自动格式化被更新的代码块

在vscode中使用 update codelens更新测试后,被更新的测试会自动格式化。

4. 增加更多 attribute snippet 的补全

标准库更新

  • 新增了 external iterator 类型 Iterator。我们会将 core 中常见的容器类型的迭代器从 internal 迁移到 external。这是为了可以在 for .. in 中使用 async 以及支持 zip 等 internal iterator 无法支持的 API。for .. in 循环现在会优先尝试调用 .iterator()/.iterator2() 方法、通过 external iterator 进行遍历。在被遍历的类型不支持 external iterator 时,则会 fallback 到原先 internal iterator 类型 Iter。未来,我们将通过警告等方式从 internal iterator 迁移到 external iterator,并最终只保留 external iterator

MGPIC 2025

1. 编译器快速入门 一个关于编译器的快速入门教程

2. 编译器交互式教程 我们提供了一个完整、系统、循序渐进的教程帮助参赛选手一步一步搭建出一个功能完备的 MiniMoonBit 编译器

3. 九月游戏评比,我们评选了九月份的月度游戏奖项:

  • 九月最佳视觉奖:赛博拾荒者-CyberScavenger

  • 九月最佳创意奖:水墨之灵-Inkfish,Flappy Bird 增强版

感兴趣的朋友可以进入 https://moonbitlang.github.io/MoonBit-Code-JAM-2025/ 试玩

20251014 MoonBit 月报 Vol.04

· 阅读需 4 分钟

对应moonc版本:v0.6.29

语言更新

  • 新增async testasync fn main 语法, 支持异步测试与异步主函数。async fn mainasync test 基于 moonbitlang/async 库,目前支持 Linux/MacOS 上的 native 后端。关于 MoonBit 异步编程的更多信息见moonbitlang/async文档GitHub 仓库async test 声明的异步测试会并行地运行:

    ///|
    async test "http request" {
      let (response, result) = @http.get("https://www.example.org")
      inspect(response.code, content="200")
      assert_true(result.text().has_prefix("<!doctype html>"))
    }
  • 新增lexmatch表达式(实验性特性)。提供了用正则表达式对StringViewBytesView进行模式匹配的能力。下面这个例子匹配2到4个连续的字符'a'以及紧随其后的字符'b', 并将连续的'a'捕获为变量a。更具体的使用方式可参考 moonbitlang/parser 中的 lexer.mbtmoonbit-evolution 中的 lexmatch 提案

    lexmatch x using longest {
      (("a{2,4}" as a) "b", _) => Some(a.length())
      _ => None
    }
  • 新增using语法,统一了 fnaliastraitalias 和简单 typealias。在导入类型和 trait 时,需要在名称前添加对应的关键字:

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

      另外,也可以使用 pub using 实现 re-export 的效果,将其他包的定义在当前包重新导出。未来,fnaliastraitalias 和简单 typealias 语法将会被废弃,给外部定义创建别名的功能会被 using 代替,给当前包的定义创建别名的功能由 #alias 属性代替

  • trait 中的方法支持可选参数:

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

      可选参数的默认值由每个 impl 各自决定,不同的 impl 可以设置不同的默认值,或者直接不提供默认值(此时可选参数在 impl 内部的类型会是 T?None 表示用户没有提供这个参数)

  • 支持使用 #alias 来重载 op_get 等运算符,相比 op_xxx 可读性更好。目前支持下列操作符:

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

      这里,实际的实现的名字(上面的 get/set/view)可以随意设置,只需要写上对应的 #alias,就可以完成运算符重载。我们推荐使用 #alias 代替 op_xxx 来进行基于方法的运算符重载(+ 等运算符是通过 trait 重载的,不受影响)

  • 一些已经废弃较长时间的语法和行为被正式移除:

    • 过去,用 fn meth(self : T, ..) 形式定义的方法,既是方法也是函数,可以直接当作普通函数使用。这一行为已经废弃较长时间,编译器会提供警告。现在,这一行为被正式移除。用 fn meth(self : T, ..)现在等价于 fn T::meth(self : T, ..)。未来,self 形式的方法定义本身也可能被废弃

    • moon.pkg.json 中的 direct_use 字段被正式移除,由 using 代替

工具链更新

  • 发布了wasm版工具链, x86 Darwin与 arm Linux用户可使用:https://www.moonbitlang.cn/blog/moonbit-wasm-toolchain

  • 我们为构建系统开发了一个实验性的新版本 (RR)。这一新版本拥有更高的性能和更好的可维护性,将会完全替代 moon 现在的内部实现,欢迎大家试用并寻找问题。可以使用环境变量 NEW_MOON=1 或者命令行参数 -Z rupes_recta 启用。如果遇到任何问题,请发在 https://github.com/moonbitlang/moon/issues 上。

  • moon fmt支持对.mbt.md文件进行format

  • 新增moon info --no-alias ,在生成pkg.generated.mbti 文件时不显示类型别名

标准库更新

  • 为了应对潜在的 HashDos 攻击,Hash 的计算将会变为进程随机。目前 JS 后端已实现此修改。

  • ArrayView已改为不可变数据结构,用于统一对Array FixedArray ImmutArray取切片。

20250908 MoonBit 月报 Vol.03

· 阅读需 4 分钟

语言更新

  • 1、新增 Bitstring pattern 支持,用于在模式匹配 Bytes 或者 BytesView 的过程中匹配特定宽度的 bits,比如可以用

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

      其中可以用 u<width>be 或者 u<width>le 来通过指定按大端或者小端序来匹配 width 宽度的 bits,如果没有写 be 或者 le 的话则默认大端序。width 长度范围是 [1, 64]

  • 允许直接用构造器进行模式匹配,来表示只匹配 enum 的 tag,比如:

    fn is_some(x: Int?) -> Unit {
      guard x is Some
    }

      之前如果一个有 payload 的构造器当作没有没有 payload 的构造器使用的话,编译器会进行报错,现在改成了报 warning,从而用户可以选择通过 warn-list 参数关掉这类 warning

  • 新增 #callsite(migration) attribute,用于对 optional argument 进行代码迁移,比如下面代码的作者希望在下个版本中修改参数 y 的默认值,所以通过 migration(fill=true, ...) 来提示下游用户都显式提供参数值,并且在下个版本中将去掉可选参数 z ,所以通过 migration(fill=false, ...) 来提示下游用户不需要再显式提供参数 z

    #callsite(migration(y, fill=true, msg="must fill y for migration"), migration(z, fill=false, msg="cannot fill z for migration"))
    fn f(x~ : Int, y~ : Int = 42, z? : Int) -> Unit {
      ...
    }
  • 新增 #skip attribute 用于跳过测试,比如:

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

      不过编译器依然会对 test block 中的代码进行类型检查

  • 新增 #as_free_fn attribute,用来替代 fnalias Type::f 的功能,比如:

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

      这样就会允许 MyType::f 被当作普通函数 f 或者 f0 直接调用

  • #alias#as_free_fn 增加了可见性控制,比如:

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

      这里的 MyType::f 方法是 private 的,但是它的 free function f 是 public 的。下述例子中的 #alias 同理。

    #alias(pub_alias, visibility="pub")
    fn priv_func() -> Unit {
      println("priv func")
    }
  • 异步函数默认 raise,这样标记了 async 的函数可以不用再标注 raise,以使代码更加简洁,如果需要通过类型检查保证异步函数不会抛错误,可以使用 noraise 标记

  • 在 C/LLVM/Wasm 后端,FFI 声明中,ABI 为指针的参数必须标注 `#borrow` 或 `#owned`,否则编译器会产生一个警告。`#borrow` 表示被调用的 FFI 函数只会局部地读写对应的参数,不会存储或返回参数。此时,被调用的函数无需对参数做特殊操作。`#owned` 表示参数的所有权会被转移给被调用的 FFI 函数。详细情况见文档中外部函数调用相关章节。这一改动的动机是,未来我们希望将 FFI 参数的默认调用约定切换为 #borrow(目前是 #owned),因此通过强制标注的方式来帮助用户渐进地迁移

  • 修改了 FuncRef[_] 的 calling convention。之前,FuncRef[_] 是以 #owned 的方式处理参数的所有权的。现在改为了 #borrow。这一改动是 breaking 的:在 FFI 中使用了 FuncRef[_],且 FuncRef[_] 的参数中有指针类型的用户需要注意修改

工具链更新

  • IDE 支持了对 mbti 文件的 hover 和 gotodef 等功能

20250811 MoonBit 月报 Vol.02

· 阅读需 5 分钟

语言更新

  • 新增条件编译属性 cfg。可以根据后端等条件进行文件内的条件编译。

    #cfg(any(target="js", target="wasm-gc"))
    let current_target = "js | wasm-gc"
  • 新增#alias属性,目前可以给方法或函数创建别名,并支持标注废弃。后续支持更多场景。

    #alias(combine, deprecated="use add instead")
    fn Int::add(x : Int, y : Int) -> Int {
      x + y
    }
    
    test {
      let _ = Int::add(1, 2)
      let _ = Int::combine(1, 2)
    }
  • 新增 defer表达式。提供了一个基于词法作用域的资源清理功能。当程序以任何方式离开 defer expr; body 中的 body 时,expr 都会被运行

    fn main {
      defer println("End of main")
      {
        defer println("End of block1")
        println("block1")
      }
      for i in 0..<3 {
        defer println("End of loop \{i}")
        if i == 2 {
          break // `break` 等也能触发 `defer`
        }
        println("Looping \{i}")
      }
      return
    }
    block1
    End of block1
    Looping 0
    End of loop 0
    Looping 1
    End of loop 1
    End of loop 2
    End of main

      目前,defer exprexpr 里不能抛出错误或调用 async 函数。expr 里不能使用 return/break/continue 等控制流跳转构造

  • Native 后端的 Bytes 的末尾现在永远会有一个额外的 '\0' 字节,因此现在 Bytes 可以直接当作 C string 传给需要 C string 的 FFI 调用。这个额外的 '\0' 字节不计入 Bytes 的长度,因此现有代码的行为不会有任何变化

  • 调整可选参数的语法,默认参数现在可以依赖前面的参数(之前这一行为被废弃了,因为它和 virtual package 不兼容,但现在我们找到了在兼容 virtual package 的前提下支持这种复杂默认值的方式)。另外,我们统一了有默认值(label~ : T = ..)和没有默认值(label? : T)的可选参数:现在,对于函数的调用者来说,这两种默认参数不再有区别,并且都支持下列调用方式:

    • 不提供参数,使用默认值

    • 通过 label=value 的形式显式提供参数

    • 通过 label?=opt 的形式调用,语义是:如果 optSome(value),等价于 label=value。如果 optNone,等价于不提供这个参数

  • 调整自动填充参数的语法,改用 #callsite(autofill(...)) 属性替代原有语法

    // 原版本
    pub fn[T] fail(msg : String, loc~ : SourceLoc = _) -> T raise Failure { ... }
    // 现版本
    #callsite(autofill(loc))
    pub fn[T] fail(msg : String, loc~ : SourceLoc) -> T raise Failure { ... }
  • 废弃 newtype,增加 tuple struct 支持

    // 旧语法,运行时等价于 Int
    type A Int
    fn get(a : A) -> Int {
      a.inner()
    }
    
    // 新语法,运行时依然等价于 Int
    struct A(Int)
    fn get(a : A) -> Int {
      a.0
    }
    
    struct Multiple(Int, String, Char)
    fn use_multiple(x: Multiple) -> Unit {
      println(x.0)
      println(x.1)
      println(x.2)
    }
    fn make_multiple(a: Int, b: String, c: Char) -> Multiple {
      Multiple(a, b, c)
    }
    • 当 tuple struct 中类型数量为 1 个的时候,tuple struct 等价于原有的 newtype。因此,当 newtype 的 underlying type 不是 tuple 的时候,formatter 目前会自动将旧语法迁移至新语法。为了便于迁移,这种情况下的 tuple struct 也提供了一个 .inner() 方法,之后会 deprecated 掉并移除

    • 当 tuple struct 中类型数量超过 1 个的时候,tuple struct 和原有的 tuple newtype 的区别在于:

      • tuple struct 不能由直接通过 tuple 构造

      • tuple struct 不能通过 .inner() 方法得到一个 tuple

    • 如果需要可以直接和 tuple 互相转换的 tuple struct,可以使用:

    struct T((Int, Int))
    
    fn make_t(x: Int, y: Int) -> T {
      (x, y)
    }
    
    fn use_t(t: T) -> (Int, Int) {
      t.0
    }

不过这种情况下访问具体元素时,需要 t.0.0 或者 t.0.1 进行访问

  • 由于主要用途为数据存储和 @json.inspect 等功能,derive(FromJson, ToJson) 将不再提供高级格式调整参数。目前保留的格式参数为每个字段的 rename(重命名)、批量重命名和 enum 的格式选择 style,其余参数均将被移除。

    • style的可选项为legacyflat。后者简化了表示,适用于@json.inspect等场景。目前所有 enum 都必须选择其中一个 style 使用。

    • 如果需要自定义 JSON 的格式,请自行实现 FromJsonToJson 两个 trait。

    ///| Flat
    test {
      @json.inspect(Cons(1, Cons(2, Nil)), content=["Cons", 1, ["Cons", 2, "Nil"]])
    }
    
    ///| Legacy
    test {
      @json.inspect(Cons(1, Cons(2, Nil)), content={
        "$tag": "Cons",
        "0": 1,
        "1": { "$tag": "Cons", "0": 2, "1": { "$tag": "Nil" } },
      })
    }

工具链更新

  • 新增 moon coverage analyze功能,提供更直观的覆盖率报告

    Total: 1 uncovered line(s) in 2 file(s)
    
    1 uncovered line(s) in src/top.mbt:
    
       | fn incr2(x : Int, step? : Int = 1) -> Int {
    12 |   x + step
       |   ^^^^^^^^         <-- UNCOVERED
       | }
    
    
    Total: 1 uncovered line(s) in 2 file(s)
  • 现在 moon test --target js在 panic 的时候,能根据 sourcemap 显示原始位置了

    test username/hello/lib/hello_test.mbt::hello failed: Error
        at $panic ($ROOT/target/js/debug/test/lib/lib.blackbox_test.js:3:9)
        at username$hello$lib_blackbox_test$$__test_68656c6c6f5f746573742e6d6274_0 ($ROOT/src/lib/hello_test.mbt:3:5)
        at username$hello$lib_blackbox_test$$moonbit_test_driver_internal_execute ($ROOT/src/lib/__generated_driver_for_blackbox_test.mbt:41:9)
    

20250715 MoonBit 月报 Vol.01

· 阅读需 3 分钟

2025年6月18日发布beta版本之后,Moonbit的语法将会更加稳定,重心会逐步放到性能提升以及生态建设等方面。从本次开始,Moonbit的改动将会以每月一版的节奏发布月报。但月报的主要内容仍以语言,标准库和工具链的更新为主。

语言更新

  1. 支持!expr语法。对布尔表达式取反现在可以直接使用!符号,不一定要使用not函数。
fn true_or_false(cond: Bool) -> Unit {
  if !cond {
    println("false branch")
  } else {
    println("true branch")
  }
}

fn main {
  true_or_false(true)  // true branch
  true_or_false(false) // false branch
}
  1. try .. catch .. else .. 语法中的 else 关键字被替换为 noraise,原因是 try .. catch .. else .. 中的 else 后是模式匹配而非代码块,和其他地方的 else 不一致。旧的写法将被废弃,编译器会提出警告

  2. 允许函数返回值标记 noraise,一方面可以使类型签名中提供更清晰的文档信息,另一方可以用于防止在一些情况下编译器自动插入 raise 标记,比如:

fn h(f: () -> Int raise) -> Int { ... }

fn init {
  let _ = h(fn () { 42 }) // ok
  let _ = h(fn () noraise { 42 }) // not ok
}
  1. 允许了 ... 对模式匹配中的代码进行省略,比如:
fn f(x: Int) -> Unit {
  match x {
    ...
  }
}

工具链更新

  1. 更加强大的代码覆盖率测试,现在,你可以使用moon coverage analyze命令直接得到代码中没有被使用到的行。例如
fn coverage_test(i : Int) -> String {
  match i {
    0 => "zero"
    1 => "one"
    2 => "two"
    3 => "three"
    4 => "four"
    _ => "other"
  }
}

test "coverage test" {
  assert_eq(coverage_test(0), "zero")
  assert_eq(coverage_test(1), "one")
  assert_eq(coverage_test(2), "two")
  // assert_eq(coverage_test(3), "three")
  assert_eq(coverage_test(4), "four")
  assert_eq(coverage_test(5), "other")
}

上述代码运行moon coverage analyze后,会首先运行测试,然后将测试运行过程中没有覆盖到的行给打印出来,如下所示:

 moon coverage analyze
Total tests: 1, passed: 1, failed: 0.

warning: this line has no test coverage
 --> main/main.mbt:6
4 |     1 => "one"
5 |     2 => "two"
6 |     3 => "three"
  |     ^^^^^^^^^^^^
7 |     4 => "four"
8 |     _ => "other"

这一工具对指导测试会有很大帮助。

标准库更新

  • 提醒:下个版本中 JSON 数据定义将会发生变化,请不要直接使用构造器,改用 Json::number 等函数进行构造

2025-06-16

· 阅读需 7 分钟

语言更新

1、用于表示错误的 ! 语法被替换为关键字 raise

  • 用于表示错误的 ! 语法被替换为关键字 raise,具体的对应如下:

    • (..) -> T ! SomeErr => (..) -> T raise SomeErr
    • (..) -> T ! => (..) -> T raise
    • (..) -> T ? Error => (..) -> T raise?(这是近期新增的错误多态语法,不了解可以略过)
    • fn f!(..) { .. } => fn f(..) raise { .. }
    • fn!( ..) { .. } => fn (..) raise { .. }

    上述改动都可以通过格式化代码自动完成迁移

2、定义错误类型的语法  type! T .. 改为 suberror T ..

  • 定义错误类型的语法 type! T .. 改为 suberror T ..。这一改动可以通过格式化代码自动完成迁移

3、f!(..)/ f?(..) 废弃警告及迁移注意事项

  • f!(..)f?(..) 语法被废弃,继续使用它们会收到编译器的警告。格式化代码能够自动去掉 ! 完成迁移,但 f?(..) 需要手动迁移至 try?。因为对于原先的 f?(g!(..)) 这种情况,简单改成 try? f(g(..)) 会改变语义,使 g 中的错误也被捕获。在手动迁移 f?(..)时,也需要特别注意这种情况

4、函数类型参数语法更新:fn f[..](..) 改为fn[..] f(..)

  • 数周前,函数定义的类型参数的位置从 fn f[..](..) 改为 fn[..] f(..),和 impl 保持一致。现在,旧的写法被废弃并会收到编译器警告。这一改动可以通过格式化代码自动迁移

5、typealiastraitalias语法更新:改用 as 替代 =

  • typealiastraitalias的语法进行了简化,typealias A = Btraitalias A = B 这两种写法被废弃,应改为使用 typealias B as Atraitalias B as A。复杂的 typealias,例如 typealias Matrix[X] = Array[Array[X]],应改为 typealias Array[Array[X]] as Matrix[X]。这一改动可以通过格式化代码自动迁移

6、废弃多参数 loop,改用元组参数以保持与 match 一致

  • 多参数的 loop 语法被废弃,应改为使用以元组为参数的 loop。这一改动让 loopmatch 更一致。MoonBit 编译器在 release 模式下能够通过优化消除掉 loop 中元组的开销,因此无需担心这一改动带来性能问题

7、显式实现特征(Trait)新规:即使有默认方法也需impl

  • 对于那些 “每一个方法都有默认实现” 的特征(trait),之前,所有类型都会自动实现它们。但现在,即使一个特征的所有方法都有默认实现,也依然需要显式实现。如果没有需要提供自定义实现的方法,可以用impl Trait for Type 来表示 “给 Type 实现 Trait,但所有方法都用默认实现”。impl Trait for Type 也可以作为文档/TODO 使用,MoonBit 在看到这种声明时,会自动检查 Type 是否实现了 Trait,如果没有实现就报错

8、废弃外部类型 impl 的点调用,改用本地方法扩展

  • 之前,给外部类型的 impl 可以在当前包内用 . 调用。但这一功能是不重构安全的:上游新增方法会改变下游代码的行为。因此,我们决定废弃这一行为。作为替代,MoonBit 支持了局部地给外部类型定义新方法的功能,语法和普通的方法定义一样。这些给外部类型定义的方法有如下特点:

    • 它们不能是 pub 的。这是为了保证跨包协作时不会产生冲突
    • 如果上游(类型自身所在的包)已经定义了同名方法,编译器会报一个警告
    • 在解析方法调用时,本地方法的优先级最高

    这一修改之后,x.f(..) 的解析规则变为(优先级从高到低):

    • 本地的方法
    • x 的类型所在的包的方法
    • x 的类型所在的包的 impl

9、Json字面量自动调用ToJson::to_json,编写更便捷

  • Json 字面量内部,编译器会自动给不是字面量的表达式插入 ToJson::to_json 调用,写 Json 字面量时会更便捷:
let x = 42
// 之前
let _ : Json = { "x": x.to_json() }
// 现在
let _ : Json = { "x": x }

10、虚拟包支持抽象类型:接口声明,多实现可自定义类型

  • 虚拟包(virtual package)功能支持了抽象类型。可以在 .mbti 接口中声明抽象类型,并且不同的实现可以用不同的实际类型来实现接口中的抽象类型

11、try可省略:简单表达式错误处理更简洁

  • 在处理简单表达式中的错误时,try 可以省略,直接写 f(..) catch { .. } 即可

12、新增保留字警告:未来可能成为关键字

  • 新增了一批保留字,它们目前不是关键字,但在未来可能成为关键字。如果在代码中使用这些名字,编译器会提出警告

下列改动目前尚未发布,将在 6.18 MoonBit beta release 之前发布

  • 新增了箭头函数语法 (..) => expr,能极大简化简单匿名函数:
test {
  let arr = [ 1, 2, 3 ]
  arr
  .map(x => x + 1) // 只有一个参数时可以省略括号
  .iter2()
  .each((i, x) => println("\{i}: \{x}"))
}
  • 矩阵函数功能被废弃,以精简语法。形如 fn { .. => expr } 的矩阵函数可以改为箭头函数,其他矩阵函数应改为显式的 fnmatch
  • 之前,可以使用 xx._ 语法来将 new type 转化为其实际表示。但这一语法和 partial application 语法(_.f(..))过于相似,有视觉歧义。因此,xx._ 语法被废弃,相应的,编译器会给每个 new type 自动生成一个 .inner() 方法,代替原本的 ._。这一改动可以通过格式化代码自动完成迁移
  • 对于一些比较模糊/不够广为人知的运算符优先级组合,例如 <<+,MoonBit 现在会产生警告。手动或者通过格式化代码加上括号来明确计算顺序即可消除警告
  • 新引入了 letrecand 关键字用于声明 local 互递归函数,比如:
fn main {
  letrec even = fn (x: Int) { ... } // anonymous function
  and odd = x => ...                // arrow function
}

等号右手侧只能是函数形式的值,比如匿名函数或者箭头函数,之前使用 fn 声明的隐式互递归写法会被 deprecated,不过自递归函数依然可以用 fn 进行声明。

  • fnalias 不再能用于创建非函数值的别名。对于非函数类型的值,可以用 let 来创建别名

标准库更新

1、错误多态支持:高阶函数现可接受带错误的回调

  • 利用新的错误多态功能,标准库中的许多高阶函数如 Array::each 现在可以接受带错误的回调函数了

工具链更新

  • main 包中支持写测试。moon test 会运行 main 包中的测试,moon run 则会运行 main 函数

  • IDE codelens 支持运行文档中的测试

  • moon testmoon check 现在默认会包含文档中的测试 经过深度打磨与社区反馈的持续优化,

MoonBit Beta 版本将于6月18日正式发布,迈入语言稳定阶段,因此 moonbit 双周报改为月报,请各位用户持续关注本专栏内容。