shape を宣言する
shape は、名前付きで型付きのフィールドを持つレコード型です。
shape Point {
x: i32,
y: i32,
}
各要素は、
shapeは宣言を導入します。Pointは型の名前です。- 波括弧の中にフィールドのリストを書き、それぞれ
名前: 型。
この宣言で、現在のスコープに 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 の例です——Run をクリックして動かしてください。
上から順に。
- 2 つの
i32フィールドを持つPointshape を宣言。 Pointを取ってフィールドの和を返すsum関数を定義。mainでPointを構築し、sumを呼び、結果を比較。
3 つのステップ、3 つの 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 で直和型を組み立てます。
- 暗黙のコンストラクターやデストラクターなし。 構築は構造体リテラル式です。クリーンアップは明示的——標準ライブラリが破棄が必要なリソースを公開するときは、隠れた RAII ではなくケイパビリティスタイルの API で行います。
- プライベートフィールドなし。 shape のフィールドは、shape の型を見られるコードにすべてアクセス可能です。可視性は型レベルにあり、フィールドレベルではありません。
パターンは、「shape はシンプルで予測可能なレコード型で、他のすべてはそこから組み立てる」というものです。
shape と choice の使い分け
簡単なガイド。
- 値がこれらのフィールドを すべて 一緒に持つとき、shape を使う。
Pointは常にxとyの両方を持ちます。 - 値が複数の選択肢の ひとつ であるとき、choice を使う。
Resultはokかerrのどちらかです。 - 選択肢が追加のデータを持たないなら——単なるラベルなら——enum を使う。曜日、シンプルな状態。
この 3 つのビルディングブロック——shape(AND)、choice(OR)、enum(ペイロードなしの OR)——で、ほぼすべてのデータモデリングのニーズをカバーできます。
次回: ジェネリクス
Pair<T, U> が顔を出していました。次のドキュメント ジェネリクス では、shape と関数の両方の型パラメータの仕組みを、Zero 標準ライブラリ全体で現れるパターンとともに説明します。
よくある質問
Zero の shape とは?
shape は Zero の構造体ライクな直積型——型付きフィールドを持つ名前付きレコードです。shape 名前 { field1: T1, field2: T2 } で宣言し、名前 { field1: v1, field2: v2 } で値を構築し、ドット構文(value.field1)でフィールドを読みます。構造化データをモデル化する基本ブロックです。
shape の値はどう作りますか?
shape の名前を指定し、各フィールドを割り当てる構造体リテラル式を使います。let point = Point { x: 40, y: 2 }。すべてのフィールドを埋めなければなりません——Zero は不足フィールドを黙ってデフォルト化しません。リテラル内のフィールドの順序は宣言と一致する必要はありません。
shape はクラスとどう違いますか?
shape はプレーンなデータです——フィールドはありますが、メソッド、継承、暗黙のコンストラクターはありません。shape を扱う関数はそれを明示的にパラメータとして受け取ります。この分離が言語を小さく保ち、shape の構築やコピーのコストを予測可能にします——隠れた vtable やデストラクターはありません。
Zero の shape はジェネリックにできますか?
はい。山括弧で型パラメータを宣言します。shape Pair<T, U> { left: T, right: U }。インスタンスは型パラメータを固定します: Pair<i32, u8>。ジェネリックな shape は標準ライブラリ全体で登場します——Maybe<T>、Span<T> などはすべて同じアイデアの上に作られたジェネリックな shape や直和型です。
shape は関数に渡すとコピーされますか、参照されますか?
shape のデフォルトのメンタルモデルは値渡しです——呼ばれた側は、呼び出し元のバインディングへの参照ではなく、データの論理的なコピーを見ます。pre-1.0 Zero の正確なメモリモデルはまだ進化中です(標準ライブラリのサンプルでは明示的な参照型のために ref や mutref を目にします)。ほとんどのアプリケーションコードでは、shape パラメータを値型の入力として扱います。