moonbit-bestpractice
Original:🇺🇸 English
Translated
MoonBit (.mbt) coding standards and best practices. Use when writing, reviewing, or refactoring MoonBit code.
8installs
Added on
NPX Install
npx skill4agent add totto2727-dotfiles/agents moonbit-bestpracticeTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →MoonBit Coding Standards
1. Documentation
- 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.
2. Naming Conventions
- 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). Avoidjsonforp,pointforls, etc.line_string - Collections: Use the suffix for array arguments/variables instead of plural names (e.g.,
_arrayinstead ofpolygon_array).polygons - Constructors: Always define inside the struct body. Factory functions use
fn new/new_hogenaming — neverfrom_hoge.::new
3. Idioms & Best Practices
3.1 Constructors & Instance Initialization
-
Default constructor: Thedeclaration inside the struct body is the constructor definition itself — do NOT write a separate
fn newimplementation outside.fn StructName::new(...)supportsfn new, so validation logic should be placed inraiseto ensure instances are always in a valid state.newOK:mbtstruct MyStruct { x : Int y : Int fn new(x~ : Int, y~ : Int) -> MyStruct }NG:mbtfn MyStruct::new(x~ : Int, y~ : Int) -> MyStruct { { x, y } } -
Factory functions: Define separate static functions with names like,
new_hoge, etc. Never name themfrom_hoge. Factory functions must generate values via the::newconstructor (newis equivalent toStructName(...)):StructName::new(...)mbtstruct 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 () should ONLY be used strictly within constructor functions like
StructName::{...}. External code must use the constructor syntax (new).StructName(...) -
Updating: Use dedicated update functions/methods to modify values.
-
Struct Update Syntax: Avoid using Struct Update Syntax (e.g.,) whenever possible, as it may bypass validation logic or constraints.
{ ..base, field: value } -
Ignore Usage: Use proper pipeline style when ignoring return values:.
expr |> ignore
3.2 Error Handling
Use the effect for functions that can fail instead of returning types for synchronous logic.
raiseResultDefining error types:
mbt
suberror DivError { DivError(String) }
suberror E3 {
A
B(String)
C(Int, loc~ : SourceLoc)
}suberrorsuberror A Bsuberror A { A(B) }Raising errors:
- Custom error:
raise DivError("division by zero") - Generic failure: — convenience function that raises
fail("message")type with source locationFailure
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 }- : function may raise a specific error type
raise CustomError - or
raise: function may raise any errorraise Error - : function guaranteed not to raise
noraise
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)- : full error handling
try { expr } catch { pattern => handler } noraise { v => ... } - : simplified inline catch
let a = expr catch { _ => default } - : convert to
try? exprResult[T, Error] - : panic on error
try! expr
Error polymorphism:
Use for higher-order functions that conditionally throw:
raise?mbt
fn[T] map(
array : Array[T],
f : (T) -> T raise?
) -> Array[T] raise? { ... }When is , is also . When raises, raises.
fnoraisemapnoraisefmapBest practices:
- Prefer effect over
raisefor synchronous codeResult - In tests, let errors propagate or use to assert success
guard
3.3 Pattern Matching & Guards
-
Avoidfor Validation: Use
ifinstead:guardmbtguard array.length() > 0 else { raise Error("Empty") } -
Usefor assertions, early returns, or unwrapping:
guard-
General Code: Always provide an explicit fallback using:
elsembtguard hoge is Hoge(fuga) else { raise fail("Message") } -
Tests: Usewithout fallback for better readability:
guardmbtguard feature.geometry is Some(@geojson.Geometry::Polygon(poly))
-
-
Usefor exhaustive handling of Enums.
match -
Use Labeled Arguments/Punners () in patterns and constructors when variable names match field names:
~mbtmatch geometry { Polygon(coordinates~) => coordinates }
3.4 Functions
-
Anonymous Functions: Prefer arrow syntaxover
args => bodykeywordfn.fn(args) { body } -
Localannotation: Local
fndefinitions must explicitly annotatefn/raiseeffects. Inference for localasyncis deprecated. Arrow functions are unaffected.fnmbtfn outer() -> Unit raise { fn local_fn() -> Unit raise { fail("err") } let arrow_fn = () => { fail("err") } }
3.5 Structs & Enums
-
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 onand delegate to the inner struct's method.
self -
Prefer distinct Structs for complex data wrapped in Enums if polymorphic behavior is needed.
-
Standard traits:,
Debug,Eq,Compare,ToJson,FromJson(note: types containingHashcannot deriveJson).Hash -
trait: Always derive
Debuginstead ofDebug.ShowreplacesDebugas the standard trait for structural formatting. UsesShowfor output.debug_inspect()mbtstruct MyStruct { ... } derive(Debug, Eq) -
Constructor qualified names: Built-in constructors require qualified names (e.g.,). Constructors with arguments cannot be used as higher-order functions; use a wrapper:
Failure::Failurembtlet f = x => Some(x)
3.6 Traits
-
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:
- Default Implementation: If a method's return value is not and it can be implemented solely using other methods, provide a default implementation.
Self - 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.
- Direct Implementation: If delegation is not possible or creates circular dependencies, implement the method directly on the type.
- Default Implementation: If a method's return value is not
3.7 Cascade Operator
x..f(){ x.f(); x }Unitmbt
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.
3.8 Range Syntax
- : exclusive upper bound (increasing)
a..<b - : inclusive upper bound (increasing) — replaces deprecated
a..<=ba..=b - : exclusive lower bound (decreasing)
a>..b - : inclusive lower bound (decreasing)
a>=..b
mbt
for i in 0..<10 { ... }
for i in 10>=..0 { ... }3.9 Loop nobreak
nobreakReplaces old loop . Executes when the loop condition becomes false.
elsembt
let r = while i > 0 {
if cond { break 42 }
i = i - 1
} nobreak {
7
}breaknobreak3.10 declare
Keyword
declareSimilar to Rust's macro. Use before function definitions to indicate specification-only declarations. Missing implementations generate warnings (not errors).
todo!declarembt
declare fn add(x : Int, y : Int) -> Int4. Performance Optimization
-
Lazy evaluation with Iterator: For array processing where the size is unknown or potentially large, preferfor lazy evaluation to avoid intermediate array allocations. This is especially effective for fold operations like
iter()/minimum():maximum()mbtlet min_x = coords.iter().map(c => c.x()).minimum().unwrap() -
Flattening: Useinstead of manual loops with
flatten()when merging nested collections.append
5. Toolchain
- DSL replaces deprecated
moon.pkg.moon.pkg.json
6. Testing
See MoonBit Testing Standards for detailed testing guidelines.