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

スライス

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

スライス型

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

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

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

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

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

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

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

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

examples/ch02-primitives/src/slices.zen:16:21

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

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:23:25

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

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

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

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

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

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

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

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

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

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

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

ただし、スライスの先頭要素へのポインタ型と、配列のポインタ型とは、異なる型になります。配列のポインタ型は要素数が型の一部ですが、スライスの先頭要素へのポインタ型は要素数が不明な配列へのポインタになります。slice1.ptr の型([*]mut i32)についている mut については後述のミュータビリティを参照して下さい。

examples/ch02-primitives/src/slices.zen:37:40

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

スライスの操作

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

初期化

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

begin または end が変数であった場合はスライス型となりますが、beginend の両方がコンパイル時に決まる値の場合は *[N]T(要素数がコンパイル時計算可能な配列へのポインタ)になります。詳細は 2章 ポインタ を参照して下さい。

examples/ch02-primitives/src/slices.zen:43:49

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

配列へのポインタからスライスを作成することも可能です。次のコードでは暗黙の型変換により配列へのポインタからスライスが生成されます。

examples/ch02-primitives/src/slices.zen:51:52

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

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

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

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

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

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

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

要素数が不明な配列に対しては、endを省略できません。要素数が不明な配列については2章 ポインタで説明します。

examples/ch02-primitives/src/slices.zen:83:93

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

ミュータビリティ

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

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

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

    var begin: usize = 0;
    const mutable_slice = mutable_array[begin..];
    const immutable_slice = immutable_array[begin..];

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

    ok(@TypeOf(mutable_slice) == []mut i32);
    ok(@TypeOf(immutable_slice) == []i32);
}

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

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

examples/ch02-primitives/src/slices.zen:111:125

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バイトの場合など) 場合、安全性保護付き未定義動作により、実行時にパニックが発生します。

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

終端値付きスライス

配列型と同様に、スライスの終端値を指定することが出来ます。 スライス型を指定する際に[]の中に:に続けて終端値を指定します。例えばヌル文字 0 終端である文字列スライスは [:0]u8 と表現します。

配列やスライスから部分的な終端値付きスライスを作成するには、 array[begin..end :sentinel]とします。 スライスを作成するとき、終端値が正しいかどうかをコンパイル時または実行時にチェックします。 コンパイル時に終端値が異なることを検出した場合はコンパイルエラーになります。 実行時に終端値が異なることを検出した場合には実行時パニックが発生します。

examples/ch02-primitives/src/slices.zen:127:137

test "sentinel-terminated slice" {
    var str_slice: [:0]u8 = "abced";

    var array = [_]u32{ 1, 2, 3 };
    const slice: [:3]u32 = array[0..2 :3];

    ok(slice.len == 2);

    // panic
    // const slice = array[0..2 :4];
}

第2章 文字列第14章 Cとのインタフェース も参照してください。

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Chapter 13

Chapter 14

Chapter 15

Appendix

Error Explanation

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 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.