One of MoonBit's appeals is the convenient commands available through the moon CLI.
If you have moonbit installed, you can see the full list with moon -h, but it's hard to know what to expect until you actually try them. So I'll introduce the features I've found particularly useful.
Even with the current library shortage, these features have enough power to compensate for it.
tl;dr
- Type checking and linting with moon lint
- Target-specific execution with moon run
- Doc tests and snapshot tests with moon test
- Type definition generation with moon info
- Searching built-in types and methods with moon doc
- Benchmarking with moon bench
- Coverage analysis with moon coverage analyze
- Direct LSP symbol search with moon ide
Type Checking and Warnings with moon check
In moonbit, there's currently no separate concept of Lint - type checking and linting are integrated. Warnings are quite strict by default.
For example, lint catches unused variables and unused type parameters.
❯ moon check
Warning: [0053]
╭─[ /Users/mizchi/mizchi/luna.mbt/src/astra/tree/builder.mbt:20:12 ]
│
20 │ pub fn[F : @env.FileSystem] build_document_tree_with_fs(
│ ───────┬───────
│ ╰───────── Warning (unused_trait_bound): This trait bound is unused.
────╯
Finished. moon: ran 419 tasks, now up to date (1 warnings, 0 errors)
Adding --deny-warn treats warnings as errors and returns status code 1. Use this in CI.
Failed with 0 warnings, 1 errors.
error: failed when checking project
You can suppress warnings in moon.mod.json or moon.pkg.json with "warn-list".
{
"warn-list": "-unused_trait_bound"
}
- removes and + adds warnings that aren't enabled by default. You can get the full list with moonc build-package -warn-help.
(Until recently, warn and alert were separate concepts, but they've been unified.)
Specifying Target with moon run --target
In moonbit, you can run a program with the moon run command when these conditions are met:
- It's under a
moon.mod.json - The directory's
moon.pkg.jsonhas"is-main": true - One of the
.mbtfiles in the directory hasfn main {}
moon run main.mbt
moon run .
moon run cmd/main.mbt
At this point, you can choose the build target with --target. Using all runs on all targets (except experimental llvm).
moon run main.mbt --target wasm-gc
moon run main.mbt --target js
moon run main.mbt --target native
moon run main.mbt --target all
moon test
Like run, you can specify targets. Let's verify with a minimal test.
test {
assert_true(true)
}
You can run with a specified target. Like moon run, --all is available.
moon test --target js
moon test --target native
moon test --target all
Additionally, there are doc tests for code comments and doc tests for .mbt.md files. You can write code examples in documentation and run them as tests.
You can use them in .mbt.md files or within ///| inline comments.
| Code Block | Behavior |
|---|---|
`mbt check
|
Checked by LSP |
mbt test ` |
Executed as test {...}
|
`moonbit
|
Not executed (display only) |
Example with inline comments:
`moonbitmbt test
///|
/// Increment an integer by 1
/// `
/// inspect(incr(41), content="42")
/// `
pub fn incr(x : Int) -> Int {
x + 1
}
`
Since documentation and tests are integrated, you can prevent sample code from becoming outdated.
Built-in Snapshot Tests with moon test
The combination of the built-in inspect function and moon test -u is very convenient.
`moonbit
test "snapshot" {
inspect([1, 2, 3], content="")
}
`
Running moon test -u automatically fills in the content="" part.
`moonbit
test "snapshot" {
inspect([1, 2, 3], content="[1, 2, 3]")
}
`
When writing tests, it's easiest to start with content="" and update while checking the execution results.
moon coverage analyze
You can get a coverage report for code not executed by tests.
(TODO: Add sample later)
Searching Built-in Types and Methods with moon doc
You can check the method list for types with the moon doc command.
`bash
moon doc StringView # Method list for StringView
moon doc Array # Method list for Array
moon doc Map # Method list for Map
`
You can see type signatures without reading the standard library source, so you know what's possible.
For example, StringView is a feature that immutably references a slice of String. This lets you reference slices of huge strings with an API similar to String.
`bash
❯ moon doc StringView
package "moonbitlang/core/string"
type StringView
pub fn StringView::add(Self, Self) -> Self
pub fn StringView::at(Self, Int) -> UInt16
pub fn StringView::char_length(Self) -> Int
pub fn StringView::char_length_eq(Self, Int) -> Bool
pub fn StringView::char_length_ge(Self, Int) -> Bool
pub fn StringView::code_unit_at(Self, Int) -> UInt16
pub fn StringView::compare(Self, Self) -> Int
pub fn StringView::contains(Self, Self) -> Bool
pub fn StringView::contains_any(Self, chars~ : Self) -> Bool
pub fn StringView::contains_char(Self, Char) -> Bool
pub fn StringView::data(Self) -> String
pub fn StringView::default() -> Self
pub fn StringView::equal(Self, Self) -> Bool
pub fn StringView::find(Self, Self) -> Int?
pub fn StringView::find_by(Self, (Char) -> Bool) -> Int?
`
Since this feature doesn't exist in other languages, you need to teach AI about it. But if you let agents know about this, it reduces the effort for humans to search references.
Benchmarking with moon bench
You can run benchmarks with moon bench.
Example usage:
`moonbit
test "fib benchmark" (b : @bench.Test) {
b.bench(fn() { fib(20) })
}
`
A test block that receives a b : @bench.Test parameter is recognized as a benchmark.
Calculations without side effects might be optimized away by the compiler, so use b.keep() to retain the result.
`moonbit
test "sum benchmark" (b : @bench.Test) {
let result = b.bench(fn() {
let mut sum = 0
for i = 0; i < 1000; i = i + 1 {
sum = sum + i
}
sum
})
b.keep(result)
}
`
Running it outputs statistical information.
`
test fib benchmark ... bench result
time (mean ± σ) range (min … max)
21.67 µs ± 0.54 µs 21.28 µs … 23.14 µs in 10 × 4619 runs
`
You can see mean time, standard deviation, min/max values, and run count.
This was very helpful in my markdown compiler implementation.
https://zenn.dev/mizchi/articles/markdown-incremental-preview
Being able to easily take benchmarks improves performance awareness - that's my experience, and I think it's really good.
Using IDE Symbol Search Directly with moon ide
Amazingly, you can directly use LSP features like symbol search and go-to-definition from the command line.
I learned about this from the author's demo at an online meetup. There's no documentation yet, but the commands exist.
moon ide find-references
`bash
❯ moon ide find-references -query "normalize_path"
fn normalize_path in package mizchi/luna/sol/router at /Users/mizchi/mizchi/luna.mbt/src/sol/router/sol_routes.mbt:270-297:
^^^^^^^^^^^^^^
Found 9 references of this symbol:
/Users/mizchi/mizchi/luna.mbt/src/sol/router/sol_routes.mbt:272:4-272:18:
|
| ///|
| /// Normalize path (remove duplicate slashes, trailing slash)
272 | fn normalize_path(path : String) -> String {
| ^^^^^^^^^^^^^^
| if path == "" || path == "/" {
| return "/"
`
moon ide goto-definition -query
`bash
$ moon ide goto-definition -query="normalize_path"
There are total 2 symbols match query 'normalize_path':
Top 2 best-matching symbols:
fn normalize_path in package mizchi/luna/sol/router at /Users/mizchi/mizchi/luna.mbt/src/sol/router/sol_routes.mbt:270-297:
^^^^^^^^^^^^^^
270 | ///|
| /// Normalize path (remove duplicate slashes, trailing slash)
| fn normalize_path(path : String) -> String {
| if path == "" || path == "/" {
| return "/"
| }
| let chars = path.to_array()
| let result : Array[Char] = []
| let mut prev_slash = false
`
There also seems to be a moon ide peek-def command, but I couldn't figure out how to use it since there's no documentation. It appears to be used like moon ide peek-def -loc ./src/luna/routes/compile.mbt:6:4 -symbol normalize_path.
Conclusion
What do you think? Similar features exist in TypeScript's eslint and vitest, or Cargo's criterion, but I don't know of any other language with such rich functionality built into the language's CLI. This abundance of features helps you get by even when libraries are lacking.
It's also interesting that moon doc and moon ide seem to be designed with AI in mind.
Of course, since it's a young language, you'll encounter bugs when using it - they tend to occur with specific backend + specific feature combinations.
If you find any, please report them here. I've reported about 5 issues there, and about 7 directly on Discord.
https://github.com/moonbitlang/moonbit-docs/issues
In my experience, critical issues usually get fixed by the following Tuesday.
Top comments (0)