MoonBit:Go 开发者入门指南#

MoonBit 是一种为云和边缘计算设计的现代编程语言。如果您是 Go 开发者,您会发现一些熟悉的概念以及强大的新特性,这些特性使 MoonBit 成为一种简单、富有表现力且高性能的语言。

共通之处#

Go 和 MoonBit 都具有以下特点:

  • 静态类型,支持类型推断

  • 编译型语言,编译速度快

  • 内存安全,尽管机制略有不同

  • 为现代计算设计,拥有出色的工具链

区别一览#

方面

Go

MoonBit

范式

命令式,带有一些函数式特性

函数式和命令式兼备

内存管理

垃圾回收

引用计数/垃圾回收(取决于后端)

错误处理

多返回值

带检查的抛出错误的函数

泛型

接口和类型参数

完整的泛型系统,支持特征(类似于 Rust)

模式匹配

有限(switch 语句)

全面的模式匹配

目标平台

原生二进制文件

WebAssembly、JavaScript、原生二进制文件(通过 C 或 LLVM)

标识符和命名约定#

在 Go 中,标识符区分大小写,必须以 Unicode 字母或下划线开头,后跟任意数量的 Unicode 字母、Unicode 数字或下划线。

由于 Go 标识符的首字母决定了其可见性(大写表示公共,小写表示私有),因此约定使用 camelCase 表示私有项,使用 PascalCase 表示公共项:

type privateType int
var PublicVariable PublicType = PublicFunction()
privateVariable := privateFunction()

在 MoonBit 中,标识符也区分大小写,并且遵循与 Go 非常相似的规则集,但首字母的大小写对可见性没有影响。相反,小写首字母应用于变量和函数,而大写首字母保留用于类型、特征、枚举变体等。

因此,MoonBit 的约定是前者使用 snake_case,后者使用 CamelCase:

Enumeration::Variant(random_variable).do_something()
impl[T : Trait] for Structure[T] with some_method(self, other) { ... }

变量绑定#

在 Go 中,使可用 var:= 创建新的绑定。类型推断通过 := 语法或使用 var 时省略类型注解来激活。无法将变量标记为不可变。

var name string = "MoonBit"
var count = 25 // 或 `count := 25`
// Go 中没有不可变变量

在 MoonBit 中,使用 let 关键字创建新的绑定。它们默认是不可变的,您可以使用 let mut 创建可变绑定。类型可以在变量名后使用 : 可选地指定,省略时使用类型推断。

let mut name : String = "MoonBit"
let mut count = 25
let pi = 3.14159 // 省略 `mut` 创建不可变绑定

注意 mut 只允许用于局部绑定,不允许用于全局绑定。

Newtype#

Newtype 用于创建现有类型的类型安全包装器,以便您可以定义具有与原始类型相同底层表示但具有不同可用操作集的领域特定类型。

在 Go 中,可以使用 type 关键字创建 newtype。通过 T() 转换语法,可以在底层值和 newtype 包装值之间进行往返转换:

type Age int

age := Age(25)
ageInt := int(age)

在 MoonBit 中,newtype 的定义方式相同,但获取底层值需要稍微不同的语法:

type Age Int

let age = Age(25)
let age_int = age._

类型别名#

在 Go 中,您可以使用 type ... = ... 语法创建类型别名:

type Description = string

在 MoonBit 中,使用 typealias 关键字代替:

typealias String as Description

结构体#

在 Go 中,命名结构体是匿名结构体 struct { ... } 的 newtype,因此常见的用法是 type ... struct { ... }

type Person struct {
    Name string
    Age  int
}

可以使用字面量创建 Person 结构体,如下所示:

john := Person{
    Name: "John Doe",
    Age:  30,
}

// 如果字段顺序一致,可以省略字段名:
alice := Person{"Alice Smith", 25}

在 MoonBit 中,所有结构体都必须命名,并使用 struct 关键字定义:

struct Person {
  name : String
  age : Int
}

可以使用字面量创建 Person 结构体,如下所示:

let john = Person::{ name: "John Doe", age: 30 }

// 如果可以推断类型,可以省略类型名:
let alice : Person = { name: "Alice Smith", age: 25 }

枚举#

枚举允许您定义具有固定值集合的类型。

在 Go 中,枚举不是语言特性,而是使用 iota 创建一系列常量的惯用法:

type Ordering int
const (
    Less Ordering = iota
    Equal
    Greater
)

另一方面,MoonBit 内置了 enum 关键字用于定义枚举:

enum Ordering {
  Less
  Equal
  Greater
}

此外,MoonBit 的枚举还可以携带载荷:

enum IntList {
  /// `Nil` 表示空列表,没有载荷。
  Nil
  /// `Cons` 表示非空列表,有两个载荷:
  /// 1. 列表的第一个元素;
  /// 2. 列表的其余部分。
  Cons(Int, IntList)
}

控制流#

MoonBit 与 Go 的一个主要区别在于,许多控制结构具有实际返回值,而不仅仅是执行代码的语句。这种以表达式为中心的方法与 Go 基于语句的控制流相比,允许更简洁和函数式的编程模式。

if 表达式#

在 Go 中,if 语句不返回值。因此,您通常需要这样写:

var result string
if condition {
    result = "true case"
} else {
    result = "false case"
}

在 MoonBit 中,if 是一个返回值的表达式:

let result = if condition {
  "true case"
} else {
  "false case"
}

match 表达式#

在 Go 中,switch 语句可用于匹配值,但它们不直接返回值:

var description string
switch err {
case nil:
    description = fmt.Sprintf("Success: %v", value)
default:
    description = fmt.Sprintf("Error: %s", err)
}

另一方面,MoonBit 中的 match 表达式可以返回值:

let description = match status {
  Ok(value) => "Success: \{value}"
  Err(error) => "Error: \{error}"
}

有关 match 表达式的更多详细信息,请参阅 模式匹配

loop 表达式#

MoonBit 的循环也可以返回值。

使用 loop 关键字的函数式循环功能强大。循环体类似于 match 表达式,每个分支尝试匹配循环变量并执行对应操作。您可以使用 continue 关键字以给定的循环值开始下一次迭代,或者使用 break 关键字以给定的输出值退出循环。在每个分支的尾部表达式中,break 是隐式的,因此不需要写出。

// 计算 `xs : IntList` 中所有元素的总和。
let sum = loop xs, 0 {
  Nil, acc => acc
  Cons(x, rest), acc => continue rest, x + acc
}

forwhile 表达式#

MoonBit 的 forwhile 循环也是返回值的表达式。

for 循环类似于 Go 的 for 循环,分别包含变量初始化、条件和更新子句:

// 对从 1 到 6 的偶数求和。
let sum = for i = 1, acc = 0; i <= 6; i = i + 1 {
  if i % 2 == 0 {
    continue i + 1, acc + i
  }
} else {
  acc
}

然而,MoonBit 的 for 循环有一些独特的特性:

  • 更新子句不是就地进行的,而是用于为循环变量分配新值。

  • continue 可以(可选地)用于使用新的输入值开始下一次迭代。在这种情况下,更新子句将被跳过。

  • else 子句用于在循环正常退出时返回循环的最终值。如果循环使用 break 关键字提前退出,则返回 break 子句中的值。

while 循环等同于只有条件子句的 for 循环,它也可以返回值:

let result = while condition {
  // 循环体
  if should_break {
    break "early exit value"
  }
} else {
  "normal completion value"
}

泛型类型#

在 Go 中,您可以使用方括号 [] 界定的类型参数定义泛型命名结构体:

type ListNode[T any] struct {
    val  T
    next *ListNode[T]
}

在 MoonBit 中,您会以非常相似的方式定义泛型结构体:

struct ListNode[T] {
  val : T
  next : ListNode[T]?
}

此外,您还可以定义泛型 MoonBit 枚举。以下是 MoonBit 标准库中提供的两个常见的泛型枚举:

enum Option[T] {
  None
  Some(T)
}

enum Result[T, E] {
  Ok(T)
  Err(E)
}

函数#

在 Go 中,函数使用 func 关键字定义,后跟函数名、参数和返回类型。

func add(a int, b int) int {
    return a + b
}

在 MoonBit 中,函数定义使用 fn 关键字和稍微不同的语法:

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

注意使用 : 标记指定类型,使用 -> 标记指示返回类型。此外,函数体是一个返回值的表达式,因此除非需要提前退出,否则不需要 return 关键字。

值语义和引用语义#

在不同编程语言之间切换时,理解数据如何传递和存储至关重要。特别是 Go 和 MoonBit,因为两者在值语义和引用语义方面略有不同。

在 Go 中,值语义是默认的。也就是说,值在传递给函数或赋值给变量时会被复制:

type Point struct {
    X int
    Y int
}

func modifyPointVal(p Point) {
    p.X = 100  // 这修改的是副本,不是原始值
}

func main() {
    point := Point{X: 10, Y: 20}
    modifyPointVal(point)
    fmt.Println(point.X) // 仍然打印 10,而不是 100
}

为了在 Go 中实现引用语义,您通常需要显式创建和解引用指针:

func modifyPointRef(p *Point) {
    p.X = 100  // 这通过指针修改原始值
}

func main() {
    point := Point{X: 10, Y: 20}
    modifyPointRef(&point)  // 使用 `&` 运算符创建指针
    fmt.Println(point.X)    // 打印 100
}

Go 中的一些其他内置类型,如切片和映射,其行为类似于指针,但所有这些类型在技术上仍然是通过值传递的(值是一个引用):

func incrementSlice(nums []int) {
    for i := range nums {
        nums[i]++  // 修改原始切片
    }
}

func modifyMap(m map[string]int) {
    m["key"] = 42  // 修改原始映射
}

MoonBit 在语义上总是按引用传递。但对于不可变类型和基本类型,它们可以按值传递,因为这在语义上是相同的,这纯粹是一种优化。

MoonBit 中值得注意的基本类型包括 UnitBoolean、整数(IntInt64UInt 等)、浮点数(DoubleFloat 等)、StringCharByte

MoonBit 中值得注意的不可变集合类型包括 元组、不可变集合(例如 @immut/hashset.T[A])以及没有 mut 字段的自定义类型。

另一方面,值得注意的可变集合类型包括可变集合(例如 Array[T]FixedArray[T]Map[K, V])以及至少包含一个 mut 字段的自定义类型。

例如,我们可以用 MoonBit 重写上面的 Go 示例代码:

struct Point {
  mut x : Int
  mut y : Int
}

fn modify_point_ref(p : Point) -> Unit {
  p.x = 100 // 修改原始结构体
}

fn main {
  let point = Point::{ x: 10, y: 20 }
  modify_point_ref(point) // 通过引用传递原始结构体
  println("\{point.x}")   // 打印 100
}

fn increment_array(nums : Array[Int]) -> Unit {
  for i = 0; i < nums.length(); i = i + 1 {
    nums[i] += 1 // 修改原始数组
  }

Ref[T] 辅助类型#

当您需要对值类型进行显式可变引用时,MoonBit 提供了 Ref[T] 类型,其大致定义如下:

struct Ref[T] {
  mut val : T
}

借助 Ref[T],您可以像在 Go 中使用指针一样创建对值的可变引用:

fn increment_counter(counter : Ref[Int]) -> Unit {
  counter.val = counter.val + 1
}

fn main {
  let counter = Ref::new(0)
  increment_counter(counter)
  println(counter.val) // 打印 1
}

泛型函数#

在 Go 中,您可以使用类型参数定义泛型函数:

// `T` 是一个类型参数,必须实现 `fmt.Stringer` 接口。
func DoubleString[T fmt.Stringer](t T) string {
    s := t.String()
    return s + s
}

MoonBit 也是如此:

// `T` 是一个类型参数,必须实现 `Show` 特征。
fn[T : Show] double_string(t : T) -> String {
  let s = t.to_string()
  s + s
}

命名参数#

MoonBit 函数还支持使用 label~ : Type 语法定义带有可选默认值的命名参数:

fn named_args(named~ : Int, optional~ : Int = 42) -> Int {
  named + optional
}

// 可以这样调用:
named_args(named=10)               // optional 默认为 42
named_args(named=10, optional=20)  // optional 被设置为 20
named_args(optional=20, named=10)  // 顺序无关紧要
let named = 10
named_args(named~)                 // `label~` 是 `label=label` 的简写

可选返回值#

对于可能或可能不逻辑返回类型 T 值的函数,Go 鼓励使用多返回值:

特别是,(res T, ok bool) 用于指示可选返回值:

func maybeDivide(a int, b int) (quotient int, ok bool) {
    if b == 0 {
        return 0, false
    }
    return a / b, true
}

在 MoonBit 中,要返回可选值,您只需返回 T?Option[T] 的简写):

fn maybe_divide(a : Int, b : Int) -> Int? {
  if b == 0 {
    None
  } else {
    Some(a / b)
  }
}

可能失败的函数#

对于可能返回错误的函数,Go 在返回位置使用 (res T, err error)

func divide(a int, b int) (quotient int, err error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

然后您可以使用上述 divide 函数和常见的 if err != nil 惯用法:

func useDivide() error {
    q, err := divide(10, 2)
    if err != nil {
        return err
    }
    fmt.Println(q) // 使用商
    return nil
}

在 MoonBit 中,可能失败的函数的声明方式略有不同。要指示函数可能抛出错误,请在返回类型位置写入 T!E,其中 E 是使用 type! 声明的错误类型:

type! ValueError String

fn divide(a : Int, b : Int) -> Int!ValueError {
  if b == 0 {
    raise ValueError("division by zero")
  }
  a / b
}

调用此类抛出错误的函数时,有不同的错误处理方式:

// 选项 1:直接传播错误。
fn use_divide_propagate() -> Unit!ValueError {
  let q = divide(10, 2) // 如果发生错误,重新抛出
  println(q) // 使用商
}

// 选项 2:使用 `try?` 将错误转换为 `Result[T, E]` 类型。
fn use_divide_try() -> Unit!ValueError {
  let mq : Result[Int, ValueError] = // 类型注解是可选的
    try? divide(10, 2)
  match mq { // 有关模式匹配的更多详细信息,请参阅相关章节
    Err(e) => raise e
    Ok(q) => println(q) // 使用商
  }
}

// 选项 3:使用 `try { .. } catch { .. }` 语法处理错误。
fn use_divide_try_catch() -> Unit!ValueError {
  try {
    let q = divide(10, 2)
    println(q) // 使用商
  } catch {
    e => raise e
  }
}

模式匹配#

Go 没有内置的模式匹配支持。在某些情况下,您可以使用 switch 语句实现类似的功能:

func fibonacci(n int) int {
    switch n {
    case 0:
        return 0
    case 1, 2:
        return 1
    default:
        return fibonacci(n-1) + fibonacci(n-2)
    }
}

另一方面,MoonBit 使用 match 关键字提供了全面的模式匹配。

您可以匹配布尔值、整数、字符串等字面量:

fn fibonacci(n : Int) -> Int {
  match n {
    0 => 0
    // `|` 组合多个模式
    1 | 2 => 1
    // `_` 是匹配任何内容的通配符模式
    _ => fibonacci(n - 1) + fibonacci(n - 2)
  }
}

此外,还可以对结构体和元组进行解构:

struct Point3D {
  x : Int
  y : Int
  z : Int
}

fn use_point3d(p : Point3D) -> Unit {
  match p {
    { x: 0, .. } => println("x == 0")
    // `if` 守卫允许您添加额外条件
    // 以匹配此分支。
    { y, z, .. } if y == z => println("x != 0, y == z")
    _ => println("uncategorized")
  }
}

最后,数组模式允许您轻松解构数组、字节、字符串和视图:

fn categorize_array(array : Array[Int]) -> String {
  match array {
    [] => "empty"
    [x] => "only=\{x}"
    [first, .. middle, last] =>
      "first=\{first} and last=\{last} with middle=\{middle}"
  }
}

fn is_palindrome(s : @string.View) -> Bool {
  match s {
    [] | [_] => true
    // `a` 和 `b` 捕获视图的第一个和最后一个字符,并且
    // `.. rest` 将视图的中间部分捕获为一个新视图。
    [a, .. rest, b] if a == b => is_palindrome(rest)
    _ => false
  }
}

方法和特征#

尽管两种语言都支持通过特征/接口实现方法和行为共享,但 MoonBit 处理方法和特征/接口的方式与 Go 显著不同。

如下所示,MoonBit 在特征方法方面比 Go 具有更大的灵活性和表达能力,因为在 MoonBit 中:

  • 每个类型必须显式实现特征;

  • 类型的 method 不一定是对象安全的(即可以在特征对象中使用),实际上,它们甚至不需要将 self 作为第一个参数。

方法#

在 Go 中,方法使用接收者语法在类型上定义:

type Rectangle struct {
    width, height float64
}

func (r *Rectangle) Area() float64 {
    return r.width * r.height
}

func (r *Rectangle) Scale(factor float64) {
    r.width *= factor
    r.height *= factor
}

可以使用 .method() 语法调用方法:

rect := Rectangle{width: 10.0, height: 5.0}
areaValue := rect.Area()
rect.Scale(2.0) // 注意:`.Scale()` 会就地修改 `rect`。

然而,在 MoonBit 中,类型 T 的方法只是使用 T:: 前缀定义的函数。

以下是如何在 MoonBit 中重新创建上面的 Rectangle 示例:

struct Rectangle {
  // `mut` 允许这些字段就地修改。
  mut width : Double
  mut height : Double
}

fn Rectangle::area(self : Rectangle) -> Double {
  self.width * self.height
}

fn Rectangle::scale(self : Rectangle, factor : Double) -> Unit {
  // 注意:此处 `self` 具有引用语义,因为 `Rectangle` 是可变的。
  self.width *= factor
  self.height *= factor
}

… 您可以像这样调用方法:

let rect = Rectangle::{ width: 10.0, height: 5.0 }
let area_value = rect.area()
rect.scale(2.0) // 注意:`.scale()` 会就地修改 `rect`。

特征#

Go 使用接口实现多态:

type Shape interface {
    Area() float64
    Perimeter() float64
}

// 为 `Rectangle` 隐式实现 `Shape` 接口
// `func (r *Rectangle) Area() float64` 已存在
func (r *Rectangle) Perimeter() float64 {
    return 2 * (r.width + r.height)
}

// 使用带有类型约束的泛型函数可以实现静态分派
func PrintShapeInfo[T Shape](s T) {
    fmt.Printf("Area: %f, Perimeter: %f\n", s.Area(), s.Perimeter())
}

// 使用接口类型可以实现动态分派
func PrintShapeInfoDyn(s Shape) {
    PrintShapeInfo(s)
}

MoonBit 拥有特征,类似于 Go 接口:

trait Shape {
  area(Self) -> Double
  perimeter(Self) -> Double
}

// 为 `Rectangle` 显式实现 `Shape` 特征
impl Shape for Rectangle with area(self) {
  // 注意:这是对先前定义的 `Rectangle::area()` 的方法调用,
  // 因此不涉及递归。
  self.area()
}

impl Shape for Rectangle with perimeter(self) {
  2.0 * (self.width + self.height)
}

// 使用带有类型约束的泛型函数可以实现静态分派
fn[T : Shape] print_shape_info(shape : T) -> Unit {
  println("Area: \{shape.area()}, Perimeter: \{shape.perimeter()}")
}

// 使用 `&Shape` 特征对象类型可以实现动态分派
fn print_shape_info_dyn(shape : &Shape) -> Unit {
  print_shape_info(shape)
}

test {
  let rect = Rectangle::{ width: 10.0, height: 5.0 }
  print_shape_info(rect)
  print_shape_info_dyn(rect)
}

对象安全#

MoonBit 的特征还可以包含 Go 接口中没有的某些类型的方法,例如下面示例中所示的没有 self 参数的方法:

trait Name {
  name() -> String
}

impl Name for Rectangle with name() {
  "Rectangle"
}

// `T : Shape + Name` 是一个约束,要求类型 `T` 实现 `Shape` 和 `Name`。
fn[T : Shape + Name] print_shape_name_and_info(shape : T) -> Unit {
  println(
    "\{T::name()}, Area: \{shape.area()}, Perimeter: \{shape.perimeter()}",
  )
}

test {
  print_shape_name_and_info(Rectangle::{ width: 10.0, height: 5.0 })
}

然而,要使特征可以在特征对象中使用,它必须只包含对象安全的方法。

类型 T 的方法要实现对象安全,需要满足以下几个要求:

  • self : T 应该是方法的第一个参数;

  • 方法的任何其他参数不应具有类型 T

例如,在上面的 Name 特征中,name() 方法不是对象安全的,因为它没有 self 参数,因此 Name 不能在特征对象中使用,即不存在 &Name 类型。

特征扩展#

MoonBit 的特征可以显式地扩展其他特征,从而允许您在现有功能的基础上进行构建:

pub(open) trait Position {
  pos(Self) -> (Int, Int)
}

pub(open) trait Draw {
  draw(Self, Int, Int) -> Unit
}

pub(open) trait Object: Position + Draw {
  // 你可以在这里添加更多必需的方法...
}

由于 Object 特征扩展了 PositionDraw 这两个特征,因此后两者被称为前者的超特征

默认实现#

与 Go 接口不同,MoonBit 的特征函数可以有默认实现:

trait Printable {
  print(Self) -> Unit
  // `= _` 标记该方法具有默认实现
  print_twice(Self) -> Unit = _
}

// `print_twice()` 的默认实现单独提供:
impl Printable with print_twice(self) {
  self.print()
  self.print()
}

运算符重载#

MoonBit 通过内置特征支持运算符重载,这在 Go 中没有对应的功能:

impl Add for Rectangle with op_add(self, other) {
  { width: self.width + other.width, height: self.height + other.height }
}

// 现在你可以对 `Rectangle` 使用 + 运算符了
let combined = rect1 + rect2

导入和包管理#

Go 和 MoonBit 的包管理和导入方式不同。

创建项目#

在 Go 中,创建新项目的第一件事是运行:

$ go mod init example.com/my-project

这将初始化一个 go.mod 文件,用于跟踪项目的依赖关系。

然后您可以创建一个包含 package main 声明的 main.go 文件并开始编写代码:

package main

import "fmt"

func main() { fmt.Println("Hello, 世界") }

在 MoonBit 中,创建新项目要容易得多。只需运行 moon new 命令即可设置项目:

$ moon new my-project

项目结构#

Go 工具链对项目结构的要求很少,除了 go.mod 文件必须位于项目的根目录。要从单个 main.go 文件扩展到更大的项目,您通常会添加更多文件和目录,形成扁平或嵌套结构,具体取决于您选择的风格。

在组织源文件时,关键在于 Go 的工具链不区分同一目录下的源文件,因此您可以在同一个包目录中自由创建多个 .go 文件,因为工具链会将它们视为一个整体。但是,对于另一个目录的源文件中的定义,您需要在导入它们后才能使用。

另一方面,MoonBit 中 moon new 提供的默认项目结构更加有组织,如下所示:

my-project
├── LICENSE
├── README.md
├── moon.mod.json
└── src
    ├── lib
    │   ├── hello.mbt
    │   ├── hello_test.mbt
    │   └── moon.pkg.json
    └── main
        ├── main.mbt
        └── moon.pkg.json

这展示了 MoonBit 中典型的“二进制和库”项目结构,位于 src 目录中。这在 moon.mod.json 中声明如下(省略了不相关的部分):

{
  "source": "src"
}

这是模块配置文件,也注册了项目的基本信息,例如名称、版本和依赖项。

源目录(本例中为 src)下的每个目录都是一个包,拥有自己的 moon.pkg.pkg.json 文件,其中包含包特定的元数据,例如其导入以及是否应将其视为主要二进制包。例如,src/lib/moon.pkg.json 的最小定义如下:

{}

src/main/moon.pkg.json 如下所示:

{
  "is_main": true,
  "import": ["username/hello/lib"]
}

与 Go 类似,MoonBit 将同一包目录下的所有 .mbt 文件视为一个整体。但是,当为更多源文件创建新目录时,该目录下需要相应的 moon.pkg.json 文件。

运行项目#

要运行上述 Go 项目,您通常会使用:

$ go run main.go

使用 moon run 运行之前的 MoonBit 项目非常相似:

$ moon run src/main/main.mbt

添加导入#

在 Go 中,您可以使用 import 子句后跟模块路径来添加导入:

package main

import (
    "github.com/user/repo/sys"
)

MoonBit 使用不同的方法,moon.mod.json 用于模块配置,moon.pkg.json 用于包配置。

首先,在 moon.mod.json"deps" 部分声明依赖项。这通常通过 moon add <package> 命令完成。

例如,要使用 moonbitlang/x,您可以运行:

$ moon add moonbitlang/x

… 这将生成一个 moon.mod.json 文件,如下所示(省略了不相关的部分):

{
  "deps": {
    "moonbitlang/x": "*"
  }
}

然后,在您的包的 moon.pkg.json 中,在 "import" 部分指定要导入的包:

{
  "import": ["moonbitlang/x/sys"]
}

现在您应该可以在此 MoonBit 包中使用 sys 包了。

使用导入的包#

在 Go 中,上述导入允许您使用 <package-name>. 前缀访问 sys 包的 API(实际 API 是假设的):

func main() {
    sys.SetEnvVar("MOONBIT", "Hello")
}

在 MoonBit 中,您使用 @<package-name>. 前缀访问导入的 API:

fn main {
  @sys.set_env_var("MOONBIT", "Hello")
}

包别名#

在 Go 中,您可以使用 import 语句为导入的包创建别名:

import (
    system "github.com/user/repo/sys"
)

在 MoonBit 中,您可以在 moon.pkg.json 中使用 alias 字段为导入的包创建别名:

{
  "import": [
    {
      "path": "moonbitlang/x/sys"
      "alias": "system"
    }
  ]
}

然后您可以使用别名,如下所示:

fn main {
  @system.set_env_var("MOONBIT", "Hello")
}

访问控制#

在 Go 中,可见性由标识符首字母的大小写决定(大写表示公共,小写表示私有)。

MoonBit 比 Go 具有更细粒度的访问控制,提供以下可见性级别:

  • priv:完全私有(类似于 Go 的小写标识符)

  • 默认(抽象):只有类型名称可见,实现隐藏

  • pub:包外部只读访问

  • pub(all):完全公共访问(类似于 Go 的大写标识符)

这使您可以精细控制 API 的哪些部分暴露以及如何使用它们。

运行时支持#

相同的 MoonBit 代码可以针对具有不同代码生成后端的多个运行时,从而允许您为特定应用程序选择最合适的后端:

  • WebAssembly(用于 Web 和边缘计算)

  • JavaScript(用于 Node.js 集成)

  • C(用于原生性能)

  • LLVM(实验性)

内存管理#

Go 使用垃圾回收器,而 MoonBit 根据所使用的代码生成后端采用不同的策略:

  • Wasm/C 后端:引用计数,不带循环检测

  • Wasm GC/JavaScript 后端:利用运行时的垃圾回收器

开始编写 MoonBit#

  1. 访问 在线演练场

  2. 查看我们的 安装指南

  3. 创建你的第一个 MoonBit 项目:

    $ moon new hello-world
    $ cd hello-world
    $ moon run
    

何时选择 MoonBit#

MoonBit 以函数式编程优势和 WebAssembly 优先设计为系统编程带来了新的视角。虽然与 Go 的理念不同,但它提供了强大的工具来构建高效、安全和可维护的应用程序。因此,如果您重视以下几个方面,MoonBit 将是您构建项目的不二之选:

  • WebAssembly 目标,具有最小的体积和最大的性能

  • 函数式编程特性,如模式匹配和代数数据类型

  • 数学/算法代码,得益于默认的不可变性

  • 强类型安全,以及全面的错误处理机制

下一步#