跳到主要内容

2025-03-24

· 阅读需 10 分钟

语言更新

Bytes 现在可以使用 array pattern 进行模式匹配

fn main {
let bytes : Bytes = "Hello, world!";
match bytes {
[..b"Hello", ..] => {
println("Starts with \"Hello\"");
}
_ => {
println("Doesn't start with \"Hello\"");
}
}
}

字符字面量使用为 IntByte

现在字符(Char)字面量可以在接受一个 Int 值的地方被使用,语义是该字符对应的 Unicode code point 。同时,字符字面量也可以在接受一个 Byte 值的地方被使用,并且如果对应的 Unicode code point 超过 Byte 对应的范围会报错。

fn main {
let a : Int = 'a';
println(a) // 97
let b : Byte = 'a';
println(b) // b'\x61'
let c : Byte = '🎉';
// ^^^ Error: Char literal '🎉' for Byte is out of range.
}

调整字符串字面量和 Bytes 字面量中的转义序列

由于\x..\o..等转义在不同上下文中(如String类型或Bytes类型)的解释存在二义性,我们进行了一些调整:

  • 在类型为String的位置使用的字符串字面量中,\xFF\o377 这两种转义被弃用。建议使用含义更明确的\u00FF\u{FF}Bytes字面量和重载到Bytes类型的字符串字面量不受影响,例如:
let bytes1 : Bytes = "\xFF\o377" // ok
let bytes2 = b"\xFF\o377" // ok, bytes2 == bytes1
let str : String = "\xFF\o377" // warning: deprecated escape sequences
  • 弃用了对 UTF-16 surrogate pair 的支持,例如\uD835\uDD04 。对于超出BMP code points的字符,使用\u{...}

  • 弃用了Bytes字面量b"..."或重载到Bytes类型的字符串字面量 "..." 中的unicode转义序列

let bytes1 = b"\u4E2D"          // deprecated, use b"\xE4\xB8\xAD" instead
let bytes2 = ("\u4E2D" : Bytes) // use ("\xE4\xB8\xAD" : Bytes) instead

trait 运算符重载

现在运算符重载不再是通过给类型上加上 op_add/op_mul/... 等方法来实现,而是通过实现 Add/Mul等标准库(core)里面的特性(trait)来实现。以下是一张运算符和特性的对应表:

运算符特性(Trait
==Eq
+Add
-Sub
*Mul
/Div
-(前缀)Neg
%Mod
&BitAnd
|BitOr
^BitXOr
<<Shl
>>Shr

如果你的代码有自定义运算符重载,那么应当将其从方法定义改为对应 traitimpl。未来,继续使用方法来重载运算符将会导致警告。在我们正式移除用方法实现 trait 的行为之后,用方法来重载运算符将导致编译错误。

如果你的代码中定义了一些包含运算符的 trait,那么应当 trait 中的运算符改为对运算符对应的 trait 的 super trait 声明。例如:

trait Number {
op_add(Self, Self) -> Self
op_mul(Self, Self) -> Self
literal(Int) -> Self
}

应该被修改为:

trait Number : Add + Mul {
literal(Int) -> Self
}

增加函数别名

语法为 fnalias <old_fn_name> as <new_fn_name>。函数别名可以帮助用户更加方便地使用包里面的函数,也有助于在包级别的重构中对于函数在包之间移动的处理。

fnalias @hashmap.new // 等价于 fnalias @hashmap.new as new

fn f() -> Int {
new().length()
}

fnalias f as g

fn main {
println("f: \{f()}")
println("g: \{g()}")
}

fnalias 也支持批量导入的语法 fnalias @pkg.(f1s as g1, f2 as g2, ..)

增加批量导入 typealias/traitalias 的语法

  • 可以通过 typealias @pkg.(A, B, C) 来批量导入类型。

  • 可以通过 traitalias @pkg.(D, E, F) 来批量导入特性(trait)。

    比如,在 lib 包中有两个类型定义 AB,各自有一个 new 方法。那么,在另一个包中就可以通过以下代码来将 @lib.A@lib.B 别名为当前包中的 AB

typealias @lib.(A, B)

fn main {
println(A::new())
println(B::new())
}

正式移除 type T 语法定义外部类型

正式移除了用 type T 语法定义外部类型的语义,绑定外部类型需要使用 extern type Ttype T 语法本身并未被移除,而是获得了不同的语义。extern type T 语法定义的类型是完全外部的类型,不参与 MoonBit 的垃圾回收。而 type T 语法定义的类型是普通的 MoonBit 类型,会参与垃圾回收。type T 的新语义配合本周新增的 C FFI external object 功能,可以实现对 FFI 外部的对象进行动态的管理和释放的效果。

C 侧的 FFI 可自定义析构函数(finalizer)

C 侧的 FFI 增加了自定义析构函数(finalizer)的功能。通过在 C 侧调用 moonbit_make_external_object,C 的 FFI 作者可以注册一个自定义的析构函数,用以释放和该对象相关的资源。以下是一个例子:

// MoonBit侧
type Greeting // 注意:不是 extern type

extern "c" fn Greeting::new() -> Greeting = "greeting_new"

fn main {
ignore(Greeting::new())
}
// C侧
#include "moonbit.h" // 记得将 $MOON_HOME/include 添加到 C 编译器的包括目录列表中
#include <stdlib.h>
#include <stdio.h>

char message[] = "Hello, World!";

struct greeting {
char *data;
};

void greeting_delete(void *object) {
fprintf(stderr, "greeting_delete\n");
free(((struct greeting*)object)->data);
// 不需要在这里释放 object 自身, object 自身会由 MoonBit 的引用计数系统释放。
}

struct greeting *greeting_new(void) {
char *data = malloc(sizeof(message));
/* moonbit_make_external_object(
void (*func_ptr)(void*),
int32_t size
)
其中:
- `func_ptr` 是一个函数指针,它负责释放对象中存储的资源
- `size` 是对象中的自定义数据的大小,单位是 byte
`moonbit_make_external_object` 会分配一个大小为
`size + sizeof(func_ptr)` 的 MoonBit 对象,并返回指向其数据的指针。
`func_ptr` 会被存储在对象的末尾,
因此返回值可以直接当成指向自定义数据的指针使用。
如果有其他接受 `struct greeting*` 的 C API,
可以直接将 MoonBit 中类型为 `Greeting` 的值传递给它们,无需进行转换
*/
struct greeting *greeting =
moonbit_make_external_object(&greeting_delete, sizeof(struct greeting));
greeting->data = data;
return greeting;
}

LLVM 后端已初步实现在调试器中打印局部变量值的功能

开发者使用 gdblldb 等调试工具时,可以查看基础数据类型(整型、浮点型)的局部变量值。针对字符串、数组及各类复合类型等其他数据结构的支持功能目前正在积极开发中。

构建系统更新

  • 我们在 Windows 平台上面的 bleeding 版本工具链提供了对 LLVM 后端的支持。Windows 用户可以如下方式来安装 bleeding 版本的工具链:
$env:MOONBIT_INSTALL_VERSION = "bleeding"; irm https://cli.moonbitlang.com/install/powershell.ps1 | iex
  • 现在发布的工具链会有对应的 dev 版本。dev 版本保留了更多的调试信息,能够更好地帮我们诊断编译器出现的问题和错误。可以通过如下方式安装 dev 版本的工具链:
# Unix (Linux or macOS)
curl https://cli.moonbitlang.com/install/unix.sh | MOONBIT_INSTALL_DEV=1 bash
# Windows (PowerShell)
$env:MOONBIT_INSTALL_DEV = 1; irm https://cli.moonbitlang.com/install/powershell.ps1 | iex
# moon
moon upgrade --dev

注意,目前dev版本的工具链并不支持 LLVM 后端。

  • 支持测试开启了 MoonBit 支持的 Markdown 文件。这些 Markdown 中的测试代码会作为黑盒测试运行。
moon test --md

IDE更新

  • IDE 中支持了 mbti 文件的语法高亮。

highlight.png

  • 为 IDE 中的 Codelens 增添了 emoji :

emoji.png

  • *.mbt.md 后缀的 Markdown 文件会开启 MoonBit LSP 支持(包括错误信息、补全等)。

2025-03-10

· 阅读需 6 分钟

语言更新

模式匹配支持守卫(Pattern Guard)

模式守卫可以通过在模式后追加 if ... 的语法结构来指定。有模式守卫的分支只有在被模式匹配的值满足对应模式,并且模式守卫为真的情况下才会执行。如果模式守卫为假,则会继续向下寻找能够被匹配的分支。

这个特性能够简化许多使用模式匹配的代码,以化简算术表达式为例:

fn simplify(e : Expr) -> Expr {
match e {
Add(e1, Lit(0)) => e1
Sub(e1, e2) if e1 == e2 => Lit(0)
// ^^^^^^^^^^^ pattern guard
_ => e
}
}

模式守卫还可以配合 is 表示式使用来引入新的变量,例如:

fn json_get_key(json : Json, key : String) -> Json! {
match json {
Object(map) if map[key] is Some(value) => value
_ => fail!("key not found: \{key}")
}
}

支持 Attribute 语法

支持 Attribute 语法,用来代替原有的 @alert deprecated "message" 等 pragmas。每个 attribute 单独占用一行,attribute 内不允许换行。

目前支持的attribute:

  1. #deprecated("message"):声明该函数为 deprecated ,并且在使用处提示 message 中的内容。
  2. #coverage.skip:声明该函数不计入覆盖率测试。 我们后续将会移除旧的 pragmas 语法。
#deprecated("use function g instead")
#coverage.skip
fn f() -> Unit {
...
}

Bytes 类型支持使用字符串字面量进行初始化和赋值

Bytes 类型支持使用字符串字面量进行初始化和赋值。该字符串会以 UTF-8 编码的形式存储为一个 Bytes 。例如:

fn main {
let xs : Bytes = "123"
let ys : Bytes = "你好,世界"
}

enum 支持自定义 tag 值

enum 支持自定义 tag 值,这在绑定 Native 的 C FFI 时非常有用。以 open 这个 syscall 为例:

enum OpenFlag {
O_RDONLY = 0x00
O_WRONLY = 0x01
O_RDWR = 0x02
}

extern "c" fn open(path : Bytes, flag : OpenFlag) -> Int = "open"

test {
let path : Bytes = "tmp.txt"
let fd = open(path, O_RDONLY)
}

增强了常量(const)声明的表达能力

const 新增支持:

  • 引用其他常量
  • 内建类型的其四则运算、位运算和比较运算

例如:

const A : Int = 1 + 2 * 3
const B : Int = A * 6

Deprecate 通过方法隐式实现 trait 的行为

通过方法隐式实现 trait 的行为现已 deprecate。如果需要为一个类型实现一个 trait ,需要通过显示的 impl 构造来实现。

// 为 T 隐式实现 Show (deprecated)
fn T::output(self : T, logger : &Logger) -> Unit {
...
}

// 你应该迁移到如下的显示实现
impl Show for T with output(Self : T, logger : &Logger) -> Unit {
...
}

移除了直接调用形如 fn T::f(..) 的行为

移除了直接调用形如 fn T::f(..) 的行为。该行为之前已通过警告的形式 deprecate。未来,形如 fn f(self : T, ..) 的方法可以当成普通函数使用,而形如 fn T::f(..)的方法只能用 T::f(..) 的形式或 x.f(..) 语法调用。新语义的更多细节见 GitHub PR #1472

单独拆分 Native 后端部分 runtime

Native 后端的一部分 runtime 拆出来到单独的 C 文件里面了。该 C 文件位于 $MOON_HOME/lib/runtime.c。如果你使用了非标准的构建方式,比如自己通过调用 C 编译器编译生成出来的 C 文件,需要注意在编译的时候加入该 C 文件。

在 bleeding 版本的工具链上面发布 LLVM 后端

我们在 bleeding 版本的工具链上面发布了我们的 LLVM 后端。目前我们的 LLVM 后端只支持了 x86_64 Linux 和 ARM64 macOS 平台。这两个平台上可以通过如下 bash 命令安装 bleeding 版本的 moon 。

curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s 'bleeding'

然后就可以在构建、测试和运行的时候,通过向 moon 传入 --target llvm 的选项来使用 LLVM 后端。例如:

moon build --target llvm

构建系统更新

  1. 新增 moon check --explain ,能够输出关于某个 error code 的详细信息。 explain

  2. moon.pkg.json中新增 "native-stub" 配置项用来声明当前包用到的 C stub 文件。在构建时,这些 stub 会被构建并连接到当前包的产物中。如果你的项目是把 C stub 文件写在 cc-flags 中,现在可以将这些 C stub 文件声明在 "native-stub" 字段中

  3. 放松了 moon publish 必须要通过 moon check 的要求。如果 check 失败,会询问用户是否坚持发布。

IDE 更新

  1. Markdown 中内嵌的 MoonBit 代码块支持格式化。用户可以通过选择 MoonBit 插件来格式化含有 MoonBit 的 Markdown 。

fmt

2025-02-24

· 阅读需 6 分钟

MoonBit 更新

String 模式匹配增强

  • 支持在 Array pattern 中使用字符串字面量

    在类型为 String 的 Array pattern 中可以通过..操作符来匹配一段字符串:

let str = "https://try.moonbitlang.com"
match str {
[.."https://", ..path] => println(path)
[.."http://", ..path] => println(path)
_ => println("unexpected protocol")
}

这相当于下面代码的简化版本:

 let str = "https://try.moonbitlang.com"
match str {
['h', 't', 't', 'p', 's', ':', '/', '/', .. path] => println(path)
['h', 't', 't', 'p', ':', '/', '/', .. path] => println(path)
_ => println("unexpected protocol")
}
  • 支持在 Array pattern 中使用 const 常量
const HTTP = "http://"
const HTTPS = "https://"

///|
fn main {
let str = "https://try.moonbitlang.com"
match str {
[..HTTP, .. path] => println(path)
[..HTTPS, .. path] => println(path)
_ => println("unexpected protocol")
}
}

while 的条件中支持使用 is 表达式

fn main {
let queue = @queue.of([1,2,3])
while queue.pop() is Some(n) {
println(n)
}
}
// 输出:
// 1
// 2
// 3

数组字面量重载支持String类型

数组字面量重载支持String类型。

C FFI 处理的 Breaking Change

[BREAKING CHANGE] C FFI 中,所有 MoonBit 对象改为指向第一个字段而非对象头。在 MoonBit 原生后端中,为了支持垃圾回收,每个对象都有一个对象头(定义在 moonbit.h 中)。之前,原生后端中,所有 MoonBit 对象都是一个指向对象头的指针。但这意味着在绑定 C FFI 时,很多函数都需要写一些 C wrapper 来跳过对象头、获取实际的数据。为了改善 C FFI 的体验,我们将所有 MoonBit 对象改为指向对象头尾部、第一个字段开始位置的指针。这样一来,在绑定 C FFI 时,就无需显式处理对象头了。例如,现在在 C FFI 中,可以直接用 Bytes 来表示 const char*void*,用 FixedArray[T] 来表示 T*,无需再写 C wrapper 了。

不过,这一改动是对 C FFI 的 breaking change:现有的、跳过对象头的 C wrapper 需要进行适配。因此,使用了 C FFI 的库或程序应当在本次编译器更新后进行修改。

新增 FuncRef[T] 类型用于 FFI

moonbitlang/builtin 中新增了一个特殊类型 FuncRef[T],表示类型为 T 的、无捕获的函数。该类型的用途是绑定一些需要传递回调函数的 FFI,例如 Unix signal handler。创建 FuncRef[T] 的方式是:在预期类型为 FuncRef 的地方写一个无捕获的匿名函数即可。FuncRef[T] 是用于绑定 FFI 的,因此它无法直接被调用。

外部类型语法更新

用于 FFI 的外部类型的语法改为 extern type T:之前,如果要在 MoonBit 里定义一个外部类型,语法是 type T。接下来,我们将把定义外部类型的语法改为更显式的 extern type T。旧的 type T 语法被 deprecate,编译器会对旧语法发出警告。本次语法迁移可以使用 moon fmt 自动完成。

未来旧的 type T 语法彻底移除后,我们将把 type T 语法另外用作其他用途。extern type T 表示的是一个完全外部的对象,MoonBit 的垃圾回收系统对它不会做任何处理。而未来 type T 将表示一些来自外部、但有正常 MoonBit 对象结构、会被垃圾回收处理的对象。例如有自定义 finalizer 的、来自 FFI 的对象。

移除旧的 Trait 对象语法

移除了旧的trait object语法。之前编译器已经对旧的 trait object 语法汇报了 warning,现在旧的 trait object 语法 Trait 正式被移除,trait object 类型应当写成 &Trait

构建系统更新

  • moon info 支持使用--package参数来指定要处理的包

  • moon.pkg.json配置文件增加supported-targets字段,用于指定当前包所支持的后端(若若设置则默认支持所有后端)。当尝试在不支持的后端中构建这个包或者依赖包支持的后端与当前包不一致时,构建系统将报错。

  • moon.pkg.json增加native-stub字段,用于声明这个包中需要一起构建并链接的.c文件。

IDE 更新

tourcn.png

  • 语言服务支持处理markdown中的moonbit代码。需要在markdown在最开始配置
---
moonbit: true
---

才会生效。

2025-02-10

· 阅读需 5 分钟

MoonBit更新

新增 is 表达式

  1. 这个表达式的语法形式为 expr is pat,这个表达式为 Bool 类型,当 expr 符合 pat 这个模式的时候返回 true,比如:
fn use_is_expr(x: Int?) -> Unit {
if x is Some(i) && i >= 10 { ... }
}
  1. Pattern 中可以引入新的 binder,这个 binder 可以以下两种情况中使用:
  • e1 && e2 中当 e1 是个 is 表达式的时候,其中通过 pattern 引入的 binder 可以在 e2 中使用

  • if e1 && e2 && ... { if_branch } else { ... }e1e2 等由 && 链接的判断中的 is 表达式引入的 binder,可以在 if_branch 这个分支中使用

String 的构造和模式匹配

  1. 新增了使用 array spread 的形式构造字符串,比如:
fn string_spread() -> Unit {
let s = "hello🤣😂😍"
let sv = s[1:6]
let str : String = ['x', ..s, ..sv, '😭']
println(str) // xhello🤣😂😍ello🤣😭
}

其中 array spread 中的单个元素为 Char 类型的值,可以使用 .. 插入 String 或者 @string.View 类型的一段字符串,这个表达式等价于使用 StringBuilder 构建字符串。

  1. 支持了使用 array pattern 对字符串进行模式匹配,并且允许其与 array literal pattern 进行混用,比如:
fn match_str(s: String) -> Unit {
match s {
"hello" => ... // string literal pattern
[ 'a' ..= 'z', .. ] => ... // array pattern
[ .., '😭' ] => ... // array pattern with unicode
_ => ...
}
}

新增编译器警告

  • 现在编译器会对无用的 guard 语句和 guard let ... else ... 中的 missing case 产生警告
fn main {
guard let (a, b) = (1, 2)
^^^^^^^^^ ----- useless guard let
println(a + b)
}

moonfmt 修复

  • 修复 moonfmt 处理 async 相关代码的格式化错误。调整 ///| 标记的插入规则。

相关包更新

  • moonbitlang/x/sys 包增加对native后端的支持。修复了这个实现在不同操作系统上行为不一致的问题。

  • 在 moonbitlang/x 中的 fs 包接口调整,增加了错误处理。

  • 字符串相关操作正在进行重新整理,string 包将会提供更多 unicode-safe 的 API,同时会 deprecated 一些暴露 UTF16 实现细节的 API,这期间 string 的方法将会变得不稳定,推荐使用 iter 方法或者模式匹配对来访问字符串中的元素

  • ArrayView/StringView/BytesView 这些类型从 @builtin 包中分别挪到了各自类型相关的包中,类型名相对应地改为了 @array.View/@string.View/@bytes.View

IDE 更新

  • 支持了自动补全模式匹配中 missing case 的 code action

  • 支持了空模式匹配中所有 case 的行内代码补全

  • 修复 trait method goto reference 的 bug 。

  • 修复了 guard let ... else ... 中引入的变量没有补全支持的问题,修复 else 分支模式匹配部分 pattern 的补全。

构建系统更新

  • 修复 moon test 在 native 后端跳过 panic test 时的 bug。

文档更新

2025-01-13

· 阅读需 6 分钟

语言更新

  • 实验性的异步支持。我们添加了实验性的异步编程支持。可以用 async fn ... 声明异步函数,用 f!!(...) 调用异步函数。同时 MoonBit 提供了一些原语用于中断控制流。更多信息见 docs/async-experimental

    目前异步标准库和事件循环仍在开发中,因此如果要使用异步编程功能,复用 JavaScript 后端的事件循环与 Promise API 会更轻松。目前异步编程相关功能的设计都是实验性质的,未来可能会根据反馈发生不兼容改动。我们欢迎并感激对异步编程功能的试用和反馈。

  • 在本周稍后,我们将对方法的语义进行一次大的调整,以简化方法相关的规则。目前,MoonBit 对方法的调用语法的规则是:

    • 方法可以用 fn f(self : T, ..)fn T::f(..) 的形式声明

    • 第一个参数为 Self 的方法,可以用 xx.f(..) 的形式调用

    • 如果没有歧义,可以用 f(..) 的形式直接调用方法 但上述最后一条规则比较混沌,且缺乏用户控制。此外,调用语法(f(..) 和声明语法(T::f(..))之间缺乏一致性。接下来,我们将对方法的语义进行调整:

    • 方法可以用 fn f(self : T, ..) 的形式声明。这种方法既可以用 xx.f(..) 的形式调用,也可以当成普通函数调用(f(..))。这种方法和普通函数处于同一个命名空间,不能重名

    • 方法也可以用 fn T::f(..) 的形式声明。这种方法可以用 xx.f(..) 的形式调用,也可以用 T::f(..) 的全名形式调用,但不能当作普通函数用 f(..) 的形式调用。这种方法可以和普通函数重名

    这一设计背后的直觉是:所有 fn f(..) 形式的定义都是普通函数,而所有 fn T::f(..) 形式的定义都会被放在 T 代表的一个小命名空间内。

    对于第一个参数不是 Self 的方法,例如 new,如果希望用 new(..) 的形式直接调用,可以将其直接定义为一个普通函数。在新的语义下,我们预期的库作者的 API 设计思路是:

    • 如果某个函数在当前包内没有歧义,就用 fn f(..) 的形式定义它。如果希望用 xx.f(..) 的方式调用它,就把第一个参数命名为 self

    • 如果某个函数在当前包内有歧义,就把它定义成 fn T::f(..),放进一个小命名空间里,来规避歧义。调用者必须用 T::f(..)xx.f(..) 的形式来调用。

  • 更多的alert支持,包括:

    • ⽀持对类型的使⽤触发 alert;

    • ⽀持对具体的constructor的使⽤触发 alert。

  • 对 ArrayView/BytesView/StringView 的构造和模式匹配进行了一些改进:

    • 允许了取 view 操作使⽤负数下标,比如:
fn main {
let arr = [1, 2, 3, 4, 5]
let arr_view = arr[-4: -1]
println(arr_view) // [2, 3, 4]
let arr_view_view = arr_view[-2:-1]
println(arr_view_view) // [3]
}
  • ⽀持了对 Bytesview 的模式匹配,并且允许 pattern ⾥⾯出现 byte literal,比如:
fn f(bs: BytesView) -> Option[(Byte, BytesView)] {
match bs {
[b'a' ..= b'z' as b, .. bs] => Some((b, bs))
_ => None
}
}

///|
fn main {
let s = b"hello"[:]
let r = f(s)
println(r) // Some((b'\x68', b"\x65\x6c\x6c\x6f"))
}
  • 在 array pattern ⾥⾯允许省略 as 关键字,可以写成 [a, .. rest, b],上面的例子中已经出现了这种用法,同时 [a, .. as rest, b] 这种写法也会继续支持,不过 formatter 会自动把中间的 as 关键字省略掉。

IDE 更新

  • 新增 toggle multi-line string 的 command。
  • Workspace symbols 增加了更多的信息,便于搜索某个具体的函数和类型。

构建系统更新

  • 修复 doc test 更新多行字符串测试结果的 bug。

文档更新

  • MoonBit Tour 支持 debug codelens,默认开启值追踪。

  • Moonbit OJ 用户可以在 oj 平台上刷题并兑换奖品。

oj.png

标准库更新

  • 改变了 Int64JSON 转换的行为。原本通过 Double 转换会损失精度,现改为通过字符串保存。

2024-12-30

· 阅读需 5 分钟

语言更新

  • 新增labeled loop语法,可在多层循环中直接跳转到指定的某一层,label使用 ~ 作为后缀
fn f[A](xs : ArrayView[A], ys : Iter[Int]) -> @immut/list.T[(Int, A)] {
l1~: loop 0, xs, @immut/list.Nil {
_, [], acc => acc
i, [x, .. as rest], acc =>
for j in ys {
if j == i {
continue l1~ i + 1, rest, @immut/list.Cons((j, x), acc)
}
if j + i == 7 {
break l1~ acc
}
} else {
continue i - 1, rest, acc
}
}
}
  • 新增discard argument, 以单个下划线命名的函数参数会被丢弃,同一函数内可以丢弃多个参数。Discard argument 只支持位置参数,暂不支持命名参数。
fn positional(a : Int, b : Int) -> Int {
a + b
}

fn discard_positional(_: Int, _: Int) -> Int {
1
}
  • pub 的语义正式从完全公开切换为只读,同时 pub(readonly) 语法被 deprecate。如果想要声明一个完全公开的 type/struct/enum,需要写 pub(all),如果想要声明一个完全公开(外部可以实现)的 trait,需要写 pub(open)。这一改动已提前通过 warning 的形式进行预告,之前使用 pub 会报 warning。如果已经按照 warning 将 pub 改为 pub(all)/pub(open),本次只需将 pub(readonly) 改为 pub 即可完成迁移。moon fmt 能自动完成 pub(readonly) -> pub 的迁移。

  • 未来,trait 找不到实现时 fallback 到方法的行为 有可能 被移除。我们鼓励新代码使用显式的 impl Trait for Type 语法而非方法来实现 trait。在无歧义时,impl Trait for Type with method(...) 也可以使用 dot syntax 调用,因此使用显式 impl 不会损失使用的便利性。

  • 移除了带标签参数旧的前缀语法。

  • 移除了用于获取 newtype 内容的旧语法 .0。同时如果一个 newtype 内的类型是 tuple,现在 .0.1 这些 index access 能自动转发到 newtype 内的 tuple,例如:

type Tuple (Int, String)

fn main {
let t = (4, "2")
println(t.0) // 4
println(t.1) // 2
}
  • derive(FromJson)derive(ToJson) 加入了参数支持,可以控制类型序列化和反序列化过程的具体行为和数据布局。具体修改见 Docs > Language > Deriving

    目前可以对字段进行重命名和调整 enum 的序列化格式。JSON 序列化与反序列化过程的具体行为在未来可能会进行优化而发生改变。

enum UntaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(untagged)), Show)
// { "0": 123 }, { "0": "str" }

enum InternallyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(tag="t")), Show)
// { "t": "C1", "0": 123 }

enum ExternallyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(ext_tagged)), Show)
// { "C1": { "0": 123 } }

enum AdjacentlyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(tag="t", contents="c")), Show)
// { "t": "C1", "c": { "0": 123 } }

struct FieldRenameAllCamel {
my_field : Int
} derive(ToJson(rename_all="camelCase", repr(tag="t")), Show)
// { "t": "fieldRenameAllCamel", "myField": 42 }

struct FieldRenameAllScreamingSnake {
my_field : Int
} derive(ToJson(rename_all="SCREAMING_SNAKE_CASE", repr(tag="t")), Show)
// { "t": "FIELD_RENAME_ALL_SCREAMING_SNAKE", "MY_FIELD": 42 }

IDE更新

  • 增加了对于loop label的gotodef/gotoref/rename 等功能的IDE 支持。

  • Document symbol现在在IDE中会以有层级的方式显示,效果如下: layer.png

  • 修复了白盒测试有关类型重命名的bug。

  • 新增了在会造成重复的情况下自动省略 parameter inlay hints 的功能。

  • IDE增加值追踪功能,点击 main 函数上面的 Trace codelen 后启用,再次点击后关闭;

    • 对于循环体内的变量,只显示最新的值以及 hit 次数。 trace.png

构建系统更新

  • moon新增 --no-strip 参数,可在 release 构建模式下保留符号信息

文档更新

  • 修复了 MoonBit Tour 在切换页面时 theme 无法保持的问题

  • MoonBit Tour增加 range、range pattern 和 newtype 小节

2024/12/16

· 阅读需 4 分钟

MoonBit更新

  • Trait object 语法由原来的直接写 Trait 变更为 &Trait (旧语法已deprecate)。这一改动是为了让 trait object 类型和 trait 本身在语法上区分开,以避免混淆。在所有涉及 trait object 的场合,包括类型标注、为 trait object 定义方法(fn &Trait::method(...))和创建 trait object(... as &Trait)都要进行修改。

  • 新增了 local types 语言特性,目前支持在一个 toplevel function 中的顶部声明只在当前 toplevel function 中可见的 struct/enum/newtype,并且可以通过 derive 来为这些 local types 添加新的方法,比如:

fn toplevel[T: Show](x: T) -> Unit {
enum LocalEnum {
A(T)
B(Int)
} derive(Show)
struct LocalStruct {
a: (String, T)
} derive(Show)
type LocalNewtype T derive(Show)
...
}

注意 local type 可以使用当前 toplevel function 中的泛型参数,但自身不可以引入额外的泛型参数,local type 可以使用 derive 生成相关方法,但不能额外定义其他新的方法,local type 暂不支持声明 error 类型。

IDE更新

  • 修复一些 LSP 相关的 bug。

    • 错误类型在 hover 的时候,定义的类型和 payload 的类型会连在⼀起;
    • 修复把⼀个单文件加到 module 里(在同级文件夹下创建moon.pkg.json)再移出(将 moon.pkg.json 删除)后,LSP 对该文件不提供服务的问题;
    • 修复了 test-import-all 配置时好时坏的问题;
    • 修复 LSP 奇怪 inlay hint 的 bug。
  • 给 LSP 的 formatter 开启了 block-line 的选项

  • LSP 支持 warn-list 的配置。

  • 优化了 web IDE 的 debug 体验,用户打开 devtools 后点击 debug 会直接停在 main 函数上。

debug.gif

  • 允许 doctest 中用 test 封装,支持更新 inspect 以及 panic test
/// ```
/// test "panic test xxx" {
/// panic()
/// }
/// ```
  • MoonBit AI 实现了生成时候切换模型,中止,重试的功能。

ai.gif

构建系统更新

  • moon run 跑测试时的参数传递调整,支持直接传递 --stack-size 调整 v8 栈大小。

  • 【breaking】黑盒测试过程中会自动导入被测试的包中的 public definition,比如在测试 @json 这个包的黑盒测试中再使用 @json 这个包里的函数或者类型时,就无须再写上 @json 前缀,而是可以直接使用。如果需要关闭这个特性,则需要在 moon.pkg.json 中写明 "test-import-all": false

文档更新

2024-12-02

· 阅读需 4 分钟

MoonBit更新

  • 添加了 range pattern 的支持,可以在模式匹配中匹配一个区间的整数值和字符。

Range pattern 的语法为 a..<b(不包含上界 b)或 a..=b(包含上界 b)。上下界可以是字面量、用 const 声明的常量,或是 _,表示在这一侧没有约束:

const Zero = 0
fn sign(x : Int) -> Int {
match x {
_..<Zero => -1
Zero => 0
Zero..<_ => 1
}
}

fn classify_char(c : Char) -> String {
match c {
'a'..='z' => "lowercase"
'A'..='Z' => "uppercase"
'0'..='9' => "digit"
_ => "other"
}
}
  • 允许用 x.f(...) 的语法调用 trait 的实现
trait I {
f(Self) -> Unit
}

type MyType Int
impl I for MyType with f(self) { println(self._) }

fn main {
let my : MyType = 42
my.f()// 输出 42
}

假设 x 的类型是 T,那么目前 x.f(...) 语法的解析规则如下:

  1. 如果 T 有方法 f,调用方法 T::f
  2. 如果在 T 所在的包内有 trait 实现 impl SomeTrait for T with f,那么调用 SomeTrait::f。如果有多个满足要求的 f,编译器会报一个歧义的错误
  3. 如果上述两条规则都没有找到 f,编译器会在当前包内搜索 impl SomeTrait for T with f。注意这一条规则只在当前包内适用,假如 T 不是当前包定义的,外部就无法用 . 调用 T 在当前包的 impl

这些规则在增加了 MoonBit 的 . 语法的灵活性的同时,维持了语义的清晰性和较好的重构安全性。解析 x.f(...) 时只会搜索 x 的类型 T 所在的包和当前包,而不是所有被引入的依赖。这保证了引入新的依赖不会使现有代码出错或改变行为。

  • 新增trait alias

没有引入新的语法,trait alias 的写法和 type alias 一样:

typealias MyShow = Show
impl MyShow for MyType with ...

IDE更新

  • Web IDE 新增更新 inspect 测试的功能。 try-inspect.gif

  • MoonBit AI 支持在已生成的代码中进行修改,修改后的代码会自动进行语法和类型检查。 ai-modify.gif

  • 修复文档注释中 markdown 代码块的高亮显示问题。

构建系统更新

  • moon check支持传递 warn list。

  • moon test支持运行项目文档注释中的测试。

    • 用法:moon test --doc,运行当前项目文档注释中的所有测试;
    • 测试必须标注在两行 ``` 之间, 如: moon-test.png
  • moon fmt 修复关于 local 函数、return 表达式的格式化。

  • moon fmt 修复数组下标语法附近的注释在格式化后错位的问题。

  • moon fmt 默认启用 -block-style

文档更新

2024-11-18

· 阅读需 6 分钟

MoonBit更新

  • trait 新增 abstract 和 pub(readonly) visibility
  1. 在当前包内,abstract/readonly trait 和一个完全公开的 trait 表现相同;
  2. 在当前包外不能直接调用 abstract trait 里的方法,且不能给 abstract trait 写新的实现;
  3. pub(readonly) 的 trait 在外部不能实现,但可以直接调用方法;
  4. trait 的默认可见性从私有变成 abstract。定义一个私有 trait 要写 priv trait
  5. 实现一个 abstract/readonly trait 时,如果想要外部也可以使用这个实现,至少有一个方法需要用 impl Trait for Type 的形式实现。

下面是一个 abstract trait 的例子:

trait Number {
add(Self, Self) -> Self
}

pub fn add[N : Number](x : N, y : N) -> N {
Number::add(x, y)
}

impl Number for Int with add(x, y) { x + y }
impl Number for Double with add(x, y) { x + y}

使用 moon info 生成 .mbti 接口文件,可以看到上述定义对外的接口是:

trait Number
impl Number for Int
impl Number for Double

fn add[N : Number](N, N) -> N

因此,外部只能对 IntDouble 调用 add 函数。编译器会保证 Number 在任何时候都只能被 IntDouble 实现。

  • 调整类型和 trait 的可见性

为了鼓励使用 readonly 的类型/trait、提供更健壮的 API,我们计划在未来将类型定义和 trait 的 pub 可见性的语义修改为默认 readonly。如果想要获得完全公开的可见性,类型定义需要写 pub(all) struct/enum/type,trait 需要写 pub(open) trait(表示这个 trait 可以接受外部的实现)。

目前,pub 的语义依然是完全公开的可见性,但编译器会输出一个警告提示用户将 pub 迁移至 pub(all)/pub(open)。已有的代码可以通过 moon fmt 自动迁移,formatter 会把 pub 自动变成 pub(all)/pub(open)

  • struct 类型新增 private field 功能,可以在一个公开的 struct 的某些 field 前用 priv 关键字隐藏它:
  1. 在外部,priv field 是完全不可见的。不能读也不能修改
  2. 如果一个 struct 有 private field,它在外部不可以用字面量直接构造。但可以使用 struct update 语法 { ..old_struct, new_field: ... } 来更新公开的 field
  3. 迁移到后缀风格的 label argument 语法。新的语法相当于把~移动到了标识符之后,如果是~label?这种形式,波浪号可以省略。
enum Foo {
Bar(label~ : Int, Bool)
}
fn f(a~ : Int, b~ : Int = 0, c? : Int) -> Unit {...}
fn main {
f(a=1, b=2, c=3)
f(a=1, c?=Some(3))//b 声明了默认值可以省略,c 转发 option 语法照旧
let a = 1
let c = Some(3)
f(a~, c?) //和声明处一样,punning 也变成了后缀的形式
}

旧的语法将在未来移除:

enum Foo {
//deprecated
Bar(~label : Int, Bool)
}
//deprecated
fn f(~a : Int, ~b : Int = 0, ~c? : Int) -> Unit {}
fn main {
let a = 1
let c = Some(3)
f(~a, ~c?) //deprecated
}

已有的代码可以通过 moon fmt 进行迁移。

  • 添加了一系列保留字,供编译器未来使用。目前,使用保留字会得到一个警告,提示用户进行迁移。未来这些保留字可能会变成语言关键字。本次新增的保留字为:
module
move
ref
static
super
unsafe
use
where
async
await
dyn
abstract
do
override
typeof
virtual
yield
local
method
alias
  • 我们计划将标准库中的 Bytes 类型改为不可变类型,因此会将一些对 Bytes 类型进行修改的函数标记为 deprecate,如果需要使用可以修改的 Bytes 类型,可以考虑使用 Array[Byte], FixedArray[Byte] 或者 Buffer 进行替代。

  • @test.is 函数被重命名为 @test.same_object,旧的名字被 deprecate。未来我们将把 is 设为保留字。

IDE更新

  • 修复了moon fmt --block-style没有正确处理///| comment的问题。

  • IDE 适配后缀 label argument 语法,支持 gotoref 和 rename。

  • 文档中支持代码高亮。

  • 修复 lsp 在@补全时有 internal package 的问题。

构建系统更新

  • -verbose选项现在会输出当前运行的命令。

MoonBit ProtoBuf

2024-11-04

· 阅读需 5 分钟

MoonBit更新

  • 增加了编译期常量的支持。常量的名字以大写字母开头,用语法 const C = ... 声明。常量的类型必须是内建的数字类型或 String。常量可以当作普通的值使用,也可以用于模式匹配。常量的值目前只能是字面量:
const MIN_INT = 0x1000_0000
const MAX_INT = 0x7fff_ffff

fn classify_int(x : Int) -> Unit {
match x {
MIN_INT => println("smallest int")
MAX_INT => println("largest int")
_ => println("other int")
}
}

fn main {
classify_int(MIN_INT) // smallest int
classify_int(MAX_INT) // largest int
classify_int(42) // other int
}
  • 改进了 unused warning,增加了一系列功能:
  1. 增加了 enum 的参数未被使用的检测
enum E {
// 现在编译器会提示 y 未被使用
C(~x : Int, ~y : Int)
}

fn f(x : E) -> Unit {
match x {
C(~x, ..) => println(x)
}
}

fn main {
f(C(x=1, y=2))
}
  1. 增加了函数的默认参数的默认值未被使用的检测(默认关闭)
// f 是一个私有函数,而且每次调用它时,调用者都显式给 x 提供了值。
// 如果开启了警告 32(默认关闭),编译器会提示 x 的默认值未被使用
fn f(~x : Int = 0) -> Unit {
println(x)
}

fn main {
f(x=1)
}
  • 支持从其他包中直接导入函数,直接导入的函数在使用时,不需要 @pkg. 的前缀,使用方式需要通过在 moon.pkg.json 配置文件中,通过 "value" 字段声明,比如下面这个示例:
{
"import": [
{
"path": "moonbitlang/pkg",
"alias": "pkg",
"value": ["foo", "bar"]
},
]
}

这里例子中,在 moon.pkg.json 中声明了从 moonbitlang/pkg 导入 foobar 两个函数,从而在使用这两个函数的时候可以直接对其进行调用,而不需要再写 @pkg.foo 或者 @pkg.bar

  • 现在 BigInt 类型在 JavaScript 后端下会编译到原生的 BigInt,对 BigInt 的 pattern matching 也会编译为高效的 switch 语句。

  • 实验性功能:现在 JavaScript 后端会根据 moon.pkg.json 中指定的导出函数配置来生成 .d.ts 文件,用于改进在 TypeScript/JavaScript 侧使用 MoonBit 生成的 JavaScript 代码的体验。(目前复杂类型的导出还在设计中,现阶段生成为 TS 的 any 类型)

IDE更新

  • block-line 标记支持

    增加对顶层注释///中的特殊标记block-line///|的支持。block-line用于分割顶层的代码块,使用它可以提升代码的可读性和维护性。通过在顶层代码块之间添加标记,IDE能够更清晰地展示代码结构。

block-line.jpg

通过moon fmt --block-style可以自动在每个顶层成员之前添加这样的标记。未来基于block-line的增量式代码解析和类型检查将进一步提高LSP的反应速度和可用性,提升开发效率。

  • 在线 IDE 现已支持访问 GitHuB 仓库,
  1. 打开 GitHub 中的 MoonBit 项目
  2. 将 github.com 更改为 try.moonbitlang.com
  • test explorer 中支持 test coverage 可视化。

coverage.jpg

  • AI添加了/doc-pub命令来为公开函数生成文档。

  • 修复了/doc命令会覆盖pragmas的问题。

  • patch 功能现在能够验证生成出来的 test case 是否正确。

ai-test.jpg

构建系统

  • moon check ⽀持指定包,指定后会 check 当前包和它的依赖;用法: moon check /path/to/pkg

MoonBit Markdown 库