Konvensi argumen

Menerapkan konvensi argumen bawaan, mut, dan var beserta implikasi peminjaman dan kepemilikannya.

Daftar isi

Konvensi argumen menentukan bagaimana nilai dari pemanggil diteruskan ke parameter fungsi, dan Mojo menetapkannya secara eksplisit pada signature setiap fungsi. Topik ini membahas tiga konvensi terkini, yaitu parameter tanpa kata kunci untuk peminjaman (borrowing), kata kunci mut untuk referensi yang dapat diubah, dan var untuk perpindahan kepemilikan (ownership), lengkap dengan implikasinya terhadap nilai di luar fungsi. Kata kunci lama borrowed, inout, dan owned yang dihapus sejak rilis 1.0 dipetakan secara singkat ke penggantinya. Setiap perilaku diperagakan pada berkas yang dapat dijalankan, termasuk penulisan yang ditolak compiler beserta pesan error aslinya, sehingga pemilihan konvensi dapat dilakukan dengan sadar pada setiap fungsi.

Konvensi bawaan: peminjaman yang tak dapat diubah#

Parameter tanpa kata kunci apa pun meminjam nilai milik pemanggil melalui referensi yang tak dapat diubah. Fungsi dapat membaca nilai tersebut selancar membaca variabel biasa, tetapi tidak dapat mengubahnya dan tidak menyalinnya secara diam-diam. Konvensi ini menjadi pilihan bawaan karena mayoritas argumen hanya dibaca, sehingga nilai berukuran besar tidak pernah disalin tanpa perintah eksplisit.

Berkas baca.mojo menjumlahkan seluruh elemen sebuah List[Int] melalui argumen tanpa kata kunci. Perulangan for nilai in daftar membaca elemen satu per satu, dan panjang daftar diperiksa kembali di luar fungsi untuk menunjukkan bahwa daftar aslinya tidak tersentuh.

mojo baca.mojo
def jumlah(daftar: List[Int]) -> Int:
    var total = 0
    for nilai in daftar:
        total += nilai
    return total


def main():
    var nilai: List[Int] = [10, 20, 30]
    print(jumlah(nilai))
    print(len(nilai))
bash
# argumen tanpa kata kunci meminjam tanpa menyalin
mojo run baca.mojo
text Keluaran
60
3

Keluaran 60 adalah jumlah dari ketiga elemen, sedangkan 3 menunjukkan bahwa panjang daftar tetap setelah pemanggilan. Nilai yang dipinjam tidak berpindah dan tidak berubah, sehingga pemanggil dapat melanjutkan penggunaannya tanpa risiko. Penugasan pada argumen bawaan di dalam fungsi ditolak compiler, sebagaimana diperagakan pada bagian Kesalahan umum.

Argumen mut: referensi yang dapat diubah#

Kata kunci mut pada argumen menyatakan referensi yang dapat diubah, sehingga penugasan di dalam fungsi menyasar penyimpanan milik pemanggil. Perilaku ini berbeda dari Python. Pada Python, penugasan pada nama parameter hanya mengaitkan ulang nama lokal tanpa menyentuh nilai pemanggil. Pada Mojo, fungsi dengan argumen mut mengubah nilai aslinya, dan perubahannya terbaca di luar fungsi.

Berkas mutasi.mojo memuat dua fungsi dengan argumen mut. Fungsi ganti menugaskan 99 pada argumen mut a: Int, sedangkan fungsi tambah_item menambahkan elemen pada List[Int] lewat argumen mut daftar.

mojo mutasi.mojo
def ganti(mut a: Int):
    a = 99


def tambah_item(mut daftar: List[Int]):
    daftar.append(4)


def main():
    var x = 5
    ganti(x)
    print(x)

    var daftar: List[Int] = [1, 2, 3]
    tambah_item(daftar)
    print(daftar)
bash
# perubahan lewat argumen mut terlihat di luar fungsi
mojo run mutasi.mojo
text Keluaran
99
[1, 2, 3, 4]

Keluaran 99 menunjukkan bahwa penugasan pada argumen mut mengubah variabel x milik pemanggil, dan [1, 2, 3, 4] menunjukkan elemen yang ditambahkan tetap ada setelah fungsi berakhir. Pembaca signature langsung mengetahui fungsi mana yang menulis nilai pemanggilnya, karena penanda mut wajib ditulis pada setiap argumen yang hendak diubah.

Konvensi mut menuntut eksklusivitas, yaitu sebuah nilai tidak boleh dipinjam secara dapat diubah dan tak dapat diubah pada saat bersamaan. Berkas alias.mojo melanggar aturan tersebut dengan mengirim variabel pesan yang sama sebagai argumen mut s sekaligus argumen bawaan other dalam satu pemanggilan.

mojo alias.mojo
def gabung(mut s: String, other: String):
    s += other
    s += other


def main():
    var pesan = "hai"
    gabung(pesan, pesan)
    print(pesan)
bash
# peminjaman mut dan bawaan pada nilai yang sama ditolak
mojo run alias.mojo
text Keluaran
/home/kal/my-project/content/mojo/dua-jenis-fungsi/konvensi-argumen/alias.mojo:8:11: error: aliasing values passed mutably to 's' argument and passed immutably to 'other' argument in 'gabung' call
    gabung(pesan, pesan)
          ^~~~~~  ~~~~~
/home/kal/my-project/content/mojo/dua-jenis-fungsi/konvensi-argumen/alias.mojo:8:11: note: 'origin_of(pesan)' value is passed through aliasing 'imm' argument 'other'
    gabung(pesan, pesan)
          ^       ~~~~~
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source module

Pesan error menyebut argumen yang bertabrakan, yaitu s yang diteruskan secara dapat diubah dan other yang dipinjam secara tak dapat diubah, keduanya berasal dari nilai yang sama. Eksklusivitas mencegah fungsi membaca nilai pada saat nilai yang sama sedang ditulis, sehingga hasil setiap pembacaan selalu konsisten. Perbaikannya adalah mengirim nilai yang berbeda atau mengubah konvensi salah satu argumennya.

Argumen var: kepemilikan berpindah#

Kata kunci var pada argumen menyatakan bahwa fungsi menjadi pemilik nilai tersebut, sehingga nilai berpindah dari pemanggil ke fungsi. Untuk tipe yang dapat disalin seperti Int, perpindahan berbentuk salinan, sehingga perubahan di dalam fungsi hanya menyentuh salinannya. Untuk tipe yang tidak mendukung penyalinan implisit seperti List, kepemilikan memang berpindah, dan pemanggil menyerahkannya dengan operator transfer ^.

Berkas salin.mojo menugaskan 77 pada argumen var a: Int kemudian mencetak nilai tersebut di dalam dan di luar fungsi. Nilai x milik pemanggil turut dicetak sebagai pembanding.

mojo salin.mojo
def salin(var a: Int):
    a = 77
    print("di dalam fungsi:", a)


def main():
    var x = 5
    salin(x)
    print("di luar fungsi:", x)
bash
# argumen var pada tipe yang dapat disalin
mojo run salin.mojo
text Keluaran
di dalam fungsi: 77
di luar fungsi: 5

Keluaran menunjukkan bahwa kedua nilai berbeda karena penugasan di dalam fungsi hanya mengubah salinan yang dimiliki fungsi, sehingga x milik pemanggil tetap 5. Pada tipe lain yang tidak dapat disalin, pola yang sama tidak dapat berjalan tanpa penyerahan kepemilikan. Berkas salin-daftar.mojo mencoba mengirim List[Int] ke argumen var tanpa operator transfer, dan compiler menolaknya.

mojo salin-daftar.mojo
def konsumsi(var daftar: List[Int]):
    daftar.append(4)


def main():
    var nilai: List[Int] = [1, 2, 3]
    konsumsi(nilai)
bash
# List tidak dapat disalin implisit untuk argumen var
mojo run salin-daftar.mojo
text Keluaran
/home/kal/my-project/content/mojo/dua-jenis-fungsi/konvensi-argumen/salin-daftar.mojo:7:14: error: value of type 'List[Int]' cannot be implicitly copied, it does not conform to 'ImplicitlyCopyable'
    konsumsi(nilai)
             ^~~~~
/home/kal/my-project/content/mojo/dua-jenis-fungsi/konvensi-argumen/salin-daftar.mojo:7:14: note: consider transferring the value with '^'
    konsumsi(nilai)
             ^
                  ^
/home/kal/my-project/content/mojo/dua-jenis-fungsi/konvensi-argumen/salin-daftar.mojo:7:14: note: you can copy it explicitly with '.copy()'
    konsumsi(nilai)
             ^
                  .copy()
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source module

Pesan error menyatakan bahwa nilai List[Int] tidak dapat disalin secara implisit karena tidak memenuhi trait ImplicitlyCopyable, yaitu kontrak bahasa untuk tipe yang boleh disalin tanpa perintah eksplisit. Compiler menawarkan dua jalan, yaitu transfer dengan ^ atau salinan eksplisit dengan .copy(). Berkas transfer.mojo menerapkan saran pertama.

mojo transfer.mojo
def konsumsi(var daftar: List[Int]):
    daftar.append(4)
    print("di dalam fungsi:", daftar)


def main():
    var nilai: List[Int] = [1, 2, 3]
    konsumsi(nilai^)
bash
# kepemilikan diserahkan dengan operator transfer
mojo run transfer.mojo
text Keluaran
di dalam fungsi: [1, 2, 3, 4]

Keluaran menunjukkan bahwa fungsi menerima kepemilikan daftar lalu menambahkan elemen 4. Tanda ^ pada pemanggilan menyatakan kepemilikan nilai diserahkan penuh, sehingga fungsi bebas mengubah atau mengonsumsinya tanpa menyalin konten daftar. Konsekuensinya diuraikan pada bagian Kesalahan umum.

Kesalahan umum#

Kesalahan pertama adalah menugaskan nilai pada argumen tanpa kata kunci. Argumen bawaan hanyalah peminjaman yang tak dapat diubah, sehingga penugasan kepadanya ditolak compiler. Berkas ubah-baca.mojo memuat fungsi ubah yang mencoba menugaskan 99 pada argumen bawaannya.

mojo ubah-baca.mojo
def ubah(a: Int):
    a = 99


def main():
    var x = 5
    ubah(x)
    print(x)
bash
# penugasan pada argumen bawaan ditolak compiler
mojo run ubah-baca.mojo
text Keluaran
/home/kal/my-project/content/mojo/dua-jenis-fungsi/konvensi-argumen/ubah-baca.mojo:2:5: error: expression must be mutable in assignment
    a = 99
    ^
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to parse the provided Mojo source module

Pesan expression must be mutable in assignment menyatakan bahwa a bukan penyimpanan milik fungsi, melainkan peminjaman yang tak dapat diubah. Apabila fungsi memang perlu mengubah nilai pemanggil, deklarasikan argumennya dengan mut, dan apabila perubahan hanya perlu berlaku di dalam fungsi, terima nilainya dengan argumen var. Aturan ini menjadikan maksud setiap fungsi terbaca langsung dari signature-nya.

Kesalahan kedua adalah melanjutkan penggunaan variabel yang kepemilikannya sudah ditransfer. Berkas pakai-transfer.mojo mengirim nilai dengan ^ lalu mencetaknya sesudah pemanggilan.

mojo pakai-transfer.mojo
def konsumsi(var daftar: List[Int]):
    daftar.append(4)


def main():
    var nilai: List[Int] = [1, 2, 3]
    konsumsi(nilai^)
    print(nilai)
bash
# variabel setelah transfer tidak lagi terinisialisasi
mojo run pakai-transfer.mojo
text Keluaran
/home/kal/my-project/content/mojo/dua-jenis-fungsi/konvensi-argumen/pakai-transfer.mojo:8:10: error: use of uninitialized value 'nilai'
    print(nilai)
         ^
/home/kal/my-project/content/mojo/dua-jenis-fungsi/konvensi-argumen/pakai-transfer.mojo:6:9: note: 'nilai' declared here
    var nilai: List[Int] = [1, 2, 3]
        ^
/home/kal/.pixi/envs/mojo/bin/mojo: error: failed to run the pass manager

Pesan error use of uninitialized value 'nilai' menyatakan bahwa variabel tersebut tidak lagi terinisialisasi setelah transfer, dan compiler melacak asal deklarasinya pada pesan berikutnya. Setelah kepemilikan diserahkan, pemanggil tidak memiliki nilai apa pun untuk dibaca. Perbaikannya adalah tidak menyentuh variabel tersebut lagi, mengubah argumen menjadi mut apabila perubahan memang diinginkan, atau mengirim salinan eksplisit dengan nilai.copy() apabila nilai aslinya perlu dipertahankan.

Ringkasan#

  • Argumen tanpa kata kunci meminjam nilai secara tak dapat diubah, sehingga fungsi dapat membaca tetapi tidak dapat menulis dan tidak menyalinnya.
  • Argumen mut adalah referensi yang dapat diubah, perubahannya terlihat di luar fungsi, dan pemakaiannya menuntut eksklusivitas terhadap peminjaman lain pada nilai yang sama.
  • Argumen var memindahkan kepemilikan, berbentuk salinan pada tipe yang dapat disalin dan transfer ^ pada tipe seperti List.
  • Variabel yang kepemilikannya ditransfer tidak boleh dibaca lagi, sedangkan pemetaan kata kunci lama adalah borrowed ke bawaan, inout ke mut, dan owned ke var.

Referensi#