inout dan owned secara mendalam

Menggarap peminjaman yang dapat diubah dan transfer yang mengonsumsi, dari inout dan owned lama ke mut dan var terkini.

Daftar isi

Konvensi argumen Mojo pada era pra-1.0 ditulis dengan keyword inout dan owned, ditambah borrowed untuk peminjaman. Ketiganya dihapus sejak rilis 1.0 dan digantikan konvensi tanpa keyword, mut, dan var. Topik ini menggarap dua konvensi terkini yang menulis dan mengonsumsi nilai secara mendalam, lalu memetakan keyword lama ke padanannya agar materi lama tetap terbaca dengan benar. Pemetaan ini juga berguna saat signature pustaka standar dibaca, karena kedua konvensi terkini tersebut muncul pada banyak tipe dan metode bawaan.

Peta keyword lama ke konvensi terkini#

Nama keyword lama masih sering muncul pada artikel, contoh kode, dan diskusi yang ditulis sebelum 1.0, sehingga pembacaan materi lama membutuhkan peta penerjemahan. Tabel berikut memuat status ketiganya pada Mojo 1.2.0 beserta padanannya yang resmi.

Keyword lamaStatus di 1.2.0Padanan kini
borroweddihapusparameter tanpa keyword, yaitu peminjaman immutable
inoutdihapusmut
owneddihapusvar, dengan transfer ^ pada pemanggilan

Jalankan berkas berikut yang masih menuliskan inout untuk melihat bentuk penolakannya.

mojo kata-lama.mojo
def ganti(inout a: Int):
    a = 99

def main():
    var x = 5
    ganti(x)
    print(x)
bash
# jalankan berkas yang masih memakai keyword inout dari era lama
mojo run kata-lama.mojo 2>&1 | sed -e "s|$PWD/||" -e "s|$HOME/|~/|"
text Keluaran
kata-lama.mojo:1:17: error: expected ')' in argument list
def ganti(inout a: Int):
                ^
~/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source module

Ketiga keyword lama gagal dengan pesan yang sama, yaitu expected ')' in argument list, karena parser tidak lagi mengenalinya sebagai konvensi argumen. Pesannya tampak tidak berhubungan dengan konvensi, tetapi letaknya selalu pada tanda kurung signature, sehingga mudah dilacak. Terjemahkan borrowed menjadi parameter tanpa keyword, inout menjadi mut, dan owned menjadi var sebelum kode lama dijalankan pada versi terkini.

Terjemahan tiga keyword tersebut hanya menyentuh signature, sedangkan isi fungsi dan pemanggilannya umumnya tetap. Keyword borrowed boleh dihapus begitu saja karena padanannya adalah keadaan bawaan, sedangkan inout dan owned diganti satu kata dengan mut dan var. Sisa pekerjaannya adalah memeriksa tempat pemanggilan, karena argumen yang diteruskan ke parameter var wajib memakai ^ bila tipenya tidak dapat disalin.

mojo Contoh: penerjemahan signature era lama
# tidak-dijalankan
def baca(borrowed a: Int):     # menjadi: def baca(a: Int):
def ganti(inout a: Int):       # menjadi: def ganti(mut a: Int):
def konsumsi(owned a: Int):    # menjadi: def konsumsi(var a: Int):

Peminjaman yang dapat diubah lewat mut#

Konvensi mut memberi fungsi referensi yang dapat diubah ke nilai milik pemanggil. Fungsi menulis lewat jalur ini, dan perubahannya langsung terlihat oleh pemanggil tanpa memerlukan nilai balikan. Kepemilikan tidak berpindah, sehingga nilai tetap sah setelah pemanggilan selesai. Eksklusivitas peminjaman tetap berlaku di sini, karena mut adalah jalur penulisan yang tidak boleh bertumpuk dengan peminjaman lain pada nilai yang sama.

Jalankan program berikut yang menukar isi dua variabel pemanggil lewat dua parameter mut.

mojo tukar.mojo
def tukar(mut a: Int, mut b: Int):
    var sementara = a
    a = b
    b = sementara

def main():
    var x = 1
    var y = 2
    tukar(x, y)
    print(x, y)
bash
# jalankan program yang menukar isi dua variabel lewat mut
mojo run tukar.mojo
text Keluaran
2 1

Keluaran 2 1 membuktikan penulisan lewat a dan b terjadi pada x dan y milik pemanggil, bukan pada salinannya. Sebelum ditukar, nilai a dibaca satu kali untuk mengisi sementara, dan pembacaan semacam ini sah pada parameter mut karena peminjaman yang dapat diubah tetap boleh membaca. Pola ini menggantikan kebiasaan mengembalikan nilai balikan ganda dari fungsi, karena perubahan langsung terjadi pada tempat penyimpanan pemanggil. Pada metode struct, mekanismenya sama persis, yaitu mutasi self membutuhkan parameter mut self sebagaimana tertulis pada signature metode mutator.

Transfer yang mengonsumsi lewat var#

Konvensi var menjadikan parameter pemilik sebuah nilai yang baru. Pemanggil harus melepaskan nilainya, dan untuk tipe yang tidak dapat disalin, pelepasan itu dituliskan dengan operator ^ pada argumen. Di dalam fungsi, parameter var berlaku seperti variabel lokal yang diwarisi, sehingga boleh dibaca, diubah, atau dipindahkan lagi ke tempat lain. Untuk tipe yang dapat disalin seperti Int, pemanggilan tanpa ^ tetap sah karena compiler membuat salinannya. Sesudah transfer berjalan, variabel sumber tidak terinisialisasi, sehingga pemakaiannya ditolak compiler sebagaimana perilaku operator transfer pada umumnya.

Jalankan program berikut yang memakai kedua konvensi tersebut pada struct yang tidak dapat disalin.

mojo mut-var.mojo
struct Catatan:
    var isi: String

    def __init__(out self, isi: String):
        self.isi = isi

def panjangkan(mut c: Catatan, tambahan: String):
    c.isi += tambahan

def konsumsi(var c: Catatan):
    print("dikonsumsi:", c.isi)

def main():
    var c = Catatan("awal")
    panjangkan(c, "-lanjutan")
    print(c.isi)
    konsumsi(c^)
    print("selesai")
bash
# jalankan program yang memakai konvensi mut dan var
mojo run mut-var.mojo
text Keluaran
awal-lanjutan
dikonsumsi: awal-lanjutan
selesai

Fungsi panjangkan menerima mut dan menambah isi Catatan tanpa memindahkan kepemilikannya, sehingga print setelahnya masih membaca isi terbaru dari variabel c. Fungsi konsumsi menerima var dan dipanggil dengan c^, yang menandakan kepemilikan nilai Catatan diserahkan kepada parameter tersebut. Baris print("selesai") berjalan tanpa masalah karena tidak lagi menyentuh c sesudah transfer. Apabila pemanggilan dilakukan tanpa ^, compiler menolaknya, dan bentuk penolakannya ditunjukkan pada bagian Kesalahan umum. Pemilihan di antara kedua konvensi ini pun mengikuti maksud, yaitu mut dipilih bila pemanggil masih membutuhkan nilainya, sedangkan var dipilih bila fungsi memang menjadikan nilai itu miliknya.

Kesalahan umum#

  • Menulis keyword lama pada signature. inout, owned, dan borrowed tidak dikenal parser Mojo 1.2.0, dan pesannya berupa error sintaks pada tanda kurung daftar argumen. Perbaikannya adalah menerjemahkan keyword tersebut sesuai pemetaan pada tabel di atas.
  • Meneruskan nilai yang tidak dapat disalin ke parameter var tanpa ^. Parameter var membutuhkan kepemilikan nilai, sedangkan tanpa ^ compiler harus menyalin, dan salinan implisit itulah yang ditolak. Jalankan berkas berikut untuk melihat pesannya.
mojo tanpa-transfer.mojo
struct Catatan:
    var isi: String

    def __init__(out self, isi: String):
        self.isi = isi

def konsumsi(var c: Catatan):
    print("dikonsumsi:", c.isi)

def main():
    var c = Catatan("awal")
    konsumsi(c)
    print(c.isi)
bash
# jalankan berkas yang meneruskan nilai ke parameter var tanpa transfer
mojo run tanpa-transfer.mojo 2>&1 | sed -e "s|$PWD/||" -e "s|$HOME/|~/|"
text Keluaran
tanpa-transfer.mojo:12:14: error: value of type 'Catatan' cannot be implicitly copied, it does not conform to 'ImplicitlyCopyable'
    konsumsi(c)
             ^
tanpa-transfer.mojo:12:14: note: consider transferring the value with '^'
    konsumsi(c)
             ^
              ^
~/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source module

Perbaikannya mengikuti maksud pemanggilannya. Tuliskan konsumsi(c^) bila kepemilikan memang berpindah ke fungsi, dan jangan menyentuh c sesudahnya. Apabila pemanggil masih membutuhkan nilainya, sediakan salinan eksplisit pada tipe yang mendukungnya, atau ubah signature menjadi mut bila yang diinginkan hanyalah perubahan tanpa perpindahan kepemilikan.

Ringkasan#

  • Keyword lama borrowed, inout, dan owned dihapus sejak 1.0, dan padanannya kini adalah parameter tanpa keyword, mut, serta var dengan transfer ^.
  • Konvensi mut menulis ke nilai pemanggil melalui referensi yang dapat diubah, tanpa memindahkan kepemilikan dan tanpa menyalin.
  • Konvensi var menjadikan parameter pemilik nilai baru, sehingga pemanggil menyerahkan nilainya dengan ^ untuk tipe yang tidak dapat disalin.

Referensi#