Slice

Menggunakan slice sebagai tampilan yang membawa panjang dan batas atas memori kontigu.

Daftar isi

Slice adalah tampilan (view) atas rentang memori kontigu yang membawa dua informasi, yaitu pointer ke elemen pertama dan panjang rentangnya. Zig memakai slice secara luas karena slice menyediakan akses berbatas tanpa menyalin data. Topik ini membahas konversi array menjadi slice, tiga bentuk rentang pemotongan, sifat pointer dan panjangnya, perbedaan slice yang dapat ditulis dengan yang hanya dibaca, serta pemeriksaan batas yang berjalan pada setiap akses elemen. Diagram memori dan program rata-rata subbagian nilai memperlihatkan perilakunya secara konkret.

Konsep#

Dari array menjadi slice#

Operasi pemotongan [awal..akhir] menghasilkan rentang yang memuat indeks awal hingga akhir - 1, sehingga nilai[2..5] memuat elemen indeks 2, 3, dan 4. Ketika hasilnya diberi anotasi tipe slice, array aslinya dikonversi menjadi tampilan yang merujuk memori yang sama. Tanpa anotasi, pemotongan yang batasnya diketahui saat kompilasi menghasilkan pointer ke array, yaitu *const [3]u32, dan bukan slice.

Program berikut menghitung rata-rata subbagian nilai dari array enam elemen.

zig slice.zig
const std = @import("std");

pub fn main() void {
    const nilai = [6]u32{ 70, 80, 90, 85, 75, 95 };
    const tengah: []const u32 = nilai[2..5];

    std.debug.print("Panjang slice: {d}\n", .{tengah.len});
    std.debug.print("Elemen pertama: {d}\n", .{tengah[0]});

    var total: u32 = 0;
    for (tengah) |n| {
        total += n;
    }
    std.debug.print("Rata-rata subbagian: {d}\n", .{total / tengah.len});

    const terbuka: []const u32 = nilai[2..];
    std.debug.print("Panjang slice terbuka: {d}\n", .{terbuka.len});
}
text Keluaran
Panjang slice: 3
Elemen pertama: 90
Rata-rata subbagian: 83
Panjang slice terbuka: 4

Jalankan program tersebut dengan perintah berikut.

bash
# menjalankan program rata-rata subbagian
zig run slice.zig
text Keluaran
Panjang slice: 3
Elemen pertama: 90
Rata-rata subbagian: 83
Panjang slice terbuka: 4

Slice tengah memuat nilai 90, 85, dan 75, sehingga jumlahnya 250 dan rata-rata bulatnya 83. Elemen pertamanya adalah 90, yaitu elemen indeks 2 dari array asalnya, bukan elemen indeks 0. Rentang terbuka nilai[2..] berjalan hingga akhir array dan menghasilkan empat elemen, yaitu indeks 2 sampai 5.

Tiga bentuk rentang#

Pemotongan tersedia dalam tiga bentuk yang saling melengkapi.

zig Contoh: tiga bentuk rentang pemotongan
// tidak-dijalankan
const bagian = nilai[1..3];  // indeks 1 dan 2, panjang 2
const ke_akhir = nilai[2..]; // indeks 2 sampai akhir array
const penuh = nilai[0..];    // seluruh array

Bentuk tertutup [awal..akhir] menghasilkan panjang akhir - awal, sehingga batas akhirnya bersifat eksklusif dan nilai[1..3] berpanjang dua. Bentuk terbuka [awal..] berlanjut hingga akhir array dan panjangnya sama dengan nilai.len - awal, sedangkan [0..] menampilkan seluruh array. Ketiga bentuk menuntut awal tidak melebihi panjang array, dan batas akhir yang melewati panjang array ditolak. Kaidah akhir-eksklusif ini perlu dipegang konsisten karena menjadi sumber kekeliruan satu indeks yang paling sering terjadi.

Kedua batas rentang boleh berupa nilai yang baru diketahui saat runtime, bukan hanya angka tetap di dalam kode. Rentang yang terbalik, yaitu awal yang melebihi akhir, dijaga pula oleh pemeriksaan Zig. Apabila kedua batasnya diketahui saat kompilasi, rentang terbalik ditolak saat kompilasi dengan pesan start index 5 is larger than end index 2. Apabila batasnya baru diketahui saat berjalan, pemeriksaan yang sama berubah menjadi panic dengan pesan start index 4 is larger than end index 1, dan alamat pada jejak tumpukannya berubah dari satu eksekusi ke eksekusi lain.

Contoh Lengkap#

Pointer dan panjang#

Diagram berikut menampilkan array enam elemen beserta slice yang menunjuk rentang tengahnya. Sel berlatar biru adalah elemen yang terlihat oleh slice, sedangkan braket biru menegaskan bahwa slice hanya menyimpan pointer ke sel pertama rentang beserta panjangnya.

slice tengah (panjang 3)pointerarray nilai (6 elemen)708090857595012345
Slice menyimpan pointer ke sel pertama rentang beserta panjang rentangnya

Slice []T pada dasarnya adalah pasangan pointer dan panjang, sehingga meneruskannya ke fungsi hanya memindahkan dua nilai kecil tanpa menyalin satu elemen pun. Panjang tersebut sekaligus menjadi batas atas akses. Setiap pengindeksan diperiksa terhadap len, dan akses pada indeks yang setara atau melebihi panjang menghentikan program dengan panic.

zig Contoh: akses di luar batas slice
// tidak-dijalankan
var i: usize = 0;
i += 99;
_ = nilai[i]; // panic: index out of bounds: index 99, len 6

Ketika contoh tersebut dijalankan, program berhenti dengan pesan panic: index out of bounds: index 99, len 6 yang menyebutkan indeks yang diminta beserta panjang yang tersedia. Alamat memori dan nomor thread pada jejak tumpukannya berubah dari satu eksekusi ke eksekusi lain. Apabila indeks dan panjangnya diketahui saat kompilasi, pemeriksaan yang sama berpindah ke waktu kompilasi, sehingga akses tengah[3] pada slice berpanjang 3 ditolak dengan error index 3 outside slice of length 3 tanpa perlu menjalankan program.

Slice yang dapat ditulis dan yang hanya dibaca#

Tipe []T adalah slice yang elemennya dapat diubah, sedangkan []const u8 dan []const u32 adalah slice yang hanya dapat dibaca. Apabila array asalnya dideklarasikan const, hasil pemotongannya bertipe hanya-baca, dan penugasan pada elemen slice tersebut ditolak oleh kompilator. Kewenangan menulis ini mengikuti tipe elemen slice, bukan pengikatnya, karena const pada deklarasi slice hanya mengunci pengikatan itu sendiri.

zig Contoh: perubahan lewat slice menyentuh array asal
// tidak-dijalankan
var stok = [3]u32{ 5, 2, 7 };
const tampilan: []u32 = stok[0..2];
tampilan[0] = 9; // stok[0] ikut berubah menjadi 9

Perubahan tampilan[0] pada contoh mengubah stok[0] menjadi 9, karena keduanya merujuk memori yang sama. Slice tidak pernah memiliki datanya sendiri, sehingga setiap penulisan lewat slice berdampak pada pemilik memori aslinya. Karena itu, fungsi yang menerima slice []u32 diberi tahu secara eksplisit oleh tipenya bahwa data asli boleh berubah, sedangkan fungsi yang hanya membaca sebaiknya meminta []const u32 agar maksudnya terbaca dari tanda tangan fungsinya.

Perbedaan kedua tipe itu juga berlaku pada penerusan argumen. Slice yang dapat ditulis selalu dapat diteruskan ke parameter yang hanya membaca, karena kewenangan membaca sudah pasti tersedia, sedangkan arah sebaliknya tidak pernah diizinkan oleh kompilator. Kaidah ini membuat kewenangan menulis tetap terlihat dari tipe parameternya, bukan dari isi fungsi yang harus dibaca lebih dahulu.

Kesalahan umum#

Kesalahan pertama berupa memakai rentang yang melewati batas array. Rentang [0..n] pada array berukuran kurang dari n menghasilkan panic batas atas pada mode Debug, sehingga panjang sumber selalu diperiksa sebelum memotong. Rentang terbuka arr[1..] menghindari kekeliruan angka batas untuk kasus potongan dari titik tertentu sampai akhir.

Kesalahan kedua berupa menyimpan slice yang lebih lama dari data aslinya. Slice adalah tampilan, bukan salinan, dan variabel asli yang keluar dari lingkupnya membuat slice menggantung. Bila data harus bertahan lebih lama, salinlah dengan dupe melalui allocator, dan kontrak kepemilikannya dibahas pada modul Memori dan allocator.

Ringkasan#

  • Slice menyimpan pointer ke elemen pertama beserta panjangnya, sehingga penerusan slice tidak menyalin data dan setiap akses diperiksa terhadap panjang tersebut.
  • Rentang [awal..akhir] menghasilkan akhir - awal elemen, rentang [awal..] berjalan hingga akhir array, dan [0..] menampilkan seluruh array.
  • Tipe []T mengizinkan perubahan elemen, sedangkan []const T hanya membaca, dan penulisan lewat slice mengubah array asalnya.
  • Akses di luar batas menghentikan program dengan panic pada runtime dan ditolak saat kompilasi apabila indeksnya diketahui saat kompilasi.

Referensi#