Menu

Zero の shape|構造体ライクなレコードを定義する

shape は Zero の構造体ライクな直積型です。宣言の仕方、値の構築、フィールドの読み取り、関数を介した受け渡しを公式サンプル付きで解説します。

このページのコードはエディタで実行できます — 編集してすぐに結果を確認できます。

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.xx フィールドを読みます。get_x() メソッドはありません——フィールドはプレーンなデータです。

完全な動作例

これは言語のリポジトリにある定番の point.0 の例です——Run をクリックして動かしてください。

上から順に。

  1. 2 つの i32 フィールドを持つ Point shape を宣言。
  2. Point を取ってフィールドの和を返す sum 関数を定義。
  3. mainPoint を構築し、sum を呼び、結果を比較。

3 つのステップ、3 つの shape 関連のアイデア(宣言・構築・アクセス)、そしてひとつのエフェクト——末尾の check world.out.write(...) です。sumWorld に触れていないことに注目してください。データに対する純粋関数で、シグネチャからそれが明らかです。

ネストされたフィールドを持つ 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 は常に xy の両方を持ちます。
  • 値が複数の選択肢の ひとつ であるとき、choice を使う。Resultok 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 の正確なメモリモデルはまだ進化中です(標準ライブラリのサンプルでは明示的な参照型のために refmutref を目にします)。ほとんどのアプリケーションコードでは、shape パラメータを値型の入力として扱います。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める