オフィスアワーがそろそろ始まるよ!()

スライス

スライスについて解説します。スライスは配列と混同しやすいですが、異なる概念です。

スライス型

スライス型は、範囲指定された配列要素にアクセスするための型です。スライスの型は[]要素の型になります。配列の型とは、要素数が入らない点が異なります。

配列要素の範囲を指定することでスライスを作成します。

examples/ch02-primitives/src/slices.zen:4:11

test "create slice" {
    const array = [_]i32{ 5, 6, 7, 8 };
    // `array`の全要素にアクセス可能なスライスを作成
    const slice = array[0..array.len];

    ok(slice.len == 4);
    ok(slice[0] == 5);
}

スライスは配列と同様、.lenで要素数を取得したり、[インデックス]による要素アクセスが可能です。配列とスライスとの差異は、配列は要素数がコンパイル時計算可能でかつ型の一部であるのに対して、スライスの要素数は実行時に決定される点です。

配列とスライスの違いは、メモリ中に値がどのように作られるか、をイメージできると理解しやすいです。スライスは、スライスの最初の要素を指すポインタと、スライスに含まれる要素数からなります。

例えば、次の例のように、arrayから2つのスライスslice1slice2を作ったとします。

test "array and slices" {
    var array = [_]i32{ 5, 6, 7, 8 };
    const slice1 = array[0..array.len];
    const slice2 = array[0..2];

slice1は、arrayの先頭から4つの要素 (array.len) の範囲にアクセスできるスライスです。slice2は、arrayの先頭から2つの要素の範囲にアクセスできるスライスです。

array, slice1, slice2のメモリ中の値は、下図のようになります。

arrayは4つの要素をメモリ中に保持しています。一方、slice1slice2は、arrayの先頭へのポインタと、要素数 (それぞれ4と2) とをメモリ中に保持しているのみで、要素自体は保持していません。スライスの要素にアクセスすると、実体の配列要素にアクセスします。つまり、slice1[0]とした場合、array[0]の値にアクセスすることになります。

上図を念頭に、配列とスライスが指すポインタの関係を見ていきましょう。なお、現時点でポインタの概念に親しんでいない方は、以下の説明を飛ばして先に進んで頂いてかまいません。

要素にアクセスした場合、同じメモリ領域を参照するため、配列とスライスとで、その値もアドレスも一致します。

examples/ch02-primitives/src/slices.zen:18:20

    // 要素アクセスは、同じメモリ領域を参照する
    ok(slice1[0] == array[0]);
    ok(&slice1[0] == &array[0]);

配列では、先頭要素のアドレスと配列自身のアドレスとは一致します。一方、スライスでは、先頭要素はarrayの先頭要素であるため、先頭要素のアドレスとスライス自身のアドレスとは異なります

examples/ch02-primitives/src/slices.zen:22:25

    // 配列では先頭要素と、配列自身のアドレスが一致する
    ok(@ptrToInt(&array[0]) == @ptrToInt(&array));
    // スライスでは異なる
    ok(@ptrToInt(&slice1[0]) != @ptrToInt(&slice1));

ノート: Zenでは異なるポインタ型同士の比較はコンパイルエラーになるため、@ptrToIntで整数型 (usize) に変換してから、比較しています。

スライス自身と配列自身のアドレスは異なります。これは上図からも見て取れます。

examples/ch02-primitives/src/slices.zen:27:28

    // スライスと配列のアドレスは異なる
    ok(@ptrToInt(&slice1) != @ptrToInt(&array));

スライスは.ptrで先頭要素へのポインタが取得できます。この先頭要素へのポインタは、配列自身のアドレスと一致します。

examples/ch02-primitives/src/slices.zen:29:30

    // スライスの先頭要素へのポインタは、配列自身のアドレスと一致する
    ok(@ptrToInt(slice1.ptr) == @ptrToInt(&array));

ただし、スライスの先頭要素へのポインタ型と、配列のポインタ型とは、異なる型になります。配列のポインタ型は要素数が型の一部ですが、スライスの先頭要素へのポインタ型は要素数が不明な配列へのポインタになります。

examples/ch02-primitives/src/slices.zen:32:35

    // スライスの先頭要素へのポインタ型は、配列自身のポインタ型とは異なる
    ok(@typeOf(slice1.ptr) != @typeOf(&array));
    // 要素数不明の配列へのポインタ型になる
    ok(@typeOf(slice1.ptr) == [*]i32);

スライスの操作

スライスの操作は配列とよく似ています。

初期化

スライス初期化の基本構文は、const/var 識別子 = 配列[begin..end];です。beginendusize型である必要があります。endは配列要素の範囲に含まれないことに注意して下さい。例えば、[0..2]とした場合、アクセス可能な配列要素は01になります。endは省略することが可能です。endを省略した場合、配列の末尾要素まで、という意味になります。

examples/ch02-primitives/src/slices.zen:38:42

test "slice initializations" {
    const array = [_]i32{ 1, 2, 3, 4 };
    // 範囲指定の`end`を省略。`array[0..array.len]`と等価。
    const slice = array[0..];
    ok(slice.len == 4);

配列のポインタからスライスを作成することも可能です。

examples/ch02-primitives/src/slices.zen:44:45

    const from_ptr = &array[0..];
    ok(from_ptr.len == 4);

配列の途中を起点として、スライスを作成することもできます。

examples/ch02-primitives/src/slices.zen:48:53

test "slice started from middle of an array" {
    const array = [_]i32{ 1, 2, 3, 4 };
    const slice = array[2..];
    ok(slice.len == 2);
    ok(slice[0] == 3);
}

スライスから、別のスライスを作成することができます。

examples/ch02-primitives/src/slices.zen:55:61

test "create slice from slice" {
    const array = [_]i32{ 1, 2, 3, 4 };
    const slice1 = array[0..];
    const slice2 = slice1[1..];
    ok(slice2.len == 3);
    ok(slice2[0] == 2);
}

配列とは異なり、実行時にしか計算できない配列要素の範囲からスライスを作ることができます。

examples/ch02-primitives/src/slices.zen:63:72

test "slice from runtime known length" {
    const array = [_]i32{ 1, 2, 3, 4 };
    var begin: usize = 1;
    var end: usize = array.len - 1;
    // `begin`と`end`は実行時に計算される
    const slice = array[begin..end];

    ok(slice.len == 2);
    ok(slice[0] == 2);
}

配列要素の範囲は、usizeで指定することに注意して下さい。

要素数が不明な配列に対しては、endを省略できません。要素数が不明な配列については[2.9 ポインタ]で説明します。下のプログラムでは@ptrCast([*]i32, &[_]i32{ 1, 2 })で要素数不明な配列へ型変換しています。

examples/ch02-primitives/src/slices.zen:74:80

test "must include end" {
    const unknown_size_array = @ptrCast([*]i32, &[_]i32{ 1, 2 });
    // `end`を書く場合はOK
    const slice = unknown_size_array[0..2];
    // Compile error: slice of pointer must include end value
    // const slice = unknown_size_array[0..];
}

ミュータビリティ

スライスの要素がミュータブルかどうか、はミュータブルな配列からスライスを作っているかどうか、に依存します。スライス自体のミュータビリティとは関係しませんので、注意して下さい。

examples/ch02-primitives/src/slices.zen:82:95

test "slice mutability" {
    var mutable_array = [_]i32{ 1, 2, 3, 4 };
    const immutable_array = [_]i32{ 1, 2, 3, 4 };

    const mutable_slice = mutable_array[0..];
    const immutable_slice = immutable_array[0..];

    mutable_slice[0] = 5;
    // Compile error: cannot assign to constant
    // immutable_slice[0] = 5;

    ok(@typeOf(mutable_slice) == []i32);
    ok(@typeOf(immutable_slice) == []const i32);
}

バイト列からスライスを作成

少し上級者向けのテクニックです。スライスを作成する前のバイト列が、スライスのアライメントに沿っていることが条件です。

examples/ch02-primitives/src/slices.zen:97:11

const builtin = @import("builtin");
test "bytes to slice" {
    const array align(@alignOf(i32)) = [_]u8{
        0x12, 0x34, 0x56, 0x78, 0xff, 0xff, 0xff, 0xff
    };
    const slice = @bytesToSlice(i32, array[0..]);
    ok(slice.len == 2);
    if (builtin.endian == .Little) {
        ok(slice[0] == 0x78563412);
        ok(slice[1] == -1);
    } else {
        ok(slice[0] == 0x12345678);
        ok(slice[1] == -1);
    }
}

バイト列のバイト数が、スライスの要素型のバイト数で割り切れない (上の例では、arrayが9バイトの場合など) 場合、安全性保護付き未定義動作により、実行時にパニックが発生します。

エンディアンは考慮されないことに留意して下さい。

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved.
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.