Menu

Zero Shape:定义类似结构体的记录类型

shape 是 Zero 中类似结构体的积类型。本文结合官方示例,介绍如何声明 shape、构造值、读取字段,以及在函数之间传递它们。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

声明 shape

shape 是一种带有具名、类型化字段的记录类型:

shape Point {
    x: i32,
    y: i32,
}

各个部分:

  • shape 用来开始声明。
  • Point 是类型的名字。
  • 花括号里是字段列表,每个字段写作 name: Type。

这个声明会在当前作用域中添加一个名为 Point 的新类型。凡是能用 i32 这类内置类型的地方,现在也都能用 Point。

构造 shape 值

用结构体字面量表达式创建实例:

let point = Point { x: 40, y: 2 }

字面量写出 shape 的名字,并给每个字段赋值。每个字段都必须出现:Zero 不会悄悄把缺失的字段默认设为零或 null。如果你漏了一个,编译器会告诉你:

{
    "code": "FLD002",
    "message": "missing field: y",
    "line": 4
}

(具体的错误码可能不同;“没有隐式默认值”这条原则是不变的。)

如果你希望类型一目了然,也可以显式标注:

let point: Point = Point { x: 40, y: 2 }

读取字段

访问字段使用点语法:

let point = Point { x: 40, y: 2 }
let xVal  = point.x
let yVal  = point.y

point.x 读取 x 字段。没有 get_x() 方法,字段就是普通的数据。

一个完整示例

这是语言仓库中经典的 point.0 示例,点击“运行”试试:

从上往下看一遍:

  1. 声明一个带有两个 i32 字段的 Point shape。
  2. 定义一个 sum 函数,它接收一个 Point,返回字段之和。
  3. 在 main 中构造一个 Point,调用 sum,再比较结果。

三个步骤,对应 shape 相关的三个概念(声明、构造、访问),再加一个副作用:末尾的 check world.out.write(...)。注意 sum 没有接触 World。它是一个作用于数据的纯函数,签名就清楚地表明了这一点。

带嵌套字段的 shape

shape 可以保存任何类型的值,包括其他 shape:

shape Range {
    start: i32,
    end: i32,
}

shape Segment {
    label: String,
    range: Range,
}

let seg = Segment {
    label: "warmup",
    range: Range { start: 0, end: 10 },
}

字段访问可以像你预期的那样链式书写:

let len = seg.range.end - seg.range.start

泛型 shape

当 shape 的字段需要支持多种类型时,就在尖括号中声明类型参数:

shape Pair<T, U> {
    left: T,
    right: U,
}

实例会把参数确定为具体类型:

let intBytePair: Pair<i32, u8> = Pair { left: 40, right: 2_u8 }
let words: Pair<String, String> = Pair { left: "hello", right: "world" }

类型别名可以简化常用的参数组合:

type BytePair = Pair<u8, u8>

let bytes: BytePair = Pair { left: 1_u8, right: 2_u8 }

泛型 会更深入地讲解类型参数,不只是 shape,也包括函数。

shape 不是什么

你可能习惯其他语言中“结构体”的一些功能,而 shape 有意没有提供:

  • 没有方法。 shape 声明只是数据。行为放在把 shape 作为参数的独立函数中。这与 函数 中数据与副作用的分离是同一个思路。
  • 没有继承。 shape 不能扩展其他 shape。如果需要共享结构,可以把它提取成一个公共字段,或者用 choice 构建和类型。
  • 没有隐式构造函数或析构函数。 构造就是结构体字面量表达式。清理是显式的:当标准库提供需要释放的资源时,是通过能力对象风格的 API 来完成,而不是隐藏的 RAII。
  • 没有私有字段。 能看到 shape 类型的代码就能访问它的所有字段。可见性是在类型层面控制的,而不是字段层面。

总结起来:shape 是简单、可预测的记录类型,其他一切都由它们构建。

shape 和 choice 怎么选

快速指南:

  • 当一个值同时拥有所有这些字段时,用 shape。Point 总是同时有 x 和 y。
  • 当一个值是几种选择之一时,用 choice。Result 要么是 ok,要么是 err。
  • 当这些选择不携带额外数据、只是标签时,用 enum。比如星期几、简单的状态。

这三种构件:shape(与)、choice(或)、enum(不带数据的或),几乎能满足所有数据建模的需要。

下一步:泛型

你已经顺带见过 Pair<T, U> 了。下一篇文档 泛型 会讲解类型参数在 shape 和函数上如何工作,包括 Zero 标准库中随处可见的那些模式。

常见问题

Zero 中的 shape 是什么?

shape 是 Zero 中类似结构体的积类型,也就是一个带有类型化字段的具名记录。用 shape Name { field1: T1, field2: T2 } 声明,用 Name { field1: v1, field2: v2 } 构造值,用点语法(value.field1)读取字段。shape 是建模结构化数据的基本构件。

怎么创建一个 shape 值?

使用结构体字面量表达式,写出 shape 的名字并给每个字段赋值:let point = Point { x: 40, y: 2 }。每个字段都必须填写,Zero 不会悄悄为缺失的字段提供默认值。字面量中字段的顺序不必与声明中的顺序一致。

shape 和类有什么不同?

shape 是纯数据:它有字段,但没有方法、没有继承,也没有隐式构造函数。操作 shape 的函数要显式地把它作为参数接收。这种分离让语言保持小巧,也让构造或复制 shape 的开销可预测,没有隐藏的虚函数表或析构函数。

Zero 中的 shape 可以是泛型的吗?

可以。在尖括号中声明类型参数:shape Pair<T, U> { left: T, right: U }。实例会把这些参数确定下来:Pair<i32, u8>。泛型 shape 在标准库中随处可见,Maybe<T>、Span<T> 等都是基于同一思路构建的泛型 shape 或和类型。

shape 传给函数时是复制还是引用?

理解 shape 的默认思路是按值传递:被调用的函数看到的是它自己那份逻辑上的数据副本,而不是指向调用方绑定的引用。1.0 之前的 Zero 中,确切的内存模型还在演进(标准库示例中会看到用于显式引用类型的 ref 和 mutref)。对大多数应用代码来说,把 shape 参数当作值类型的输入即可。

Coddy 编程语言插图

和 Coddy 一起学编程

开始