Binding let dan var

Binding immutable secara bawaan dengan let dan mutasi sebagai opsi dengan var.

Daftar isi

Topik ini membahas dua kata kunci yang mengikat nilai ke sebuah nama di dalam fungsi Carbon, yaitu let dan var. Pembahasan mencakup semantik keduanya menurut dokumen desain resmi, perilaku nyata pada interpreter Carbon Explorer, inferensi tipe dengan auto, serta error yang muncul ketika aturan mutasi dilanggar. Setelah topik ini, program yang menggunakan binding secara tepat dapat ditulis dan dijalankan di Compiler Explorer.

let untuk nilai yang tidak berubah#

Kata kunci let mendeklarasikan binding, yakni nama yang terikat pada sebuah nilai, dan nilai tersebut tidak dapat diubah setelah dideklarasikan. Dokumen desain menyebut bentuk ini value binding, yaitu binding yang merujuk nilai secara langsung. Nama yang terikat dapat digunakan sebagai ekspresi, tetapi nilai di baliknya tidak dapat ditimpa dan alamatnya tidak dapat diambil. Ketidakberubahan ini adalah perilaku bawaan Carbon, bukan tambahan opsional, sehingga setiap binding tanpa var dapat dipercaya tetap stabil. Nilai awalnya wajib ditulis pada deklarasi, karena binding tanpa nilai tidak memiliki arti apa pun.

Pilihan ini berbeda dari tradisi C++ yang menjadikan variabel dapat diubah sebagai bawaan dan meminta kata kunci const untuk penguncian. Carbon membalik konvensi tersebut. Deklarasi let a: i32 = 5; mengikat nama a ke nilai 5 bertipe i32 secara permanen, sedangkan kebutuhan penyimpanan yang dapat ditulis ulang dinyatakan secara eksplisit dengan var. Dengan demikian, pembaca kode dapat mengetahui sifat sebuah nama hanya dari kata kunci yang mendahuluinya.

var untuk penyimpanan yang dapat diubah#

Kata kunci var mendeklarasikan binding yang menyediakan objek dengan penyimpanan tersendiri. Nama yang dibuat oleh var merujuk penyimpanan tersebut, sehingga nilainya dapat ditimpa melalui penugasan. Program berikut menggunakan let dan var sekaligus, kemudian mengubah nilai variabelnya dua kali.

carbon binding-let-var.carbon
package sample api;

fn Main() -> i32 {
  let a: i32 = 5;
  var b: i32 = 10;
  b = b + a;
  Print("a = {0}", a);
  Print("b = {0}", b);
  var c: auto = 42;
  c = c + 8;
  Print("c = {0}", c);
  return 0;
}
text Keluaran
a = 5
b = 15
c = 50
result: 0

Baris let a: i32 = 5; mengikat a ke nilai 5, sedangkan var b: i32 = 10; membuat penyimpanan bertipe i32 berisi 10. Penugasan b = b + a; membaca nilai lama b, menjumlahkannya dengan a, lalu menimpa isi penyimpanan b menjadi 15. Program mencetak a dan b setelah penugasan, sehingga nilai 15 pada keluaran memperlihatkan efek penugasannya. Penulisan tipe i32 menandakan bilangan bulat bertanda 32 bit, dan penjelasan lengkapnya berada pada topik tipe skalar bawaan.

Keluaran program pada Compiler Explorer mengikuti satu konvensi. Fungsi Print tidak menyisipkan baris baru di akhir teks, tetapi Compiler Explorer menampilkan setiap panggilan Print sebagai satu baris keluaran tersendiri. Panel keluaran juga selalu menambahkan baris result: N di akhir, dengan N berupa kode kembalian fungsi Main. Nilai result: 0 pada contoh di atas berarti program berakhir normal.

Inferensi tipe dengan auto#

Kata kunci auto dapat menggantikan penulisan tipe pada sebuah binding, dan tipenya disimpulkan dari nilai inisialisasinya. Deklarasi var c: auto = 42; pada program di atas menghasilkan binding bertipe i32 tanpa menuliskan i32 secara eksplisit, karena 42 adalah bilangan bulat. Bentuk yang sama sah untuk let, misalnya let x: auto = 7;, yang mengikat x ke nilai 7 dengan tipe yang disimpulkan. Dokumen desain membatasi auto pada beberapa konteks saja, yaitu tipe sebuah binding dan tipe kembalian fungsi, sehingga auto bukan pengganti nama tipe di semua tempat.

Inferensi tipe (type inference) semacam ini berguna saat tipe sebuah nilai panjang atau sulit ditulis, misalnya tipe kembalian dari ekspresi yang rumit. Penulisan var rata: auto = HitungRataRata(nilai); tetap memberikan tipe yang statis dan diperiksa penuh, tanpa meminta penulisan nama tipenya. Perlu dicatat bahwa auto hanya mengubah sumber tipe, bukan sifat bindingnya. Binding dengan let dan auto tetap tidak dapat diubah, sedangkan binding dengan var dan auto tetap dapat ditimpa.

Kesalahan umum#

Kesalahan yang paling sering terjadi pada pembahasan ini adalah menimpa nilai sebuah binding let. Program berikut sengaja melakukan penugasan pada binding yang tidak dapat diubah agar pesan errornya dapat dikenali.

carbon ubah-let.carbon
package sample api;

fn Main() -> i32 {
  let a: i32 = 5;
  a = 7;
  Print("a = {0}", a);
  return 0;
}
text Keluaran
COMPILATION ERROR: <source>:5: Only a reference expression can be assigned to, but got `a`

Pesan error tersebut muncul pada tahap pemeriksaan, sebelum program dijalankan. Penyebabnya adalah a dideklarasikan dengan let, sehingga a adalah nama untuk sebuah nilai, bukan untuk penyimpanan yang dapat ditimpa. Ungkapan reference expression pada pesan tersebut merujuk nama yang dibuat oleh var, yaitu satu-satunya binding yang boleh menjadi sasaran penugasan. Perbaikannya bergantung pada maksud kode. Apabila nilai memang perlu berubah, ubah deklarasi menjadi var a: i32 = 5;. Apabila nilai seharusnya tetap, hapus penugasannya dan gunakan nama lain untuk nilai baru tersebut.

Penanda lokasi pada pesan perlu dibaca dengan tepat. Angka setelah <source>:, yaitu 5 pada contoh di atas, menunjuk baris penugasan yang ditolak dalam berkas sumber, sehingga pencarian penyebabnya dapat dimulai dari baris tersebut. Pesan yang sama muncul untuk penugasan apa pun terhadap binding let, tidak bergantung pada tipenya, dan tidak ada baris keluaran program yang tercetak karena eksekusi tidak pernah dimulai. Pemeriksaan yang berjalan sejak sedini ini merupakan keuntungan bawaan, karena pelanggaran ketidakberubahan tidak mungkin lolos tanpa ditemukan.

Ringkasan#

  • let a: i32 = 5; mengikat nama ke nilai yang tidak dapat diubah, sedangkan var b: i32 = 10; menyediakan penyimpanan yang dapat ditimpa melalui penugasan.
  • Penugasan hanya sah pada binding var, dan penugasan pada binding let ditolak dengan error Only a reference expression can be assigned to.
  • auto menggantikan penulisan tipe binding dan menyimpulkan tipenya dari nilai inisialisasi, baik pada let maupun var.
  • Compiler Explorer menampilkan setiap panggilan Print sebagai satu baris keluaran dan menambah baris result: N berisi kode kembalian Main.

Referensi#