moonbit-bestpractice

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MoonBit Coding Standards

MoonBit编码标准

1. Documentation

1. 文档规范

  • Use
    ///|
    for documentation comments on top-level definitions (functions, structs, enums, traits, tests). This is the output format of
    moon fmt
    .
  • Ensure public APIs are documented.
  • 对顶层定义(函数、结构体、枚举、Trait、测试)使用
    ///|
    编写文档注释,这是
    moon fmt
    的输出格式。
  • 确保公开API都有文档说明。

2. Naming Conventions

2. 命名约定

  • Types (Structs, Enums) & Traits: PascalCase (e.g.,
    Position
    ,
    GeoJSONObject
    ,
    ToBBox
    ).
  • Abbreviations: Abbreviations other than those at the beginning (such as JSON or ID) should generally be all uppercase (e.g.
    FromJSON
    ,
    JSONToMap
    ).
  • Functions & Methods: snake_case (e.g.,
    from_json
    ,
    to_geometry
    ,
    boolean_point_in_polygon
    ).
  • Variables & Parameters: snake_case (e.g.,
    coordinates
    ,
    shifted_poly
    ).
  • No Abbreviations: Do not abbreviate variable names unless they are common abbreviations (e.g.,
    id
    ,
    json
    ). Avoid
    p
    for
    point
    ,
    ls
    for
    line_string
    , etc.
  • Collections: Use the
    _array
    suffix for array arguments/variables instead of plural names (e.g.,
    polygon_array
    instead of
    polygons
    ).
  • Constructors: Always define
    fn new
    inside the struct body. Factory functions use
    new_hoge
    /
    from_hoge
    naming — never
    ::new
    .
  • 类型(结构体、枚举)与Trait:采用PascalCase命名法(例如:
    Position
    GeoJSONObject
    ToBBox
    )。
  • 缩写规则:除开头的缩写(如JSON或ID)外,其他缩写应全部大写(例如
    FromJSON
    JSONToMap
    )。
  • 函数与方法:采用snake_case命名法(例如:
    from_json
    to_geometry
    boolean_point_in_polygon
    )。
  • 变量与参数:采用snake_case命名法(例如:
    coordinates
    shifted_poly
    )。
  • 禁止随意缩写:除非是通用缩写(如
    id
    json
    ),否则不要缩写变量名。避免用
    p
    代替
    point
    ls
    代替
    line_string
    等。
  • 集合命名:数组类型的参数/变量使用
    _array
    后缀,而非复数形式(例如:
    polygon_array
    而非
    polygons
    )。
  • 构造函数:始终在结构体内部定义
    fn new
    。工厂函数使用
    new_hoge
    /
    from_hoge
    命名——绝不要使用
    ::new

3. Idioms & Best Practices

3. 编码惯例与最佳实践

3.1 Constructors & Instance Initialization

3.1 构造函数与实例初始化

  • Default constructor: The
    fn new
    declaration inside the struct body is the constructor definition itself — do NOT write a separate
    fn StructName::new(...)
    implementation outside.
    fn new
    supports
    raise
    , so validation logic should be placed in
    new
    to ensure instances are always in a valid state.
    OK:
    mbt
    struct MyStruct {
      x : Int
      y : Int
    
      fn new(x~ : Int, y~ : Int) -> MyStruct
    }
    NG:
    mbt
    fn MyStruct::new(x~ : Int, y~ : Int) -> MyStruct {
      { x, y }
    }
  • Factory functions: Define separate static functions with names like
    new_hoge
    ,
    from_hoge
    , etc. Never name them
    ::new
    . Factory functions must generate values via the
    new
    constructor (
    StructName(...)
    is equivalent to
    StructName::new(...)
    ):
    mbt
    struct Rect {
      x : Double
      y : Double
      width : Double
      height : Double
    
      // Validation in new ensures all Rect instances have valid size
      fn new(x~ : Double, y~ : Double, width~ : Double, height~ : Double) -> Rect raise
    }
    
    // Conversion: create from a different representation
    fn Rect::from_corners(x1~ : Double, y1~ : Double, x2~ : Double, y2~ : Double) -> Rect {
      Rect(x=x1, y=y1, width=x2 - x1, height=y2 - y1)
    }
    
    // Specific state: create a type with optional fields in a predetermined state
    fn Rect::new_unit(x~ : Double, y~ : Double) -> Rect {
      Rect(x~, y~, width=1.0, height=1.0)
    }
  • Initialization: Struct literal syntax (
    StructName::{...}
    ) should ONLY be used strictly within constructor functions like
    new
    . External code must use the constructor syntax (
    StructName(...)
    ).
  • Updating: Use dedicated update functions/methods to modify values.
  • Struct Update Syntax: Avoid using Struct Update Syntax (e.g.,
    { ..base, field: value }
    ) whenever possible, as it may bypass validation logic or constraints.
  • Ignore Usage: Use proper pipeline style when ignoring return values:
    expr |> ignore
    .
  • 默认构造函数:结构体内部声明的
    fn new
    即为构造函数定义——不要在外部单独实现
    fn StructName::new(...)
    fn new
    支持
    raise
    特性,因此验证逻辑应放在
    new
    中,确保实例始终处于有效状态。
    正确示例
    mbt
    struct MyStruct {
      x : Int
      y : Int
    
      fn new(x~ : Int, y~ : Int) -> MyStruct
    }
    错误示例
    mbt
    fn MyStruct::new(x~ : Int, y~ : Int) -> MyStruct {
      { x, y }
    }
  • 工厂函数:定义独立的静态函数,命名如
    new_hoge
    from_hoge
    等,绝不要命名为
    ::new
    。工厂函数必须通过
    new
    构造函数生成实例(
    StructName(...)
    等价于
    StructName::new(...)
    mbt
    struct Rect {
      x : Double
      y : Double
      width : Double
      height : Double
    
      // new中的验证逻辑确保所有Rect实例的尺寸都合法
      fn new(x~ : Double, y~ : Double, width~ : Double, height~ : Double) -> Rect raise
    }
    
    // 转换:从不同表示形式创建实例
    fn Rect::from_corners(x1~ : Double, y1~ : Double, x2~ : Double, y2~ : Double) -> Rect {
      Rect(x=x1, y=y_1, width=x_ - x1, height=y2 - y1)
    }
    
    // 指定状态:创建具有可选字段且处于预设状态的实例
    fn Rect::new_unit(x~ : Double, y~ : Double) -> Rect {
      Rect(x~, y~, width=1.0, height=1.0)
    }
  • 初始化规则结构体字面量语法(
    StructName::{...}
    )仅能在
    new
    这类构造函数内部严格使用。外部代码必须使用构造函数语法(
    StructName(...)
    )。 更新规则**:使用专门的更新函数方法修改值。
  • Struct Update Syntax:尽可能避免使用Struct Update Syntax(例如
    { ..base, field value }
    ),因为它可能会绕过验证逻辑或约束条件。 忽略返回值**:忽略返回值时使用标准管道风格:
    expr |> ignore

3.2 Error Handling

3.2 错误处理

Use the
raise
effect for functions that can fail instead of returning
Result
types for synchronous logic.
Defining error types:
mbt
suberror DivError { DivError(String) }

suberror E3 {
  A
  B(String)
  C(Int, loc~ : SourceLoc)
}
suberror
uses enum-like constructor syntax. The older
suberror A B
syntax is deprecated. Always use
suberror A { A(B) }
with explicit constructors.
Raising errors:
  • Custom error:
    raise DivError("division by zero")
  • Generic failure:
    fail("message")
    — convenience function that raises
    Failure
    type with source location
Function signatures:
mbt
fn div(x : Int, y : Int) -> Int raise DivError { ... }

fn f() -> Unit raise { ... }

fn add(a : Int, b : Int) -> Int noraise { a + b }
  • raise CustomError
    : function may raise a specific error type
  • raise
    or
    raise Error
    : function may raise any error
  • noraise
    : function guaranteed not to raise
Handling errors:
mbt
try div(42, 0) catch {
  DivError(msg) => println(msg)
} noraise {
  v => println(v)
}

let a = div(42, 0) catch { _ => 0 }

let res = try? (div(6, 0) * div(6, 3))

try! div(42, 0)
  • try { expr } catch { pattern => handler } noraise { v => ... }
    : full error handling
  • let a = expr catch { _ => default }
    : simplified inline catch
  • try? expr
    : convert to
    Result[T, Error]
  • try! expr
    : panic on error
Error polymorphism:
Use
raise?
for higher-order functions that conditionally throw:
mbt
fn[T] map(
  array : Array[T],
  f : (T) -> T raise?
) -> Array[T] raise? { ... }
When
f
is
noraise
,
map
is also
noraise
. When
f
raises,
map
raises.
Best practices:
  • Prefer
    raise
    effect over
    Result
    for synchronous code
  • In tests, let errors propagate or use
    guard
    to assert success
对于可能失败的同步逻辑函数,使用
raise
特性而非返回
Result
类型。
定义错误类型
mbt
suberror DivError { DivError(String) }

suberror E3 {
  A
  B(String)
  C(Int, loc~ : SourceLoc)
}
suberror
使用类似枚举的构造函数语法。旧版的
suberror A B
语法已被废弃,应始终使用带显式构造函数的
suberror A { A(B) }
形式。
抛出错误
  • 自定义错误:
    raise DivError("division by zero")
  • 通用失败:
    fail("message")
    ——便捷函数,会抛出带源码位置的
    Failure
    类型错误
函数签名
mbt
fn div(x : Int, y : Int) -> Int raise DivError { ... }

fn f() -> Unit raise { ... }

fn add(a : Int, b : Int) -> Int noraise { a + b }
  • raise CustomError
    :函数可能抛出特定错误类型
  • raise
    raise Error
    :函数可能抛出任意错误
  • noraise
    :函数保证不会抛出错误
处理错误
mbt
try div(42, 0) catch {
  DivError(msg) => println(msg)
} noraise {
  v => println(v)
}

let a = div(42, 0) catch { _ => 0 }

let res = try? (div(6, 0) * div(6, 3))

try! div(42, 0)
  • try { expr } catch { pattern => handler } noraise { v => ... }
    :完整错误处理
  • let a = expr catch { _ => default }
    :简化内联捕获
  • try? expr
    :转换为
    Result[T, Error]
    类型
  • try! expr
    :错误时触发panic
错误多态性
对于可能条件性抛出错误的高阶函数,使用
raise?
mbt
fn[T] map(
  array Array[T],
  f : (T) -> T raise?
) -> Array[T] raise? { ... }
f
noraise
时,
map
也为
noraise
;当
f
会抛出错误时,
map
也会抛出错误。
最佳实践
  • 同步代码优先使用
    raise
    特性而非
    Result
    类型
  • 测试中,可让错误传播或使用
    guard
    断言成功

3.3 Pattern Matching & Guards

3.3 模式匹配与Guard

  • Avoid
    if
    for Validation
    : Use
    guard
    instead:
    mbt
    guard array.length() > 0 else { raise Error("Empty") }
  • Use
    guard
    for assertions, early returns, or unwrapping:
    • General Code: Always provide an explicit fallback using
      else
      :
      mbt
      guard hoge is Hoge(fuga) else { raise fail("Message") }
    • Tests: Use
      guard
      without fallback for better readability:
      mbt
      guard feature.geometry is Some(@geojson.Geometry::Polygon(poly))
  • Use
    match
    for exhaustive handling of Enums.
  • Use Labeled Arguments/Punners (
    ~
    ) in patterns and constructors when variable names match field names:
    mbt
    match geometry {
      Polygon(coordinates~) => coordinates
    }
  • 避免用if做验证:改用
    guard
    mbt
    guard array.length() > 0 else { raise Error("Empty") }
  • 使用
    guard
    进行断言、提前返回或解包:
    • 通用代码:始终通过
      else
      提供显式回退逻辑:
      mbt
      guard hoge is Hoge(fuga) else { raise fail("Message") }
    • 测试代码:不带回退的
      guard
      可读性更好:
      mbt
      guard feature.geometry is Some(@geojson.Geometry::Polygon(poly))
  • 使用
    match
    对枚举进行穷尽处理
  • 当变量名与字段名匹配时,在模式和构造函数中使用带标签参数/Punners
    ~
    ):
    mbt
    match geometry {
      Polygon(coordinates~) => coordinates
    }

3.4 Functions

3.4 函数

  • Anonymous Functions: Prefer arrow syntax
    args => body
    over
    fn
    keyword
    fn(args) { body }
    .
  • Local
    fn
    annotation
    : Local
    fn
    definitions must explicitly annotate
    raise
    /
    async
    effects. Inference for local
    fn
    is deprecated. Arrow functions are unaffected.
    mbt
    fn outer() -> Unit raise {
      fn local_fn() -> Unit raise { fail("err") }
      let arrow_fn = () => { fail("err") }
    }
  • 匿名函数:优先使用箭头语法
    args => body
    而非
    fn
    关键字形式
    fn(args) { body }
  • 局部fn注解:局部
    fn
    定义必须显式标注
    raise
    /
    async
    特性。局部
    fn
    的特性推断已被废弃,箭头函数不受此影响。
    mbt
    fn outer() -> Unit raise {
      fn local_fn() -> Unit raise { fail("err") }
      let arrow_fn = () => fail("err") }
    }

3.5 Structs & Enums

3.5 结构体与枚举

  • Enum Wrapping Structs: Define independent Structs for each variant, then wrap them in the Enum. Use the same name for the Variant and the Struct.
    mbt
    pub struct Point { ... }
    
    pub enum Geometry {
      Point(Point)
      MultiPoint(MultiPoint)
    } derive(Debug, Eq)
  • Delegation: When implementing traits for such Enums, pattern match on
    self
    and delegate to the inner struct's method.
  • Prefer distinct Structs for complex data wrapped in Enums if polymorphic behavior is needed.
  • Standard traits:
    Debug
    ,
    Eq
    ,
    Compare
    ,
    ToJson
    ,
    FromJson
    ,
    Hash
    (note: types containing
    Json
    cannot derive
    Hash
    ).
  • Debug
    trait
    : Always derive
    Debug
    instead of
    Show
    .
    Debug
    replaces
    Show
    as the standard trait for structural formatting. Uses
    debug_inspect()
    for output.
    mbt
    struct MyStruct { ... } derive(Debug, Eq)
  • Constructor qualified names: Built-in constructors require qualified names (e.g.,
    Failure::Failure
    ). Constructors with arguments cannot be used as higher-order functions; use a wrapper:
    mbt
    let f = x => Some(x)
  • 枚举包裹结构体:为每个枚举变体定义独立的结构体,然后将其包裹在枚举中。变体与结构体使用相同名称。
    mbt
    pub struct Point { ... }
    
    pub enum Geometry {
      Point(Point)
      MultiPoint(MultiPoint)
    } derive(Debug, Eq)
  • 委托实现:为此类枚举实现trait时,对
    self
    进行模式匹配,然后委托给内部结构体的方法。
  • 如果需要多态行为,优先为枚举中包裹的复杂数据定义独立结构体。
  • 标准Trait
    Debug
    Eq
    Compare
    ToJson
    FromJson
    Hash
    (注意:包含
    Json
    的类型无法派生
    Hash
    )。
  • Debug Trait:始终派生
    Debug
    而非
    Show
    Debug
    已取代
    Show
    成为结构格式化的标准Trait,使用
    debug_inspect()
    输出内容。
    mbt
    struct MyStruct { ... } derive(Debug, Eq)
  • 构造函数限定:内置构造函数需要限定名(例如:
    Failure::Failure
    )。带参数的构造函数无法作为高阶函数使用,需使用包装器:
    mbt
    undefined
let f = x => Some(x)
undefined

3.6 Traits

3.Trait

  • Definition Notation:
    mbt
    pub trait ToGeoJSON {
      to_geojson(Self) -> GeoJSON
    }
  • Performance Overrides: Always override default trait methods if a more efficient implementation is possible for the specific type.
  • Implementation Rules:
    1. Default Implementation: If a method's return value is not
      Self
      and it can be implemented solely using other methods, provide a default implementation.
    2. Trait Object Delegation: When implementing a super-trait for a type that also implements a sub-trait, define the logic as a static function on the sub-trait's object and relay to it.
    3. Direct Implementation: If delegation is not possible or creates circular dependencies, implement the method directly on the type.
  • 定义语法
    mbt
    pub trait ToGeoJSON {
      to_geojson(Self) -> GeoJSON
    }
性能优化重写:如果针对特定类型有更高效实现方式,务必重写Trait的默认方法。
  • 实现规则
    1. 默认实现如果方法的返回值不是
      Self
      ,且仅能通过方法实现,则提供默认实现。 Trait对象委托**:当为某个类型实现父Trait,而该类型同时实现Trait时,将逻辑定义为子Trait对象的静态函数并委托给它。 直接实现**:如果无法委托或会产生循环依赖,则直接在类型上实现方法。

3.7 Cascade Operator

3.7 级联运算符

x..f()
is equivalent to
{ x.f(); x }
— for methods returning
Unit
.
mbt
let result = StringBuilder::new()
  ..write_char('a')
  ..write_object(1001)
  ..write_string("abcdef")
  .to_string()
Enables chaining mutable operations without modifying return types. Compiler warns if result is ignored.
x..f()
等价于
{ x.f(); x }
——适用于返回
Unit
的方法。
mbt
let result = StringBuildernew()
  ..write_char('a')
  ..write_object(10)
  ..write_string("abcdef")
  .to_string()
支持在不修改返回类型的情况下链式调用可变操作。如果忽略返回值,编译器会发出警告。

3.8 Range Syntax

3.8 范围语法

  • a..<b
    : exclusive upper bound (increasing)
  • a..<=b
    : inclusive upper bound (increasing) — replaces deprecated
    a..=b
  • a>..b
    : exclusive lower bound (decreasing)
  • a>=..b
    : inclusive lower bound (decreasing)
mbt
for i in 0..<10 { ... }
for i in 10>=..0 { ... }
  • a..<b
    :上界排他(递增范围)
  • a..<=b
    :上界包含(递增范围)——替代已废弃的
    a..=b
  • a>..b
    :下界排他(递减范围)
  • a>=..b
    :下界包含(递减范围)
mbt
for i in 0..<10 { ... }
for i in 10>=..0 { ... }

3.9 Loop
nobreak

3.9 循环
nobreak

Replaces old loop
else
. Executes when the loop condition becomes false.
mbt
let r = while i > 0 {
  if cond { break 42 }
  i = i - 1
} nobreak {
  7
}
break
must provide a value matching the
nobreak
return type.
替代旧版循环的
else
。当循环条件变为false时执行。
mbt
let r = while i > 0 {
  if cond { break 42 }
  i = i - 1
} nobreak {
  7
}
break
必须提供与
nobreak
返回类型匹配的值。

3.10
declare
Keyword

3.10
declare
关键字

Similar to Rust's
todo!
macro. Use
declare
before function definitions to indicate specification-only declarations. Missing implementations generate warnings (not errors).
mbt
declare fn add(x : Int, y : Int) -> Int
类似Rust的
todo!
宏。在函数定义前使用
declare
表示仅为规范声明。缺失的实现会生成警告(而非错误)。
mbt
declare fn add(x : Int, y : Int) -> Int

4. Performance Optimization

4. 性能优化

  • Lazy evaluation with Iterator: For array processing where the size is unknown or potentially large, prefer
    iter()
    for lazy evaluation to avoid intermediate array allocations. This is especially effective for fold operations like
    minimum()
    /
    maximum()
    :
    mbt
    let min_x = coords.iter().map(c => c.x()).minimum().unwrap()
  • Flattening: Use
    flatten()
    instead of manual loops with
    append
    when merging nested collections.
  • 使用Iterator延迟求值:对于大小未知或可能很大的数组处理,优先使用
    iter()
    进行延迟求值,避免中间数组分配。这在
    minimum()
    /
    maximum()
    这类折叠操作中效果尤为显著:
    mbt
    let min_x = coords.iter().map(c => c.x()).minimum().unwrap()
  • 扁平化处理:合并嵌套集合时,使用
    flatten()
    而非手动循环加
    append

5. Toolchain

5. 工具链

  • moon.pkg
    DSL replaces deprecated
    moon.pkg.json
    .
  • moon.pkg
    DSL替代已废弃的
    moon.pkg.json

6. Testing

6. 测试

See MoonBit Testing Standards for detailed testing guidelines.
详细测试指南请参考MoonBit测试标准