跳到主要内容

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 双周报改为月报,请各位用户持续关注本专栏内容。

2025-06-03

· 阅读需 4 分钟

语言更新

1. 调用副作用函数时可省略 !,IDE 自动高亮错误与异步

  • 调用带副作用的函数(抛出错误或异步)时的 ! 现在可以省略,IDE 的语义着色会自动给带错误的函数加上下划线,将异步函数标记为斜体

2. f?(..) 替换为更灵活的 try? 表达式语法

f?(..) 语法被替代为一个新语法 try? f(..)try? expr 等价于 try Ok(expr) catch { err => Err(err) },可以把错误转换为Result类型。相比原来的 f?(..)try? 具有更多的灵活性,它可以同时处理多处函数调用的错误。

例如

try? {
  f(..)
  g(..)
}

在迁移时,需要特别注意一种情况:f?(g!(..)) 不能简单翻译为 try? f(g(..)),因为这会导致 g 中的错误被重定向到 Result 中。这种情况需要先用 letg 的结果提取出来,再使用 try?

3. 新增了 “错误多态” 功能,允许高阶函数同时处理带错误和不带错误的参数:

fn[X] Array::each(arr : Array[X], f : (X) -> Unit?Error) -> Unit?Error {
  for x in arr {
    f(x)
  }
}

fn main {
  let arr = [ "a", "b", "c" ]
  arr.each(println) // 无错误
  println(try? arr.each(fail)) // 输出 Err(Failure)
}

错误多态的语法是 (..) -> T?Error,在调用时,?Error 可以被替换为任意错误类型,也可以被抹去,变成没有错误的情况。例如,在上面的 each 函数中,如果参数 f 没有错误,那么 ?Error 会被替换为 “没有错误”,整个 each 函数的调用也不会抛出错误。如果 f 会抛出错误,那么 ?Error 会被替换为 f 实际的错误类型,整个 each 函数的调用也会抛出同样的错误

4. 废弃 fn meth(self: T) 风格,统一使用 fn T::meth(..) 定义方法

之前,使用 fn meth(self : T) 形式定义的方法既是方法也是函数,可以用 meth(..)@pkg.meth(..) 的形式直接调用。这一 “将方法当作普通函数使用” 的行为将被废弃,目前编译器会对这一行为的使用提出警告。 这一调整后,我们鼓励的 API 设计方式是:

  • 永远使用 fn T::meth(..) 的形式定义方法,新代码中不再使用 fn meth(self : T)(在以后,fn meth(self : T) 语法本身也可能被废弃)
  • 和某个类型绑定的 API 若无特殊理由,都鼓励设计成方法

5. . 语法支持使用 _ 的匿名函数

写法是 _.meth(..),同时,管道运算符|>的右手侧也支持了这种形式的匿名函数。注意使用这一语法时,必须能从上下文中得知_的类型,否则无法解析方法。例子:

fn main {
  let array_of_array = [ [], [ 1 ], [ 1, 2 ] ]
  let lens = array_of_array.map(_.length())
  lens |> _.each(println)
}

和直接写 x.meth(..) 相比,x |> _.meth(..) 的好处是,它可以把方法嵌入到一个现有的 |> 管线里,例如 x |> regular_func(..) |> _.meth(..)

6. 方法定义中支持用 Self 替代类型名,简化签名

fn TypeName::meth(..) 形式的方法声明中,可以使用 Self 来指代 TypeName,缩短类型签名的长度:

type MyLongTypeName Int

fn MyLongTypeName::to_string(x : Self) -> String {
  x._.to_string()
}

如果 TypeName 有参数,使用 Self 时也需要提供参数

7. 使用方法实现 trait 的行为被正式移除

8. 函数的类型参数的位置从 fn f[..] 调整为 fn[..] f,从而和 impl 保持一致。这一改动可以使用格式化工具自动迁移

9. moon info 生成的 .mbti 文件中,方法的格式发生了变动。之前方法会被合并到一个大的 impl 块中,现在方法在 .mbti 中会被显示为一个扁平的列表,和 MoonBit 自身的语法保持一致

10. 异步函数的语法调整回了 async (..) -> T!Async 的语法由于和错误多态语法不兼容被废弃。这一改动可以使用格式化工具自动迁移

11. 增加了Float类型的字面量3.14F

标准库更新

CharShow::output 实现发生变更,现在对于所有不可打印字符会进行转译,包括 Control、Format、Surrogate、Private Use、Unassigned、Separator(除空格)等

工具链更新

单个 .mbt.md 文件支持外部依赖,用法如下:

---
moonbit:
  deps:
    moonbitlang/x: 0.4.23
    # moonbitlang/x:
    #   path: "/Users/flash/projects/x"  # local deps
  backend:
    js
---

2025-05-19

· 阅读需 2 分钟

语言更新

x..f(..) 的语义即将发生改变,在 ./.. 调用链末尾的最后一个 .. 以后会自动丢弃它的值。因此,下面的代码:

impl[X : Show, Y : Show] Show for (X, Y) with output(self, logger) {
  logger
  ..write_string("(")
  ..write_object(self.0)
  ..write_string(", ")
  ..write_object(self.1)
  // 原本,这里必须写 `.`,否则整个 `.` 链的类型是 `&Logger`,不符合预期类型 `Unit`
  .write_string(")")
}

以后可以简化成

impl[X : Show, Y : Show] Show for (X, Y) with output(self, logger) {
  logger
  ..write_string("(")
  ..write_object(self.0)
  ..write_string(", ")
  ..write_object(self.1)
  // 以后可以直接一路 `..` 到底了
  ..write_string(")")
}

但这也意味着直接使用 x..f() 的值的用法将会被废弃,需要显式保存x。例如,下面的代码:

let arr = []..push(1)..push(2)

需要改写成:

let arr = []
arr..push(1)..push(2)

枚举构造器和结构体的字段支持单独的文档注释,在补全时会显示相应的文档。

///| Location enum
struct Location {
  /// X coordinate
  x : Int
  /// y coordinate
  y : Int
}

///| Variant enum
enum Variant {
  /// Stirng constructor
  String(String)
  /// Number constructor
  Number(Double)
}

@bytes.View@string.View 在 C 和 wasm1 后端现在会被编译成值类型,这意味着这两个类型不会引入内存分配,性能有较大提升

工具链更新

  • vscode 插件支持semantic token, 会对有effect的函数(会抛出异常的函数, 异步函数)调用使用不同的样式高亮.

  • 构建系统支持 virtual package 特性,通过将一个 package 声明为虚拟包,定义好一套接口,用户可选择具体使用哪一份实现,如不指定则使用该虚拟包的默认实现。通过这项特性,给分离接口与实现带来较大灵活性。注意:目前这项特性处于实验性状态。详情请查看:MoonBit 新特性:Virtual Package 虚拟包机制

  • 支持对于单个 .mbt 和 .mbt.md 文件的 test 和 debug codelen

2025-05-06

· 阅读需 2 分钟

语言更新

  • 【Breaking Change】Trait 的实现方式将发生改动,未来将只支持通过 impl T for A ... 对类型 A 显式实现 trait T;仅对 A 实现同签名的方法不再视为对 A 实现了 trait T。该改动之前已在编译器中提供警告,近期将会正式生效。

  • 新增语法糖,允许使用 _ 作为待定参数占位符以简化匿名函数的创建,如 f(a, _, c, _) 等效于 fn(b, d) { f(a, b, c, d) }。目前支持的使用场景有:

    • Constructor(args, _), lhs |> Constructor(args, _)
    • function(args, _), lhs |> function(args, _)
    • object.method(args, _)(暂不支持 _.method(args)
  • fnalias 支持给类型和 trait 的方法创建别名:

trait Floating {
  sin(Self) -> Self
  cos(Self) -> Self
}

// 接下来可以直接使用 `sin` 和 `cos`,不需要加 `Floating::`
pub fnalias Floating::(sin, cos)
  • 移除了所有 pragmas,未来将全面使用 attributes 替代。

  • 实现了 #internal attribute,用于为 public API 的外部用户提供警告:

/// in moonbitlang/core
#internal(unsafe, "message")
pub fn unsafe_get(args){...}

/// in user/module
fn main {
  unsafe_get(...) // warning!
}

用户可以在 moon.pkg.json 中通过配置 alert 选项来关闭这些警告。

  • 对于 loop 中可能产生歧义的 loop argument 的使用方式新增了警告:
fn main {
  let a = "asdf"
  loop a {
    [k, .. b] => continue a // warning
    [k, .. b] as a => continue a // suggested
    [] => ()
  }
}
  • 支持了从 ArrayArrayView 类型、Bytes@bytes.View 类型的隐式类型转换。

工具链更新

  • moon 支持 bench 子命令,用于执行基准性能测试。

    使用带 b : @bench.T 参数的 test 块创建基准测试。可对计算结果使用 b.keep() 防止无副作用的计算被编译优化移除:

    fn fib(n : Int) -> Int {
      if n < 2 {
        return n
      }
      return fib(n - 1) + fib(n - 2)
    }
    
    test (b : @bench.T) {
      b.bench(fn() { b.keep(fib(20)) })
    }

    使用 moon bench 运行基准测试:

    $ moon bench
    [..]
    time (mean ± σ)         range (min max)
      21.67 µs ±   0.54 µs    21.28 µs  23.14 µs  in 10 ×   4619 runs

    更详细的使用说明参见 https://docs.moonbitlang.com/zh-cn/latest/language/benchmarks.html

2025-04-21

· 阅读需 4 分钟

语言更新

  • async 函数的调用处语法改为和 error 相同的 f!(..),原语法 f!!(..) 将触发警告

  • 运算符重载的语义从基于方法迁移到了基于 trait,以后重载运算符需要通过给 @moonbitlang/core/builtin 中对应的 trait 添加 impl 的形式。各个运算符对应的 trait 可以参考语言文档和 @moonbitlang/core/builtin 中的 operators.mbt。迁移上:

    • 使用方法重载运算符的旧方式依然可用,但编译器会对此提出警告
    • op_xxx 系列方法改成对应 traitimpl 即可完成迁移
      • trait 对运算符的签名会有更严格的要求,例如 - 运算符的返回类型和参数类型必须相等。如果有签名不符合要求的运算符,这些 API 之后将不再能以运算符的形式提供,只能以普通方法的形式提供
    • 如果在 trait 中定义了 op_xxx 方法,可以删去这些方法,并将运算符对应的 trait 添加到 super trait 列表中,例如:
    // 旧的写法
    trait Number {
      from_int(Int) -> Self
      op_add(Self, Self) -> Self
      op_sub(Self, Self) -> Self
    }
    
    // 迁移后的新写法
    trait Number : Add + Sub {
      from_int(Int) -> Self
    }
  • trait 定义中,在方法声明后新增了一个 = _ 的标记,用于标记 trait 中的某个方法是否有默认实现,例如:

    trait Hash {
      hash_combine(Self, Hasher) -> Unit
      hash(Self) -> Int = _ // 说明 `hash` 有默认实现
    }

    a. 如果一个方法有 = _ 的标记,它必须有对应的默认实现 impl Trait with some_method(..)。反之,如果一个方法有默认实现但没有 = _ 标记,编译器会提出警告。

    b. 这一新增的标记主要是为了提升源码的可读性。现在,从 trait 的定义即可直接看出各个方法是否有默认实现。那么,为什么不直接把默认实现的内容放到 trait 定义里呢?这是因为我们希望 trait 的定义本身尽可能短,这样在阅读时更容易完整获取方法列表和它们的类型签名信息。

  • 提供了从 String 类型到 @string.View 的隐式类型转换,并且恢复了使用 [:] 操作符来取一个完整的 view,对于通用的使用 [i:j] 取 view 的情况目前还在设计中。

    fn f(v : @string.View) -> Unit {
      ignore(v)
    }
    
    fn main {
      f("hello")
      f("world"[:])
    }
  • @string.View/@bytes.View 进行模式匹配时,允许直接匹配 string/bytes literal,如:

    test {
      let s = "String"
      inspect!(s.view(end_offset=3) is "Str", content="true")
      let s : Bytes = "String"
      inspect!(s[:3] is "Str", content="true")
    }
  • 【Breaking Change】Core 中的 @string 包 API 发生改动,参数类型由 String 迁移至 @string.View,返回值类型根据情况调整为了 @string.View,比较有代表性的改动如下:

旧方法签名新方法签名
self.replace(old~: String, new~: String) -> Stringself.replace(old~: View, new~: View) -> String
self.trim(charset: String) -> Stringself.trim(charset: View) -> View
self.split(substr: String) -> Iter[String]self.split(substr: View) -> Iter[View]
self.index_of(substr: String, from~: Int) -> Intself.find(substr: View) -> Option[Int]
self.last_index_of(substr: String, from~: Int) -> Intself.rev_find(substr: View) -> Option[Int]
self.starts_with(substr: String) -> Boolself.has_prefix(substr: View) -> Bool
self.ends_with(substr: String) -> Boolself.has_suffix(substr: View) -> Bool
  • Core 中的 Json 类型未来将改为 readonly,届时将不能使用该类型的 enum constructor,但作为替代提供了对应的辅助函数,如:
test {
  let num = Json::number(3.14)
  let str = Json::string("Hello")
  let obj = Json::object({ "hello": num, "world": str })
}

工具链更新

  • IDE 停止支持 moonbit: true Markdown 文件头
    • 现在只有 .mbt.md 拓展名会触发 Markdown 文件的 MoonBit IDE 支持
  • IDE 支持在 .mbt.md 中直接设置 debug 断点
    • 今后不再需要在 VSCode 设置中开启 Debug: Allow Breakpoint Everywhere 选项
  • moon.mod.json 中添加构建脚本 scripts 字段
    • 目前支持 postadd 脚本:如果一个模块中包含 postadd 字段,那么执行 moon add 之后会自动执行该脚本
      • 设置 MOON_IGNORE_POSTADD 环境变量可以忽略 postadd 脚本的执行
{
  "scripts": {
    "postadd": "python3 build.py"
  }
}
  • 优化 moon 工具的 .mbt.md 格式 Markdown 支持
    • moon check 命令执行时自动包含 Markdown 检查
    • moon test 命令执行时自动包含 Markdown 测试(未来该命令的 --md 选项将被移除)

2025-04-07

· 阅读需 2 分钟

语言更新

wasm 后端:支持将 extern type t 存储到数据结构中

现在 wasm 后端支持将 extern type t 存储到数组等数据结构中。在FFI边界(导入/导出函数的签名)上,extern type T 依然会被编译成wasm的 externref 类型。

C FFI支持 borrow

C FFI支持 borrow。现在,你可以通过在 extern "c" 的函数上方指定 #borrow(args, ...) 来修改C FFI对指定参数的生命管理方式,其中 arg 是C FFI的参数名字的一个子集。默认情况下,C FFI需要负责把参数释放掉,这意味着,绑定C FFI时,往往需要写一个辅助函数来释放掉参数:

fn open(path : Bytes, flags : Int, mode : Int) -> Int = "open"
int open_wrapper(moonbit_bytes_t path, int flags, int mode) {
  int rc = open(path, flags, mode);
  moonbit_decref(path)
  return rc;
}

使用 borrow attribute,我们可以指示MoonBit对C FFI函数调用不生成引用计数指令,从而不用再写辅助函数,可以直接绑定C库中的函数:

#borrow(path)
fn open(path : Bytes, flags : Int, mode : Int) -> Int = "open"

由于 #borrow 标记,MoonBit会自动在调用完 open 后释放掉 path

typetrait 支持了 #deprecated attribute

typetrait 支持了 #deprecated attribute。我们下次发布将移除旧的pragmas机制,建议使用attribute替代:

/// the @alert pragmas is deprecated
/// @alert deprecated "message"
fn f() -> Unit {...}

/// use attribute #deprecated instead
#deprecated("message")
fn f() -> Unit {...}

对于FFI的extern函数的声明进行后端一致性检查

对于FFI的extern函数的声明添加了后端一致性的检查,例如下列函数会在非Native后端构建的时候报错。

extern "c" fn open(path : Bytes, flags : Int, mode : Int) -> Int = "open"

工具链更新

  1. 从本周开始,工具链的发布从周一改到周四。

  2. 修复了test explorer的bug,并新增了对 .mbt.md 的测试和调试支持:

md.png

另外,可以通过开启以下设置来允许在Markdown文件中设置断点:Settings > Debug: Allow Breakpoint Everywhere

setting.png

  1. moon info --package 支持模糊匹配包名。