跳到主要内容

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

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