Trait adalah kontrak metode antara sebuah tipe dan kode yang menggunakannya. Topik ini membahas pendeklarasian trait, penulisan conformance pada struct, pemanggilan lewat fungsi generik dengan batas trait, serta conformance untuk pencetakan teks. Setelah topik ini dibaca, struct dapat memenuhi sebuah kontrak trait dan dicetak melalui print dengan benar.
Trait sebagai kontrak metode#
Trait mendeklarasikan kemampuan yang wajib dimiliki sebuah tipe tanpa menetapkan cara implementasinya. Deklarasinya memakai kata kunci trait, diikuti nama trait dan daftar persyaratannya. Metode wajib (required method) ditulis dengan isi berupa tiga titik, yaitu ..., dan bukan pass. Bentuk tiga titik menyatakan bahwa metode tersebut hanya sebuah persyaratan, sehingga setiap tipe yang memenuhi kontrak wajib menyediakan implementasinya sendiri.
# tidak-dijalankan
trait Peserta:
def nama(self) -> String: ...Selain metode wajib, sebuah trait dapat menyediakan implementasi bawaan untuk metode tertentu. Implementasi bawaan ditulis dengan body biasa dan diwarisi oleh setiap tipe yang memenuhi kontrak, dan tetap terbuka untuk dioverride. Compiler memverifikasi seluruh persyaratan pada waktu kompilasi, sehingga kode yang mengandalkan sebuah trait dapat memanggil metode-metodenya tanpa pemeriksaan tambahan saat program berjalan.
Conformance bersifat eksplisit dan utuh. Struct yang kebetulan memiliki metode dengan nama dan tanda tangan yang sama tidak dianggap memenuhi kontrak selama trait tersebut tidak dideklarasikan pada struct-nya. Sebaliknya, struct yang mendeklarasikan sebuah trait wajib memenuhi seluruh persyaratannya, karena tidak ada conformance parsial.
Menuliskan conformance#
Conformance, yaitu pernyataan bahwa sebuah tipe memenuhi kontrak trait, dideklarasikan dengan menuliskan nama trait di dalam tanda kurung setelah nama struct. Setelah itu, seluruh metode wajib dituliskan di dalam body struct. Compiler memeriksa kesesuaian ini pada waktu kompilasi dan menolak struct yang persyaratannya belum terpenuhi, misalnya dengan pesan yang menyatakan bahwa Petugas tidak mengimplementasikan seluruh persyaratan Peserta. Pesan tersebut disertai catatan berisi nama metode wajib yang belum diimplementasikan, sehingga bagian yang belum terpenuhi langsung terlihat.
Conformance hanya dapat dideklarasikan pada saat tipe didefinisikan. Trait tidak dapat ditambahkan belakangan ke tipe lain yang tidak didefinisikan sendiri, misalnya ke Int atau String, karena deklarasi conformance adalah bagian dari definisi tipe tersebut. Untuk tipe bawaan, dukungan trait yang tersedia sudah ditetapkan oleh pustaka standar.
Satu struct dapat memenuhi lebih dari satu trait sekaligus dengan menuliskan beberapa nama trait yang dipisahkan koma, misalnya struct Suhu(Writable, Copyable). Daftar conformance inilah yang menghubungkan kemampuan umum seperti penyalinan dan pencetakan dengan struct yang didefinisikan sendiri.
Fungsi generik dengan batas trait#
Fungsi generik (generic function) mendeklarasikan parameter compile-time yang dibatasi oleh trait, sehingga satu fungsi melayani semua tipe yang memenuhi kontrak yang sama. Bentuk penulisannya adalah def sebut[T: Peserta](p: T), dengan T sebagai parameter tipe yang dibatasi trait Peserta. Di dalam fungsi, compiler mengetahui bahwa setiap pemanggilan p.nama() sah, karena seluruh tipe yang mengisi T wajib menyediakan metode tersebut. Pemanggilan fungsinya ditulis tanpa kurung siku, karena T diinferensikan dari argumen.
Bila nama tipe yang konkret tidak diperlukan di dalam fungsi, parameter dapat ditulis langsung dengan singkatan Some[Peserta] tanpa memberi nama pada parameter tipe. Dispatch bersifat statis, yaitu pemanggilan metode ditetapkan pada waktu kompilasi, sehingga polimorfisme lewat trait tidak menambah biaya saat program berjalan.
Program berikut mendefinisikan trait Peserta, dua struct yang memenuhi kontraknya, dan satu fungsi generik yang melayani keduanya.
trait Peserta:
def nama(self) -> String: ...
struct Petugas(Peserta):
var nama_lengkap: String
var hari: String
def __init__(out self, nama_lengkap: String, hari: String):
self.nama_lengkap = nama_lengkap
self.hari = hari
def nama(self) -> String:
return self.nama_lengkap
struct Siswa(Peserta):
var nama_panggilan: String
def __init__(out self, nama_panggilan: String):
self.nama_panggilan = nama_panggilan
def nama(self) -> String:
return self.nama_panggilan
def sebut[T: Peserta](p: T) -> String:
return "nama peserta: " + p.nama()
def main():
var petugas = Petugas("Dewi", "Selasa")
var siswa = Siswa("Budi")
print(sebut(petugas))
print(sebut(siswa))# jalankan program trait dan conformance
mojo run peserta.mojonama peserta: Dewi
nama peserta: BudiDua struct dengan field yang berbeda memenuhi kontrak yang sama dan dilayani oleh satu fungsi sebut. Perluasan ke struct ketiga hanya perlu menambahkan conformance Peserta beserta implementasi nama, tanpa mengubah fungsi generiknya.
Conformance Writable untuk pencetakan#
Pencetakan nilai struct melalui print menuntut conformance pada trait Writable, yaitu trait pustaka standar untuk tipe yang dapat menuliskan dirinya sebagai teks. Trait ini diperoleh dari modul std.format dengan pernyataan from std.format import Writable. Pada dokumentasi versi sebelum 1.0, peran ini dipegang oleh trait bernama Stringable dengan metode wajib __str__, sedangkan pada Mojo 1.2.0 trait untuk pencetakan adalah Writable dengan metode write_to.
Conformance Writable dapat ditulis tanpa satu pun metode, karena @fieldwise_init sekaligus menghasilkan implementasi pencetakan berbasis refleksi. Implementasi bawaan ini mencetak nama struct beserta seluruh fieldnya, asalkan setiap field juga memenuhi Writable. Program berikut memperlihatkan perilaku bawaan tersebut beserta konversi eksplisit ke String.
from std.format import Writable
@fieldwise_init
struct Suhu(Writable):
var celsius: Float64
var lokasi: String
def main():
var pagi = Suhu(20.5, "kamar")
var siang = Suhu(27.0, "teras")
print(pagi)
print(siang)
print(String(pagi))# jalankan program conformance Writable
mojo run suhu.mojoSuhu(celsius=20.5, lokasi=kamar)
Suhu(celsius=27.0, lokasi=teras)
Suhu(celsius=20.5, lokasi=kamar)Bentuk keluaran dapat diubah dengan mengoverride metode write_to pada struct. Tanda tangannya adalah def write_to(self, mut writer: Some[Writer]), dan isi field dituliskan dengan pemanggilan seperti writer.write(self.lokasi, ": ", self.celsius, " derajat"). Konversi String(s) memakai jalur yang sama dengan print, sehingga hasilnya konsisten di kedua tempat.
Kesalahan umum#
Menuliskan metode wajib trait dengan pass alih-alih tiga titik ditolak compiler. Berkas berikut memperlihatkan pesan errornya.
trait Peserta:
def nama(self) -> String:
pass
def main():
pass# jalankan berkas yang sengaja keliru
mojo run salah-pass.mojo/home/kal/my-project/content/mojo/struct-dan-trait/trait-dan-conformance/salah-pass.mojo:3:9: error: trait method with a return type must not use 'pass'; use '...' to declare the method as required
pass
^
/home/kal/my-project/content/mojo/struct-dan-trait/trait-dan-conformance/salah-pass.mojo:2:9: note: in 'nama', declared here
def nama(self) -> String:
^
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source modulePesan error menetapkan aturannya secara langsung, yaitu metode trait yang memiliki tipe kembalian wajib memakai ... agar dinyatakan sebagai metode wajib. Perbaikannya adalah mengganti pass dengan ... pada deklarasi metode di dalam trait.
Kesalahan kedua adalah mencetak struct yang belum memenuhi Writable. Compiler menolak pemanggilan print dengan pesan yang menyatakan bahwa tipe struct tersebut tidak memenuhi trait Writable.
@fieldwise_init
struct Suhu:
var celsius: Float64
def main():
var s = Suhu(20.5)
print(s)# jalankan berkas yang sengaja keliru
mojo run salah-print.mojo/home/kal/my-project/content/mojo/struct-dan-trait/trait-dan-conformance/salah-print.mojo:7:11: error: invalid call to 'print': an element of 'values' with type 'Suhu' does not conform to trait 'Writable'; either prove the conformance with 'conforms_to', or add conformance
print(s)
~~~~~ ^
Imported from /home/kal/my-project/content/mojo/struct-dan-trait/trait-dan-conformance/salah-print.mojo:1:
Mojo/stdlib/std/io/io.mojo:359:1: note: function declared here
def print[*Ts: Writable](*values: *Ts.values, *, sep: StringSpan = " ", end: StringSpan = "\n", flush: Bool = False, var file: RawFileDescriptor = stdout) # note - synthetic signature
^
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source modulePerbaikannya adalah menambahkan Writable ke daftar conformance struct, sebagaimana dilakukan pada berkas suhu.mojo.
Kesalahan ketiga adalah mendeklarasikan conformance tanpa mengimplementasikan seluruh metode wajibnya. Compiler menolak struct tersebut dengan pesan yang menyatakan bahwa struct tidak mengimplementasikan semua persyaratan trait, disertai catatan nama metode yang tertinggal. Perbaikannya adalah menuliskan implementasi setiap metode wajib di dalam struct. Penolakan yang sama terjadi bila tanda tangan implementasi tidak cocok dengan deklarasi trait, misalnya tipe kembalian yang berbeda. Dalam kedua kasus, compiler menyebut persyaratan yang gagal, sehingga perbaikannya dapat ditulis segera.
Ringkasan#
- Trait mendeklarasikan kontrak metode, dengan metode wajib berupa
...dan bukanpass. - Conformance dideklarasikan saat struct didefinisikan dengan menuliskan nama trait dalam tanda kurung, dan seluruh persyaratan wajib terpenuhi tanpa kecuali.
- Fungsi generik dengan parameter yang dibatasi trait, misalnya
def sebut[T: Peserta](p: T), melayani semua tipe yang memenuhi kontrak dengan dispatch statis. - Pencetakan struct pada Mojo 1.2.0 menuntut conformance
Writabledaristd.format, dan@fieldwise_initmenyediakan implementasi pencetakan field secara otomatis.
Referensi#
- Structs — Mojo Manual — Panduan resmi struct beserta penghubungannya dengan trait.
- Mojo Manual — Indeks dokumentasi resmi bahasa Mojo versi 1.2.0, termasuk halaman traits.
- Mojo standard library
— Peta API pustaka standar Mojo, termasuk trait
Writabledi modul format.