Skip to content

Instantly share code, notes, and snippets.

@mizchi
Created April 9, 2024 03:28
Show Gist options
  • Save mizchi/e4801fd55760f010893cd7fa80f04289 to your computer and use it in GitHub Desktop.
Save mizchi/e4801fd55760f010893cd7fa80f04289 to your computer and use it in GitHub Desktop.

This is translation of https://zenn.dev/mizchi/articles/introduce-moonbit from japanese


MoonBit: A Prototype Ideal for the WebAssembly Era

Recently, I came across a language called MoonBit, and the more I looked into it, the more I liked it, so I'd like to introduce it.

https://www.moonbitlang.com/

Grammatically, it's like Rust with garbage collection, and it compiles to WebAssembly. It seems particularly designed for running on CDN Edge Workers, which I love.

Note: Since it's a young language, the language specifications are expected to change significantly. This information is accurate as of now.

tl;dr

  • Pros
    • Can mostly be seen as Rust with garbage collection
      • Easy to catch up on the syntax
      • No need to worry about the complexity of lifetimes
    • Already has an ecosystem with a VS Code extension and package manager
  • Cons
    • Still unstable / source code might not be published for a while
    • Currently lacks learning resources and packages, requiring significant effort from the writer

Introduction: Dissatisfaction with JS/TS/Rust

I've been searching for an alternative language to TypeScript for front-end use.

The reason being, I believe there are inherent limitations to extending TypeScript, which is based on JavaScript. These are some issues I've always had:

  • No distinction between integers and floating-point numbers; the Number type is the standard
    • This makes it difficult to compile for wasm or integrate with vanilla JS
    • Performance optimization requires understanding the quirks of JIT
  • Despite having a type system, it's discarded at transpilation time, not utilized by the compiler, which is a waste
  • Due to backward compatibility, old syntax cannot be discarded, leading to some rigid specifications
    • TC39 is doing a great job, but still...
  • No pattern matching
  • Using objects as substitutes for records, resulting in forced practices like adding a type: "datatype" property to everything

Rust would be a better foundation for extensions. However, it can be pretty tough to use for application layers like GUIs.

To understand the difficulty of Rust Wasm, let's look at an example using requestAnimationFrame.

#[wasm_bindgen(start)]
fn run() -> Result<(), JsValue> {
    let f = Rc::new(RefCell::new(None));
    let g = f.clone();
    let mut i = 0;
    *g.borrow_mut() = Some(Closure::new(move || {
        if i > 300 {
            body().set_text_content(Some("All done!"));
            let _ = f.borrow_mut().take();
            return;
        }
        i += 1;
        let text = format!("requestAnimationFrame has been called {} times.", i);
        body().set_text_content(Some(&text));
        request_animation_frame(f.borrow().as_ref().unwrap());
    }));
    request_animation_frame(g.borrow().as_ref().unwrap());
    Ok(())
}

This isn't to say Rust is a bad language, but in front-end GUIs with frequent event handlers, the complexity and nuisance of lifetime management are glaringly apparent.

I've previously discussed this in a stream with koba789 on YouTube.

https://www.youtube.com/watch?v=Cij3CUJmLXI

Languages like Zig or Grain aren't bad for wasm, but they're not primarily focused on wasm, so there were some impracticalities.


So, let's try using MoonBit

Installation

You can try it on VS Code in the browser.

https://try.moonbitlang.com/

For local installation:

https://www.moonbitlang.com/download/

Installing the VS Code extension allows for smooth local development.

https://marketplace.visualstudio.com/items?itemName=moonbit.moonbit-lang

The official completion extension is also useful, incorporating concepts like local and global sampling at the language level for Copilot-like completions.

https://marketplace.visualstudio.com/items?itemName=moonbit.moonbit-ai

Quick MoonBit Tutorial

Let's create and run a project.

$ moon new hello
$ cd hello
$ moon run main
hello, world

Now, let's write in main/main.mbt.

It's mostly like Rust. There are no lifetime traits since it's a GC language.

// function
fn add(a: Int, b: Int) -> Int {
  return a + b
}

// generics
fn self[T](v: T) -> T {
  return v
}

// generics trait
fn _lge[X : Compare](a: X, b: X) -> Bool {
  return a >= b
}

// data structure
enum T {
  A
  B
}

struct Point {
  x: Int
  y: Int
} derive(Debug)

fn distance(self: Point, other: Point) -> Double {
  sqrt((self.x * other.x + self.y * other.y).to_double())
}

// Generics and derived trait
struct Point3d[N] {
  x: N
  y: N
  z: N
} derive(Debug)

// newtype
// type Point3dInt Point3d[Int]

// trait
trait Animal {
  speak(Self) -> Unit
}
struct Duck {
  name: String
}
fn speak(self: Duck) -> Unit {
  let name = self.name
  println("\(name): Quak!")
}

fn main {
  println("Hello, World!")

  // variable
  let _x = "hello"
  let mut y: Int = 2

  let _multiline_text =
    #| hello
    #| world
    #| multiline

  y = 3
  let p = Point::{x: 1, y: 2}
  println(p.distance(Point::{x: 3, y: 4}))
  debug(p)

  // function and call
  let _ = add(1, 2)
  // pipeline
  let _ = 1 |> add(2) |> add(4)

  // call ./foo.mbt: pub fn foo() -> Int { 1 }
  let _ = foo()

  // list and iterator
  let list = [1, p.x, p.y]
  let mapped = list.map(fn (x) -> Int { return x + 1 })
  println(mapped)

  // inference
  let _: Int = self(1) // as Int

  // trait
  let duck = Duck::{name: "Donald"} as Animal
  duck.speak()

  // if else
  if y == 2 {
    println("y is 1")
  } else {
    println("y is not 1")
  }

  // match
  let mut m = T::A
  m = T::B
  let _ = match m {
    T::A => 1
    T::B => 2
  }

  // for
  for i = 1; i < 5; i = i + 1 {
    print(i)
  }
  println("")

  // while
  let mut i = 0
  while i > 0 {
    i = i - 1
  }

  // scope
  {
    let x = 1
    println(x)
  }
}

// inline test
test {
  let a = 1
  let b = 2
  let c = add(a, b)
  @assertion.assert_eq(c, 3)?
}

Apart from differences in macros, derive, and generics, it's quite similar to Rust. Syntax highlighting also borrows from Rust.

The structure, pattern matching, and pipeline operator feel right. Unlike TypeScript, if it compiles, it runs, giving a sense of security. The completions also feel great once you write in it.

Of course, MoonBit's language features are not limited to this, but if you're familiar with other programming languages, this should give you a basic understanding.

Wasm Compilation: Use in the Browser

Since MoonBit compiles to wasm, it can be executed in the browser.

$ moon build
<html lang="en">

<body>
  <canvas id="canvas" width="150" height="150"></canvas>
</body>
<script>
  let memory;
  const [log, flush] = (() => {
    let buffer = [];
    function flush() {
      if (buffer.length > 0) {
        console.log(new TextDecoder("utf-16").decode(new Uint16Array(buffer).valueOf()));
        buffer = [];
      }
    }
    function log(ch) {
      if (ch == '\n'.charCodeAt(0)) { flush(); }
      else if (ch == '\r'.charCodeAt(0)) { /* noop */ }
      else { buffer.push(ch); }
    }
    return [log, flush]
  })();

  const importObject = {
    spectest: {
      print_char: log
    },
    js_string: {
      new: (offset, length) => {
        const bytes = new Uint16Array(memory.buffer, offset, length);
        const string = new TextDecoder("utf-16").decode(bytes);
        return string
      },
      empty: () => { return "" },
      log: (string) => { console.log(string) },
      append: (s1, s2) => { return (s1 + s2) },
    }
  };

  WebAssembly.instantiateStreaming(fetch("/target/wasm/release/build/main/main.wasm"), importObject).then(
    (obj) => {
      memory = obj.instance.exports["moonbit.memory"];
      obj.instance.exports._start();
      flush();
    }
  )
</script>

</html>

MoonBit for Troubleshooting

When in doubt, the official examples usually cover most scenarios.

https://github.com/moonbitlang/moonbit-docs/tree/main/examples

Discussions often provide a wealth of information.

https://discuss.moonbitlang.com

awesome-moonbit doesn't have a lot yet, but it's worth checking out.

https://github.com/moonbitlang/awesome-moonbit

The Appeal of MoonBit

MoonBit is strongly focused on Rust, AI, and Wasm for Edge Workers. I'm already a fan of this direction.

https://www.moonbitlang.com/blog/moonbit-ai

AI-integrated completion is an interesting feature, and the toolchain is already well-established, making it pleasant to write in.

Complaints About MoonBit

Lack of Libraries

Firstly, there are simply not enough packages yet, which is understandable given its newness.

https://mooncakes.io/

Module System

The explicit scoping in moon.pkg.json or moon.mod.json means there's no direct equivalent to JS's import or Rust's use, which can make file scopes a bit challenging to grasp.

Ineffective AI Completions

Despite the hype around AI-powered completions, they often lean too heavily on Rust, leading to irrelevant suggestions. Consequently, I've turned off Copilot.

{
  "github.copilot.editor.enableCodeActions": false,
  "github.copilot.editor.enableAutoCompletions": false
}

Looking Forward

MoonBit is still too new, and I haven't used it for large projects outside of game-like scenarios with few assumptions. However, the potential is exciting, especially for technologies with minimal prerequisites like CDN edge workers, making them ideal for new tech.

Official examples include CF Worker demos.

https://github.com/moonbitlang/moonbit-docs/tree/main/examples/cf_worker

pub fn fib(n : Int) -> Int64 {
  loop 0L, 1L, n {
    a, _, 0 => a
    a, b, n => continue b, a + b, n - 1
  }
}
import wasm from '../target/wasm-gc/release/build/hello.wasm';
const module = await WebAssembly.instantiate(wasm);
module.exports._start();
export interface Env {}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		return new Response(`Hello World! ${module.exports.fib(10)}`);
	}
};

Currently, it seems limited to numerical computations, but as the ecosystem grows, broader applications should become feasible. Asynchronous runtime support seems necessary as well.

Despite potential significant changes on the horizon, MoonBit has the potential to dominate its niche, a future I'm eagerly anticipating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment