月报 Vol.02:新增条件编译属性 cfg、#alias属性、defer表达式,增加 tuple struct 支持

语言更新

  • 新增条件编译属性 cfg。可以根据后端等条件进行文件内的条件编译。

    Rust 复制代码
    #cfg(any(target="js", target="wasm-gc"))
    let current_target = "js | wasm-gc"
  • 新增#alias属性,目前可以给方法或函数创建别名,并支持标注废弃。后续支持更多场景。

    Rust 复制代码
    #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 都会被运行

    Rust 复制代码
    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
    }
    Rust 复制代码
    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(...)) 属性替代原有语法

    Rust 复制代码
    // 原版本
    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 支持

    Rust 复制代码
    // 旧语法,运行时等价于 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,可以使用:

    Rust 复制代码
    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。

    Rust 复制代码
    ///| 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功能,提供更直观的覆盖率报告

    Rust 复制代码
    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 显示原始位置了

    Bash 复制代码
    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)

EN

Language updates

  • New conditional compilation attribute cfg. You can now compile specific sections of code based on conditions such as the target backend.

    Rust 复制代码
    #cfg(any(target="js", target="wasm-gc"))
    let current_target = "js | wasm-gc"
  • New #alias attribute. You can now create aliases for methods or functions and attach annotation information. More scenarios will be supported in the future.。

    Rust 复制代码
    #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)
    }
  • New defer statement. Provides a scope-based resource cleanup feature. When any form of defer expr; body appears in the body of a block, the expr will always be executed when the body ends.

    Rust 复制代码
    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` and similar statements can also trigger `defer`
        }
        println("Looping \{i}")
      }
      return
    }
    Rust 复制代码
    block1
    End of block1
    Looping 0
    End of loop 0
    Looping 1
    End of loop 1
    End of loop 2
    End of main

    Currently, the expr in a defer expr cannot contain expressions or calls to async functions, and expr cannot use control flow constructs such as return / break / continue.

  • In the Native backend, the Bytes representation used to always have an extra trailing '\0' character. Now, Bytes can be directly used to pass C strings in FFI calls without this extra trailing '\0' character being counted in the Bytes length, so the current code behavior will remain unchanged.

  • Adjusted the syntax for optional parameters: default arguments can now depend on preceding parameters (this behavior was previously removed due to incompatibility with virtual packages, but we have now found a way to support such complex defaults while remaining compatible).

We have also unified optional parameters with default values (label: T = ...) and those without (label?: T). From the caller's perspective, there is no longer any difference between them, and both now support the following call styles:

  • Omit the argument to use the default value.

  • Pass explicitly using label=value.

  • Use label?=opt, meaning: if opt is Some(value), it is equivalent to label=value; if opt is None, it is equivalent to omitting the argument.

When calling functions with default arguments, the #callsite(autofill(...)) attribute can be used as a shorthand:

Rust 复制代码
// Original code
pub fn[T] fail(msg : String, loc~ : SourceLoc = _) -> T raise Failure { ... }
// new code
#callsite(autofill(loc))
pub fn[T] fail(msg : String, loc~ : SourceLoc) -> T raise Failure { ... }
  • Removed newtype, added support for tuple struct.

    Rust 复制代码
    // Old syntax, accessing the wrapped Int
    type A Int
    fn get(a : A) -> Int {
      a.inner()
    }
    
    // New syntax, accessing the wrapped 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)
    }

    For a tuple struct with one field , it is equivalent to the original newtype. If the underlying type is not a tuple, the formatter will auto-migrate old access syntax. In this case, an .inner() method is provided for migration and will be deprecated later.

    For a tuple struct with multiple fields, differences from the original tuple newtype are:

    • Cannot be constructed directly with tuple syntax.

    • No .inner() method to retrieve the tuple.

    • For tuple structs that support conversion to a tuple, you can use:

    Rust 复制代码
    struct T((Int, Int))
    
    fn make_t(x: Int, y: Int) -> T {
      (x, y)
    }
    
    fn use_t(t: T) -> (Int, Int) {
      t.0
    }

In this case, to access specific elements, you need to use t.0.0 or t.0.1.

  • Since the primary purpose is for data storage and functions such as @json.inspect, derive(FromJson, ToJson) will no longer provide advanced format adjustment parameters. The currently retained format parameters are: rename for each field, batch renaming, and the style option for enum format selection; all other parameters will be removed.

    • The optional values for style are legacy and flat. The latter simplifies representation and is suitable for scenarios such as @json.inspect. All enums must currently choose one of these styles.

    • If you need to customize the JSON format, please implement the FromJson and ToJson traits yourself.

    Rust 复制代码
    ///| 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" } },
      })
    }

Toolchain update

  • Added moon coverage analyze for clearer coverage reports.

    Rust 复制代码
    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 now shows original source locations on panic via sourcemap.

Bash 复制代码
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)
相关推荐
程序猿毕设源码分享网3 分钟前
springboot医院信管系统源码和论文
java·spring boot·后端
桦说编程2 小时前
数据丢失,而且不抛出并发异常,多线程使用HashMap踩坑
java·数据结构·后端
颜如玉2 小时前
Redis主从同步浅析
后端·开源·源码
David爱编程4 小时前
对象锁 vs 类锁:Java 并发中的隐形对决
java·后端
努力的小雨4 小时前
我一个写Java的,怎么就开始玩K8s和Jenkins了?!
后端·云原生
winrisef4 小时前
Node.js版本管理工具 || 全配置安装
后端·node.js·nvm·asdf·fnm
池易4 小时前
调用后端接口像调用函数一样简单——Fun改变了我的开发方式
后端·go
Shawn_Shawn4 小时前
Spring-Ai-Mcp-Server 快速入门
后端·agent·mcp
心月狐的流火号4 小时前
分布式事务XA模式:基于数据库的2PC
分布式·后端
AI大模型5 小时前
RAG生产环境实战指南:从Demo到百万用户的血泪教训
程序员·llm·agent