跳到主要内容

2025-05-19

· 阅读需 3 分钟

语言更新

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

· 阅读需 3 分钟

语言更新

  • 【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

· 阅读需 6 分钟

语言更新

  • 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

· 阅读需 3 分钟

语言更新

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 支持模糊匹配包名。

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 小节