Konstruk inline

Mengendalikan perulangan dan percabangan inline pada waktu kompilasi.

Daftar isi

Konstruk inline adalah perulangan dan percabangan yang dibuka saat kompilasi, sehingga setiap iterasi menjadi salinan kode tersendiri di dalam biner. Zig menyediakan inline for dan inline while untuk kebutuhan ini, dan keduanya menuntut iterable yang diketahui pada waktu kompilasi. Topik ini membahas kapan konstruk inline diperlukan, pembentukan tabel pangkat pada waktu kompilasi, serta hubungan konstruk inline dengan fungsi berparameter comptime. Hasil akhirnya adalah kemampuan memilih antara perulangan biasa dan perulangan inline secara sadar.

Konsep#

Perulangan yang dibuka saat kompilasi#

Perulangan biasa menghasilkan satu rangkaian instruksi yang diulang pada waktu berjalan, sedangkan inline for meminta kompilator menuliskan tubuh perulangan berkali-kali, satu salinan untuk setiap iterasi. Proses ini disebut unrolling, yaitu membuka gulungan perulangan menjadi rangkaian blok yang berurutan. Karena setiap iterasi berdiri sendiri, nilai indeksnya menjadi konstanta tersendiri pada salinannya masing-masing.

Sifat itulah yang membuat inline for wajib pada dua situasi, yaitu pemanggilan fungsi berparameter comptime di dalam tubuh dan akses @field dengan nama yang berubah per iterasi. inline while memberikan perilaku yang sama dengan bentuk perulangan bersyarat, lengkap dengan ekspresi lanjutan sebagaimana while biasa. Kedua konstruk menuntut iterable atau kondisinya diketahui saat kompilasi, sehingga slice yang diterima fungsi pada waktu berjalan tidak dapat menjadi iterable inline.

Pembukaan berdampak pada ukuran biner, sehingga pilihan ini layak dipertimbangkan dan tidak dipakai otomatis. Perulangan biasa tetap lebih tepat untuk data berukuran besar pada waktu berjalan, sedangkan konstruk inline unggul ketika jumlah iterasi kecil dan setiap iterasi menuntut konstanta. Angka iterasi yang besar membuat salinan tubuh menumpuk dengan cepat, sehingga jumlah iterasi inline biasanya dijaga tetap kecil.

zig Contoh: inline for membuka iterasi
// tidak-dijalankan
inline for (0..3) |i| {
    // tiga salinan blok ini dibuat saat kompilasi,
    // dengan i bernilai 0, 1, dan 2 pada salinannya masing-masing
    std.debug.print("iterasi {d}\n", .{i});
}

Membangun tabel pangkat saat kompilasi#

Tabel konstanta adalah penggunaan konstruk waktu kompilasi yang paling terlihat hasilnya. Program berikut membentuk tabel pangkat bilangan dua, yaitu 2 pangkat 0 sampai 2 pangkat 7, sepenuhnya saat kompilasi melalui blok berlabel pada tingkat container.

zig tabel-pangkat.zig
const std = @import("std");

fn pangkatDua(comptime n: u32) u64 {
    return @as(u64, 1) << @intCast(n);
}

// tabel pangkat dua dibangun sepenuhnya saat kompilasi
const TABEL = blk: {
    var hasil: [8]u64 = undefined;
    for (0..8) |i| {
        hasil[i] = pangkatDua(i);
    }
    break :blk hasil;
};

pub fn main() void {
    for (TABEL, 0..) |nilai, i| {
        std.debug.print("2^{d} = {d}\n", .{ i, nilai });
    }
}
text Keluaran
2^0 = 1
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 16
2^5 = 32
2^6 = 64
2^7 = 128

Blok berlabel blk: adalah ekspresi yang menghitung nilai array saat kompilasi, dan break :blk hasil mengirimkan hasilnya sebagai nilai konstanta TABEL. Kata kunci inline tidak diperlukan di dalam blok tersebut karena seluruh tubuhnya sudah berada pada konteks comptime, sehingga perulangan for biasa dijalankan oleh kompilator. Perulangan di dalam main hanya membaca elemen tabel dan mencetaknya, dan tidak ada satu operasi pangkat pun dihitung pada waktu berjalan karena seluruh nilainya sudah tertanam di dalam biner.

Tabel semacam ini lazim dipakai untuk konversi satuan dengan faktor tetap, pengali statistik, atau deret konstanta yang ditentukan spesifikasi. Keuntungannya muncul dua kali, yaitu tidak ada kode inisialisasi pada waktu berjalan dan tidak ada risiko tabel tertinggal kosong. Kegagalan perhitungan tabel menghentikan kompilasi, sehingga kesalahan konstantanya tertangkap sebelum program pernah berjalan.

bash
# jalankan program dari direktori topik
zig run tabel-pangkat.zig
text Keluaran
2^0 = 1
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 16
2^5 = 32
2^6 = 64
2^7 = 128

Contoh Lengkap#

inline while dan pemanggilan yang menuntut konstanta#

Konstruk inline paling diperlukan ketika tubuh perulangan memanggil fungsi berparameter comptime. Program berikut memanggil kuadrat untuk lima nilai sekaligus, dan setiap pemanggilan merupakan instansiasi tersendiri karena parameternya harus konstanta waktu kompilasi.

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

fn kuadrat(comptime n: u32) u32 {
    return n * n;
}

pub fn main() void {
    // perulangan dibuka saat kompilasi: lima pemanggilan kuadrat berbeda
    comptime var i: u32 = 1;
    inline while (i <= 5) : (i += 1) {
        std.debug.print("kuadrat {d} = {d}\n", .{ i, kuadrat(i) });
    }
}
text Keluaran
kuadrat 1 = 1
kuadrat 2 = 4
kuadrat 3 = 9
kuadrat 4 = 16
kuadrat 5 = 25

Penghitung i dideklarasikan dengan comptime var, sehingga nilai per iterasi diketahui saat kompilasi dan setiap salinan tubuh memanggil kuadrat dengan konstanta yang berbeda. Apabila inline while diganti while biasa, kompilasi berhenti dengan pesan unable to resolve comptime value karena kuadrat(i) menuntut konstanta yang tidak tersedia pada waktu berjalan. Perbaikannya bukan mengubah fungsi, melainkan membuka perulangannya dengan konstruk inline.

Pola ini menjelaskan kehadiran inline for pada fungsi cetakSemua di topik refleksi, yaitu akses @field menuntut nama field yang menjadi konstanta pada setiap iterasi. Hubungan kedua mekanisme itu dua arah, karena fungsi berparameter comptime memperluas daftar kasus yang memerlukan perulangan inline. Perulangan biasa tetap dipertahankan untuk bagian yang tidak menuntut konstanta, sebagaimana main pada program tabel pangkat membaca nilainya saat berjalan.

bash
# jalankan program dari direktori topik
zig run unroll.zig
text Keluaran
kuadrat 1 = 1
kuadrat 2 = 4
kuadrat 3 = 9
kuadrat 4 = 16
kuadrat 5 = 25

Kesalahan umum#

Kesalahan pada topik ini berpusat pada pemilihan antara perulangan biasa dan konstruk inline. Tiga kasus berikut paling sering muncul.

  1. Memakai for biasa untuk memanggil fungsi berparameter comptime. Kompilator menolaknya dengan pesan unable to resolve comptime value beserta catatan argument to comptime parameter must be comptime-known. Ganti for dengan inline for, atau while dengan inline while, agar setiap iterasi menyediakan konstanta tersendiri.
  2. Menjadikan slice runtime sebagai iterable inline. inline for atas parameter []const u8 yang diterima fungsi ditolak dengan pesan inline loop condition must be comptime-known, karena isinya baru diketahui saat berjalan. Gunakan for biasa untuk slice, dan sisakan konstruk inline untuk iterable konstanta seperti rentang 0..N.
  3. Memakai konstruk inline pada semua perulangan besar. Setiap iterasi menambah salinan kode, sehingga biner membesar tanpa manfaat apabila tubuhnya tidak menuntut konstanta waktu kompilasi. Cadangkan inline for dan inline while untuk kasus yang memang memerlukannya, dan biarkan perulangan biasa mengelola data pada waktu berjalan.

Ringkasan#

  • inline for dan inline while membuka perulangan saat kompilasi, sehingga setiap iterasi menjadi salinan tersendiri dengan konstanta indeksnya.
  • Blok berlabel seperti blk: menghitung nilai konstanta tingkat container saat kompilasi, dan perulangan di dalamnya sudah berjalan pada konteks comptime.
  • Konstruk inline wajib apabila tubuh perulangan memanggil fungsi berparameter comptime atau mengakses @field dengan nama yang berubah.
  • Iterable untuk konstruk inline wajib diketahui saat kompilasi, sedangkan slice runtime tetap diperulangkan dengan for biasa.

Referensi#