From moonbit
MoonBit (.mbt) coding standards and best practices. Use when writing, reviewing, or refactoring MoonBit code.
npx claudepluginhub totto2727-org/monorepo --plugin moonbitThis skill uses the workspace's default tool permissions.
- [moonbit-docs](../moonbit-docs/SKILL.md) — MoonBit language reference (syntax, types, functions, methods, deriving). Use for language questions and debugging.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
///| for documentation comments on top-level definitions (functions, structs, enums, traits, tests). This is the output format of moon fmt.Position, GeoJSONObject, ToBBox).FromJSON, JSONToMap).from_json, to_geometry, boolean_point_in_polygon).coordinates, shifted_poly).id, json). Avoid p for point, ls for line_string, etc._array suffix for array arguments/variables instead of plural names (e.g., polygon_array instead of polygons).fn new inside the struct body. Factory functions use new_hoge/from_hoge naming — never ::new.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:
struct MyStruct {
x : Int
y : Int
fn new(x~ : Int, y~ : Int) -> MyStruct
}
NG:
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(...)):
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.
Use the raise effect for functions that can fail instead of returning Result types for synchronous logic.
Defining error types:
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:
raise DivError("division by zero")fail("message") — convenience function that raises Failure type with source locationFunction signatures:
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 typeraise or raise Error: function may raise any errornoraise: function guaranteed not to raiseHandling errors:
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 handlinglet a = expr catch { _ => default }: simplified inline catchtry? expr: convert to Result[T, Error]try! expr: panic on errorError polymorphism:
Use raise? for higher-order functions that conditionally throw:
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:
raise effect over Result for synchronous codeguard to assert successAvoid if for Validation: Use guard instead:
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:
guard hoge is Hoge(fuga) else { raise fail("Message") }
Tests: Use guard without fallback for better readability:
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:
match geometry {
Polygon(coordinates~) => coordinates
}
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.
fn outer() -> Unit raise {
fn local_fn() -> Unit raise { fail("err") }
let arrow_fn = () => { fail("err") }
}
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.
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.
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:
let f = x => Some(x)
Definition Notation:
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:
Self and it can be implemented solely using other methods, provide a default implementation.x..f() is equivalent to { x.f(); x } — for methods returning Unit.
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.
a..<b: 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)for i in 0..<10 { ... }
for i in 10>=..0 { ... }
nobreakReplaces old loop else. Executes when the loop condition becomes false.
let r = while i > 0 {
if cond { break 42 }
i = i - 1
} nobreak {
7
}
break must provide a value matching the nobreak return type.
declare KeywordSimilar to Rust's todo! macro. Use declare before function definitions to indicate specification-only declarations. Missing implementations generate warnings (not errors).
declare fn add(x : Int, y : Int) -> Int
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():
let min_x = coords.iter().map(c => c.x()).minimum().unwrap()
Flattening: Use flatten() instead of manual loops with append when merging nested collections.
moon.pkg DSL replaces deprecated moon.pkg.json.See MoonBit Testing Standards for detailed testing guidelines.