Setiap nilai struct menjalani empat peristiwa siklus hidup, yaitu dibuat, disalin, dipindah, dan dihancurkan. Topik ini membahas metode khusus yang mengatur setiap peristiwa, mulai dari initializer dan @fieldwise_init, metode penyalinan dengan trait Copyable, perpindahan dengan operator transfer ^, hingga deinitializer __deinit__. Setelah topik ini dibaca, masa hidup sebuah nilai dapat diatur dengan perilaku yang jelas dan dapat diprediksi.
Initializer dan @fieldwise_init#
Peristiwa pertama pada masa hidup sebuah nilai adalah pembuatan. Initializer __init__(out self, ...) membangun nilai dengan mengisi seluruh field, dan compiler menolak initializer yang menyisakan satu field saja tanpa nilai. Banyak initializer berbentuk sederhana, yaitu setiap argumen langsung diisikan ke field dengan nama yang sama. Untuk bentuk yang berulang itu, decorator @fieldwise_init di atas definisi struct menghasilkan initializer secara otomatis, sehingga satu pun __init__ tidak perlu ditulis secara manual. Initializer hasil decorator ini menerima argumen posisi maupun argumen bernama, sehingga pembuatan nilai dapat ditulis Titik(x=3, y=4).
@fieldwise_init
struct Titik(Copyable):
var x: Int
var y: Int
def main():
var a = Titik(3, 4)
var b = a.copy()
print("a:", a.x, ",", a.y)
print("b:", b.x, ",", b.y)# jalankan program initializer fieldwise
mojo run fieldwise.mojoa: 3 , 4
b: 3 , 4Dua cara pembuatan nilai muncul di program di atas. Titik(3, 4) memakai initializer hasil @fieldwise_init, sedangkan a.copy() memakai metode penyalinan yang disediakan oleh trait Copyable. Conformance Copyable tetap perlu dideklarasikan secara eksplisit, karena decorator @fieldwise_init hanya mengurusi initializer biasa dan bukan kemampuan penyalinan. Aturan yang sama berlaku sebaliknya, yaitu struct dengan initializer manual tetap wajib mendeklarasikan conformance yang dikehendaki.
Penyalinan dengan trait Copyable#
Trait Copyable menyatakan bahwa sebuah tipe boleh diduplikasi. Dengan conformance tersebut, metode .copy() tersedia pada setiap nilai, dan metode itulah cara penyalinan yang disarankan. Kemampuan penyalinan didukung oleh metode khusus bernama copy initializer dengan tanda tangan def __init__(out self, *, copy: Self). Tanda * pada tanda tangan menyatakan bahwa argumen copy hanya dapat ditulis dengan nama, sehingga salinan juga dapat dibuat lewat bentuk Titik(copy=a).
Bila struct hanya berisi field sederhana, Mojo menghasilkan copy initializer secara otomatis begitu conformance Copyable dideklarasikan. Metode khusus ini perlu ditulis manual hanya bila penyalinan memerlukan logika tambahan, misalnya menyalin memori yang dialokasikan secara manual atau menghitung jumlah salinan. Penyalinan lewat .copy() diutamakan daripada salinan otomatis yang disisipkan compiler karena biayanya terlihat langsung di tempat pemanggilan.
Trait ImplicitlyCopyable mengizinkan compiler menyisipkan salinan tanpa ditulis secara eksplisit, misalnya saat sebuah nilai dipakai di dua tempat. Trait ini sebaiknya dideklarasikan hanya untuk tipe yang murah disalin dan bebas efek samping, karena salinan tersembunyi dapat membebani kinerja tanpa disadari.
Perpindahan dengan operator transfer#
Berbeda dengan penyalinan, perpindahan memindahkan kepemilikan nilai dari satu variabel ke variabel lain tanpa menduplikasi isinya. Secara bawaan, struct dapat dipindahkan tetapi tidak dapat disalin, sehingga transfer adalah perilaku penugasan yang tersedia untuk semua struct. Penulisan transfernya memakai operator ^ pada sumber nilai, misalnya var c = b^. Setelah transfer, b berada dalam keadaan tidak terinisialisasi dan tidak boleh dipakai lagi, sedangkan c menjadi pemilik nilai tersebut.
Perpindahan didukung oleh metode move initializer dengan tanda tangan def __init__(out self, *, deinit move: Self). Konvensi argumen deinit menyatakan bahwa nilai sumber dikonsumsi oleh initializer tersebut, sehingga tidak ada deinitializer yang dijalankan pada nilai sumber setelahnya. Move initializer hanya perlu ditulis manual bila perpindahan memerlukan perilaku khusus, karena compiler menyintesisnya untuk struct sederhana.
Penghancuran dengan deinit#
Peristiwa terakhir adalah penghancuran, yang diatur oleh deinitializer dengan tanda tangan def __deinit__(deinit self). Di dalam metode ini, field masih valid dan dapat dibaca, sehingga pembersihan seperti penutupan berkas atau pembebasan memori manual dapat dilakukan dengan aman. Setelah metode berakhir, nilai dinyatakan tidak terinisialisasi.
Mojo menerapkan kebijakan destruksi secepat mungkin (as-soon-as-possible destruction), yaitu nilai dihancurkan segera setelah pemakaian terakhirnya selesai, tanpa menunggu akhir blok. Compiler menentukan pemakaian terakhir setiap nilai pada waktu kompilasi, lalu menyisipkan pemanggilan __deinit__ tepat di titik itu. Nilai perantara di dalam sebuah ekspresi pun mengalami hal yang sama. Nilai yang dibuat tetapi tidak pernah dipakai langsung dihancurkan, dan compiler menyertakan peringatan penugasan tidak terpakai untuk mencerminkan keadaan tersebut.
Mojo menghasilkan __deinit__ secara otomatis untuk struct dengan field yang dapat dihancurkan, sehingga metode ini perlu ditulis manual hanya bila struct memegang sumber daya yang menuntut pembersihan khusus. Urutan penghancuran pada program utuh di bawah memperlihatkan perilaku tersebut secara nyata.
Program utuh siklus.mojo#
Buat berkas siklus.mojo dengan isi berikut, kemudian jalankan.
struct Titik(Copyable):
var x: Int
var y: Int
def __init__(out self, x: Int, y: Int):
self.x = x
self.y = y
print("init: dibuat")
def __init__(out self, *, copy: Self):
self.x = copy.x
self.y = copy.y
print("init salin: nilai diduplikasi")
def __init__(out self, *, deinit move: Self):
self.x = move.x
self.y = move.y
print("init pindah: kepemilikan berpindah")
def __deinit__(deinit self):
print("deinit: nilai dihancurkan")
def main():
var a = Titik(3, 4)
var b = a.copy()
print("isi b:", b.x, ",", b.y)
var c = b^
print("isi c:", c.x, ",", c.y)
print("isi a:", a.x, ",", a.y)# jalankan program siklus hidup lengkap
mojo run siklus.mojoinit: dibuat
init salin: nilai diduplikasi
isi b: 3 , 4
init pindah: kepemilikan berpindah
isi c: 3 , 4
deinit: nilai dihancurkan
isi a: 3 , 4
deinit: nilai dihancurkanDelapan baris keluaran tersebut memuat seluruh rangkaian siklus hidup. a dibuat lewat initializer biasa, lalu b dibuat lewat copy initializer. Transfer b^ memanggil move initializer, dan b tidak menjalankan __deinit__ karena nilainya telah dikonsumsi oleh c. Dua baris deinit muncul di posisi yang tidak menunggu akhir main, yaitu c dihancurkan segera setelah pemakaian terakhirnya, dan a dihancurkan setelah baris print terakhir yang membacanya. Posisi itu adalah bukti langsung kebijakan destruksi secepat mungkin.
Ketiga initializer pada struct ini bernama sama __init__ dan dibedakan oleh argumen bernamanya. Pemanggilan a.copy() mengaktifkan copy initializer melalui argumen copy, sedangkan transfer b^ mengaktifkan move initializer melalui argumen deinit move. Bentuk Titik(copy=a) menghasilkan salinan yang sama dengan a.copy(), sehingga pilihan bentuk pemanggilannya dapat disesuaikan dengan kejelasan yang dikehendaki. Dengan pemisahan ini, satu nama initializer mencakup seluruh peristiwa pembuatan, mulai dari nilai baru, salinan, hingga nilai pindahan.
Kesalahan umum#
Menugaskan struct ke variabel lain tanpa penyalinan eksplisit adalah kesalahan yang paling sering muncul. Compiler menolaknya karena struct tidak memenuhi ImplicitlyCopyable. Berkas berikut memperlihatkan penolakan tersebut.
@fieldwise_init
struct Titik(Copyable):
var x: Int
var y: Int
def main():
var a = Titik(3, 4)
var b = a
print(b.x)# jalankan berkas yang sengaja keliru
mojo run salah-salin.mojo/home/kal/my-project/content/mojo/struct-dan-trait/metode-siklus-hidup/salah-salin.mojo:8:13: error: value of type 'Titik' cannot be implicitly copied, it does not conform to 'ImplicitlyCopyable'
var b = a
^
/home/kal/my-project/content/mojo/struct-dan-trait/metode-siklus-hidup/salah-salin.mojo:8:13: note: consider transferring the value with '^'
var b = a
^
^
/home/kal/my-project/content/mojo/struct-dan-trait/metode-siklus-hidup/salah-salin.mojo:8:13: note: you can copy it explicitly with '.copy()'
var b = a
^
.copy()
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source modulePesan errornya sekaligus menawarkan dua perbaikan, yaitu transfer dengan a^ apabila kepemilikan ingin berpindah, atau penyalinan eksplisit dengan a.copy() apabila duplikasi memang dikehendaki.
Kesalahan kedua adalah memakai nilai setelah ditransfer. Bentuk seperti print(a.x) setelah var b = a^ ditolak compiler dengan pesan error yang menyatakan bahwa a berada dalam keadaan tidak terinisialisasi. Perbaikannya adalah memastikan hanya variabel penerima yang dipakai setelah titik transfer.
Kesalahan ketiga adalah mengasumsikan __deinit__ berjalan di akhir blok. Destruksi berjalan di titik pemakaian terakhir, sehingga pesan dari deinitializer dapat muncul di tengah fungsi, seperti urutan keluaran siklus.mojo di atas. Apabila rentang hidup nilai perlu diperpanjang sampai titik tertentu, nyatakan pemakaian terakhirnya secara eksplisit dengan penugasan ke _ pada baris yang dikehendaki. Compiler bahkan menyarankan penugasan itu melalui peringatan bila sebuah nilai dibuat tetapi tidak pernah dipakai.
Ringkasan#
@fieldwise_initmenghasilkan initializer fieldwise, sedangkan initializer manual__init__(out self, ...)dipakai bila pembuatan nilai memerlukan logika tersendiri.- Conformance
Copyablemenyediakan.copy()serta copy initializer__init__(out self, *, copy: Self), dan compiler menyintesiskannya untuk struct sederhana. - Transfer
var b = a^memindahkan kepemilikan lewat move initializer__init__(out self, *, deinit move: Self), dan nilai sumber tidak boleh dipakai lagi setelahnya. __deinit__(deinit self)berjalan tepat setelah pemakaian terakhir nilai sesuai kebijakan destruksi secepat mungkin, dan hanya perlu ditulis manual untuk pembersihan khusus.
Referensi#
- Structs — Mojo Manual
— Panduan resmi kemampuan penyalinan dan perpindahan struct beserta trait
CopyabledanImplicitlyCopyable. - Mojo Manual — Indeks dokumentasi resmi bahasa Mojo versi 1.2.0, termasuk halaman siklus hidup nilai.
- Mojo standard library
— Peta API pustaka standar Mojo, termasuk trait
CopyabledanMovable.