Topik ini membahas dua sisi constraint bernama pada generics Carbon, yaitu interface yang mendeklarasikan kemampuan dan impl yang mengaitkan kemampuan itu dengan sebuah tipe. Pembahasan bergerak dari definisi keduanya, program dua bentuk geometri yang dapat dieksekusi di Explorer, hingga pemeriksaan yang terjadi pada batas pemanggilan fungsi generik. Setelah membacanya, alur pengecekan dari deklarasi interface sampai pemanggilan yang ditolak dapat diikuti lengkap dengan bukti eksekusinya.
Interface sebagai kontrak bernama#
Interface adalah constraint bernama untuk parameter waktu kompilasi. Dokumen desain generics mendefinisikannya sebagai sebuah nama yang mendeskripsikan fungsi, metode, dan entitas lain yang dapat diimplementasikan oleh tipe. Deklarasinya menggunakan kata kunci interface, dan setiap fungsi di dalamnya menyebut parameter Self sebagai tempat tipe yang mengimplementasikan interface tersebut. Sifatnya nominal, yaitu sebuah tipe tidak otomatis memenuhi interface hanya karena kebetulan memiliki fungsi dengan nama yang sama, melainkan harus menyatakan implementasinya secara eksplisit melalui impl.
Dokumen desain juga mengatur dua kata kunci tambahan untuk fungsi di dalam interface. Deklarasi yang diberi awalan default boleh membawa tubuh berisi implementasi bawaan, sedangkan final melarang implementasi itu ditimpa oleh tipe yang mengimplementasikannya. Interface hanya mendeskripsikan kemampuan dan tidak menyimpan data, sehingga deklarasi variabel tidak diperbolehkan di dalamnya. Bentuk berikut memperlihatkan interface yang digunakan sepanjang topik ini.
// tidak-dijalankan
interface Luas {
fn Hitung(self: Self) -> i32;
}Interface Luas menuntut satu fungsi Hitung yang menerima Self dan mengembalikan i32. Pemilihan i32 mengikuti batasan interpreter Explorer pada trunk yang baru mendukung i32 untuk angka, sehingga contoh pada topik ini dapat dieksekusi apa adanya. Rancangan lengkapnya jauh lebih luas, misalnya associated constants dan interface berparameter yang dijelaskan dokumen desain, tetapi keduanya tidak diperlukan untuk membaca contoh di sini.
Impl sebagai jangkar implementasi#
Impl adalah deklarasi yang mengaitkan sebuah tipe dengan sebuah interface sekaligus mengisi tubuh setiap fungsi yang dituntutnya. Bentuk umumnya impl NamaTipe as NamaInterface, diikuti definisi fungsi-fungsi interface tersebut. Dokumen desain mengizinkan impl ditulis di dalam tubuh kelas maupun di luar kelas, dan pada rancangan lengkapnya impl di luar kelas hanya boleh berada di library yang mendefinisikan salah satu nama dari type structure-nya, yaitu pola tipe yang ditulis pada deklarasi impl. Aturan itu mengarah pada sifat coherence agar setiap pasangan tipe dan interface memiliki satu implementasi yang sah.
Program berikut menggunakan dua tipe, yaitu Persegi dan PersegiPanjang, yang keduanya mengimplementasikan Luas dengan rumus berbeda. Fungsi generik CetakLuas menerima satu parameter bentuk yang tipenya ditulis T dan diperiksa terhadap interface Luas.
package sample api;
interface Luas {
fn Hitung(self: Self) -> i32;
}
class Persegi {
var sisi: i32;
}
class PersegiPanjang {
var panjang: i32;
var lebar: i32;
}
impl Persegi as Luas {
fn Hitung(self: Self) -> i32 {
return self.sisi * self.sisi;
}
}
impl PersegiPanjang as Luas {
fn Hitung(self: Self) -> i32 {
return self.panjang * self.lebar;
}
}
fn CetakLuas[T:! Luas](bentuk: T) {
Print("Luas = {0}", T.Hitung(bentuk));
}
fn Main() -> i32 {
var p: Persegi = {.sisi = 4};
var pp: PersegiPanjang = {.panjang = 3, .lebar = 5};
CetakLuas(p);
CetakLuas(pp);
return 0;
}Luas = 16
Luas = 15
result: 0Keluaran memuat dua angka luas pada baris berurutan, karena Compiler Explorer menampilkan setiap pemanggilan Print sebagai satu baris dan menambahkan baris result: 0 sebagai kode kembalian Main. Nilai 16 berasal dari impl Persegi dan nilai 15 berasal dari impl PersegiPanjang. Kedua pemanggilan CetakLuas menjalankan tubuh fungsi yang sama persis, sedangkan perbedaan rumus seluruhnya berasal dari impl yang ditemukan untuk T pada tiap pemanggilan.
Pemeriksaan pada batas pemanggilan#
Pola [T:! Luas] pada deklarasi CetakLuas merupakan checked binding, yaitu penanda bahwa T adalah parameter generik yang diperiksa. Tubuh fungsi diperiksa sekali pada saat definisi, tanpa mengetahui tipe argumen yang akan datang, dan pemeriksaannya cukup terhadap nama-nama yang dijanjikan interface. Pada saat pemanggilan, nilai T dideduksi dari tipe argumen, kemudian pemeriksa mencari impl tipe itu terhadap Luas sebelum program dijalankan. Pemanggilan hanya sah bila impl ditemukan, sehingga batas pemanggilan menjadi titik pemeriksaan utama program generik.
Percobaan berikut menyajikan versi tanpa impl untuk Segitiga, lalu tetap memanggil CetakLuas dengan argumen Segitiga. Pemanggilan ditolak pada batas pemanggilan dengan pesan yang menyebut pasangan interface dan tipe yang bermasalah.
package sample api;
interface Luas {
fn Hitung(self: Self) -> i32;
}
class Segitiga {
var alas: i32;
var tinggi: i32;
}
fn CetakLuas[T:! Luas](bentuk: T) {
Print("Luas = {0}", T.Hitung(bentuk));
}
fn Main() -> i32 {
var t: Segitiga = {.alas = 6, .tinggi = 4};
CetakLuas(t);
return 0;
}COMPILATION ERROR: <source>:18: could not find implementation of interface Luas for class SegitigaPesan error tersebut memperlihatkan keunggulan model terperiksa. Kesalahan terdeteksi pada berkas pemanggil, bukan jauh di dalam tubuh fungsi yang telah diinstansiasi, dan pesannya menunjuk pasangan interface serta tipe yang belum terhubung secara langsung. Apabila impl untuk Segitiga ditambahkan, pemanggilan yang sama akan diterima tanpa mengubah CetakLuas sama sekali.
Batas interpreter juga perlu dinyatakan apa adanya. Pemanggilan p.Hitung() pada objek Persegi ditolak oleh interpreter, sebagaimana program berikut.
package sample api;
interface Luas {
fn Hitung(self: Self) -> i32;
}
class Persegi {
var sisi: i32;
}
impl Persegi as Luas {
fn Hitung(self: Self) -> i32 {
return self.sisi * self.sisi;
}
}
fn Main() -> i32 {
var p: Persegi = {.sisi = 4};
p.Hitung();
return 0;
}COMPILATION ERROR: <source>:19: class Persegi does not have a field named HitungDokumen desain mengatur dua bentuk pemanggilan metode interface, yaitu simple member access untuk impl yang dinyatakan dengan extend di dalam kelas dan qualified member access seperti p.(Luas.Hitung)() untuk semua impl. Keduanya belum dijalankan oleh interpreter Explorer, sehingga pola yang berjalan saat ini adalah T.Hitung(bentuk) di dalam fungsi generik yang parameternya diperiksa terhadap interface tersebut. Penyebutan Luas.Hitung(p) di luar pola itu juga ditolak dengan pesan expected callee to be a function, found member name Hitung.
Ringkasan#
- Interface adalah constraint bernama yang mendeklarasikan fungsi untuk tipe, bersifat nominal, dan tidak menyimpan data.
- Impl mengaitkan tipe dengan interface beserta tubuh fungsinya, dan satu pasangan tipe serta interface hanya boleh dideklarasikan sekali.
- Binding
[T:! Luas]membuat tubuh fungsi diperiksa sekali pada definisi, sedangkan impl dicari pada batas pemanggilan dari tipe argumen. - Di Explorer,
T.Hitung(bentuk)di dalam fungsi generik berjalan, sedangkanp.Hitung()danLuas.Hitung(p)belum didukung interpreter.
Referensi#
- Generics: Overview — Proyek Carbon Language
— Mendefinisikan interface, impl, kata kunci
defaultdanfinal, serta aturan coherence pada deklarasi impl. - Generics: Detailed design — Proyek Carbon Language — Menetapkan semantik checked-generic functions dan qualified member names pada bagian checked facet bindings.
- Generics: Goals — Proyek Carbon Language — Menjelaskan complete definition checking, encapsulation, dan alasan coherence dikejar dalam desain.
- Compiler Explorer — godbolt.org — Layanan tempat seluruh program Carbon pada topik ini dieksekusi.