Struct adalah cara Mojo mendefinisikan tipe buatan sendiri. Topik ini membahas pendeklarasian struct dengan field bertipe, penulisan initializer, pembuatan nilai, serta konsekuensi tata letak tetap dan semantik nilai. Setelah topik ini dibaca, struct beserta nilainya dapat didefinisikan dan digunakan di dalam program dengan benar.
Struct sebagai tipe nilai#
Struct mengemas data dan operasi menjadi satu tipe. Data disimpan dalam field, yaitu variabel milik tipe yang dideklarasikan secara eksplisit, sedangkan operasi berbentuk metode yang didefinisikan di dalam struct yang sama. Bentuk sebuah struct ditetapkan pada waktu kompilasi (compile time), sehingga compiler mengetahui ukuran memori dan posisi setiap field sebelum program dijalankan. Berbeda dengan class Python, struct tidak mendukung pewarisan. Perluasan kemampuan tipe dilakukan lewat trait, yaitu daftar persyaratan metode yang dapat dipenuhi oleh sebuah struct, sebagaimana dibahas dalam modul ini.
Perbedaan struct Mojo dengan class Python paling terasa pada sifat statisnya. Tabel berikut merangkum perbedaan utamanya.
| Aspek | class Python | struct Mojo |
|---|---|---|
| Penambahan atribut | boleh dilakukan saat runtime | terlarang, field wajib dideklarasikan di awal |
| Pewarisan | didukung | tidak ada, digantikan trait |
| Penentuan bentuk tipe | saat runtime | saat kompilasi |
| Semantik nilai | referensi ke objek | nilai, diatur metode siklus hidup |
Semantik nilai (value semantics) berarti setiap variabel memiliki nilainya sendiri, dan perubahan pada satu variabel tidak memengaruhi variabel lain. Aturan penyalinan dan perpindahan nilai struct diatur oleh metode khusus siklus hidup yang didaftarkan lewat trait, bukan oleh keputusan runtime.
Mendeklarasikan field bertipe#
Setiap field wajib dideklarasikan dengan kata kunci var beserta anotasi tipe. Anotasi tipe bukan pelengkap, melainkan bagian dari jaminan kompilasi Mojo, karena compiler menggunakannya untuk menghitung tata letak memori dan memeriksa kebenaran setiap akses. Contoh berikut mendeklarasikan dua field bertipe Int.
# tidak-dijalankan
struct Titik:
var x: Int
var y: IntBeberapa aturan pendeklarasian field perlu dipatuhi secara bersamaan. Nama field wajib unik di dalam satu struct, karena field, metode, dan konstanta compile-time berada dalam satu ruang nama. Field juga tidak boleh diberi nilai pada saat deklarasi, sehingga bentuk var x: Int = 10 ditolak compiler. Nilai awal selalu diberikan lewat initializer, bukan pada baris deklarasi field. Semua akses field diperiksa compiler pada waktu kompilasi, sehingga salah nama pada notasi titik terdeteksi sebelum program berjalan.
Field diakses dari luar struct dengan notasi titik, misalnya t.x, dan diakses dari dalam metode melalui self. Tidak ada level akses privat bawaan, sehingga konvensi penamaan dengan awalan garis bawah digunakan untuk menandai field yang hanya ditujukan bagi penggunaan internal.
Initializer init dan pembuatan nilai#
Struct tanpa initializer tidak dapat dibuat nilainya, karena compiler tidak mengetahui cara mengisi field-fieldnya. Initializer adalah metode khusus bernama __init__ yang membangun sebuah nilai dari argumen yang diberikan. Argumen pertamanya selalu out self, sebuah konvensi argumen yang menyatakan bahwa self mulai dalam keadaan tidak terinisialisasi dan wajib diisi sepenuhnya sebelum metode berakhir. Apabila ada satu field saja yang tertinggal, compiler menolak kode tersebut.
# tidak-dijalankan
struct Titik:
var x: Int
var y: Int
def __init__(out self, x: Int, y: Int):
self.x = x
self.y = yNilai dibuat dengan memanggil nama struct seperti pemanggilan fungsi, misalnya var t = Titik(3, 4). Argumen self tidak dikirim secara manual, karena Mojo menyediakannya secara otomatis. Argumen boleh ditulis menurut posisi maupun dengan nama, sehingga Titik(x=3, y=4) juga sah. Banyak initializer memang berbentuk penugasan satu argumen ke satu field dengan nama yang sama, dan initializer hasil decorator menerima argumen posisi maupun argumen bernama dengan perilaku yang sama. Mojo menyediakan decorator @fieldwise_init untuk menghasilkan initializer semacam itu secara otomatis, sebagaimana dibahas dalam modul ini.
Program utuh titik.mojo#
Buat berkas titik.mojo dengan isi berikut, kemudian jalankan dengan perintah mojo run.
struct Titik:
var x: Int
var y: Int
def __init__(out self, x: Int, y: Int):
self.x = x
self.y = y
def main():
var t = Titik(3, 4)
print("x awal:", t.x)
print("y awal:", t.y)
t.x = 10
print("x baru:", t.x)# jalankan program struct pertama
mojo run titik.mojox awal: 3
y awal: 4
x baru: 10Eksekusi pertama membutuhkan waktu beberapa detik karena mojo run mengompilasi program sebelum menjalankannya, sedangkan pemanggilan berikutnya pada berkas yang sama lebih cepat. Argumen initializer juga dapat dikirim dengan nama, sehingga baris pembuatan nilai dapat ditulis var t = Titik(x=3, y=4) tanpa mengubah hasilnya. Pembacaan dan pengubahan field pada program ini berlangsung langsung pada variabel t, dan semua pemeriksaan tipe terhadap t.x serta t.y diselesaikan compiler sebelum program berjalan.
Program di atas memperlihatkan tiga peristiwa pada satu nilai struct. Titik(3, 4) memanggil __init__ dan mengisi kedua field, t.x dan t.y membaca field lewat notasi titik, dan t.x = 10 mengubah isi field pada variabel yang dideklarasikan dengan var. Perubahan t.x hanya memengaruhi nilai pada variabel t, karena variabel tersebut menjadi pemilik nilainya sendiri. Dengan demikian, satu nilai struct dapat dibuat, dibaca, dan diubah tanpa mekanisme tambahan apa pun.
Tata letak tetap dan semantik nilai#
Karena semua field dideklarasikan dengan tipe yang eksplisit, compiler menghitung tata letak memori struct secara lengkap sebelum program berjalan. Offset setiap field bersifat tetap, sehingga akses field dikompilasi menjadi operasi memori yang langsung dan cepat. Ukuran satu nilai struct pun tetap, sehingga alokasi memori untuknya dapat dihitung tanpa memeriksa keadaan program. Tata letak tersebut juga tidak dapat berubah, baik dengan menambah field baru maupun dengan mengganti tipe field saat program berjalan.
Tata letak tetap dan semantik nilai berdampingan. Saat sebuah nilai struct berpindah antarvariabel, compiler memindahkan atau menyalin isi nilai secara utuh, bukan membuat referensi kedua yang menunjuk memori yang sama. Secara bawaan, struct dapat dipindahkan tetapi tidak dapat disalin. Penyalinan hanya terjadi bila struct mendefinisikan kemampuan salin lewat trait Copyable, sedangkan perpindahan menggunakan operator transfer ^ pada bentuk seperti var b = a^. Perbedaan perilaku antara penyalinan, perpindahan, dan penghancuran nilai diatur oleh metode khusus siklus hidup yang dibahas dalam modul ini.
Kesalahan umum#
Menambahkan field baru saat program berjalan adalah kebiasaan class Python yang langsung ditolak Mojo. Berkas berikut memperlihatkan penolakan tersebut beserta pesan errornya.
struct Titik:
var x: Int
var y: Int
def __init__(out self, x: Int, y: Int):
self.x = x
self.y = y
def main():
var t = Titik(3, 4)
t.z = 5
print(t.x)# jalankan berkas yang sengaja keliru
mojo run salah-field.mojo/home/kal/my-project/content/mojo/struct-dan-trait/dasar-struct/salah-field.mojo:11:6: error: 'Titik' value has no attribute 'z'
t.z = 5
~^
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source modulePesan error menyatakan bahwa nilai bertipe Titik tidak memiliki atribut z. Perbaikannya adalah mendeklarasikan field z di dalam struct sejak awal beserta tipenya, lalu mengisinya di dalam initializer.
Kesalahan kedua adalah mendeklarasikan field tanpa kata kunci var atau tanpa tipe. Field tanpa var ditolak dengan pesan bare expressions must not appear within structs, sedangkan field tanpa tipe ditolak dengan pesan struct field declaration must have a type. Keduanya diperbaiki dengan menuliskan var dan anotasi tipe pada setiap field.
Kesalahan ketiga adalah membuat nilai dari struct yang belum memiliki initializer. Pemanggilan seperti Titik(3, 4) pada struct tanpa __init__ menghasilkan error invalid initialization karena tidak ada initializer yang cocok. Perbaikannya adalah menuliskan __init__ secara manual atau menambahkan decorator @fieldwise_init di atas definisi struct. Ketiga kesalahan di atas terdeteksi selama kompilasi, sehingga program yang memuatnya tidak pernah sempat berjalan.
Ringkasan#
- Struct mendefinisikan tipe dengan field bertipe yang bentuknya ditetapkan pada waktu kompilasi, tanpa pewarisan dan tanpa perubahan bentuk saat runtime.
- Field wajib dideklarasikan dengan
vardan tipe, tidak boleh diberi nilai saat deklarasi, dan seluruhnya wajib diisi oleh initializer. __init__(out self, ...)membangun nilai dengan konvensi argumenout, dan nilai dibuat dengan memanggil nama struct menurut posisi maupun dengan nama argumen.- Semantik nilai membuat setiap variabel menjadi pemilik nilainya sendiri, dengan penyalinan dan perpindahan yang diatur lewat trait serta metode siklus hidup.
Referensi#
- Structs — Mojo Manual — Panduan resmi pendefinisian struct, field, metode, dan kemampuan penyalinan struct.
- Mojo Manual — Indeks dokumentasi resmi bahasa Mojo versi 1.2.0.