跳到主要内容

20251202 MoonBit 月报 Vol.06

· 阅读需 9 分钟

对应moonc版本:v0.6.33

语言更新​

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

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

    fn main {
      let xs : FixedArray[Byte] = [0x80, 0, 0, 0]
      match xs {
        [i8be(i), ..] => println(i) // prints -128 because 0x80 is treated as signed 8-bit int
        _ => println("error")
      }
    }
  • cascade 函数调用改进 以前在x..f()..g()这种形式的函数调用中,要求f的返回类型必须是Unit。现在解除了这个限制,当f会返回一个Unit以外类型的值时,将触发invalid_cascade警告,在运行时这个返回值会被隐式地丢弃:

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

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

  • 语法解析改进

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

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

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

  • #label_migration 属性

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

    • 一是可以给同一个参数两个不同的 label,
    • 二是当额外提供 msg 的时候可以用于 deprecate 某个参数 label:
    #label_migration(x, alias=xx)
    #label_migration(y, alias=yy, msg="deprecate yy label")
    fn f(x~ : Int, y? : Int = 42) -> Unit { ... }
    
    ///|
    fn main {
      f(x=1, y=2)   // ok
      f(xx=1, yy=2) // warning: deprecate yy label
      f(x=1)        // ok
    }
  • #deprecated 默认行为改进

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

  • warnings 和 alerts 改进

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

    • #warnings 属性支持

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

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

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

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

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

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

  • Lexmatch 改进

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

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

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

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

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

import { dirname } from "path";

工具链更新​

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

  • 构建系统改进

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

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

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

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

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

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

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

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

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

  • moon fmt改进

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

标准库和实验库更新​

  • 弃用Container::of函数

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

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

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

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

  • 字符串索引改进

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

  • moonbitlang/x/path 实验库改进

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

  • moonbitlang/async更新

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

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 { ... } 的语法用于统一 traitalias,fnalias,和简单的typealias,其具体语法如下:

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

该语法会创建重名别名,使得在当前包内可直接使用 value,CONST,Type,Trait 引用 @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

总体来说,将来我们会彻底删除 traitalias,fnalias 和 typealias,并用 using,type 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]

  • le 和 be 的后缀是要写明的,所有 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 test 与 async fn main 语法, 支持异步测试与异步主函数。async fn main 和 async 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表达式(实验性特性)。提供了用正则表达式对StringView或BytesView进行模式匹配的能力。下面这个例子匹配2到4个连续的字符'a'以及紧随其后的字符'b', 并将连续的'a'捕获为变量a。更具体的使用方式可参考 moonbitlang/parser 中的 lexer.mbt 和 moonbit-evolution 中的 lexmatch 提案。

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

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

      另外,也可以使用 pub using 实现 re-export 的效果,将其他包的定义在当前包重新导出。未来,fnalias、traitalias 和简单 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 expr 的 expr 里不能抛出错误或调用 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 的形式调用,语义是:如果 opt 是 Some(value),等价于 label=value。如果 opt 是 None,等价于不提供这个参数

  • 调整自动填充参数的语法,改用 #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的可选项为legacy和flat。后者简化了表示,适用于@json.inspect等场景。目前所有 enum 都必须选择其中一个 style 使用。

    • 如果需要自定义 JSON 的格式,请自行实现 FromJson 和 ToJson 两个 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、typealias和traitalias语法更新:改用 as 替代 =​

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

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

  • 多参数的 loop 语法被废弃,应改为使用以元组为参数的 loop。这一改动让 loop 和 match 更一致。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 } 的矩阵函数可以改为箭头函数,其他矩阵函数应改为显式的 fn 和 match
  • 之前,可以使用 xx._ 语法来将 new type 转化为其实际表示。但这一语法和 partial application 语法(_.f(..))过于相似,有视觉歧义。因此,xx._ 语法被废弃,相应的,编译器会给每个 new type 自动生成一个 .inner() 方法,代替原本的 ._。这一改动可以通过格式化代码自动完成迁移
  • 对于一些比较模糊/不够广为人知的运算符优先级组合,例如 << 和 +,MoonBit 现在会产生警告。手动或者通过格式化代码加上括号来明确计算顺序即可消除警告
  • 新引入了 letrec 和 and 关键字用于声明 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 test 和 moon 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 中。这种情况需要先用 let 将 g 的结果提取出来,再使用 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​

标准库更新​

Char 的 Show::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
    [] => ()
  }
}
  • 支持了从 Array 到 ArrayView 类型、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。