Referensi dan Cuplikan

Permasalahan pada kode tuple dalam Listing 4-5 adalah kita harus mengembalikan Array ke fungsi pemanggil sehingga kita masih bisa menggunakan Array setelah pemanggilan ke calculate_length, karena Array telah dipindahkan ke dalam calculate_length.

Cuplikan

Pada bab sebelumnya, kita membahas bagaimana sistem kepemilikan Cairo mencegah kita menggunakan variabel setelah kita telah memindahkannya, melindungi kita dari potensi menulis dua kali ke sel memori yang sama. Namun, ini tidak begitu nyaman. Mari kita lihat bagaimana kita dapat mempertahankan kepemilikan variabel dalam fungsi pemanggil dengan menggunakan snapshot.

Di Cairo, snapshot adalah tampilan tidak berubah dari suatu nilai pada titik waktu tertentu. Ingatlah bahwa memori tidak berubah, sehingga memodifikasi nilai sebenarnya membuat sel memori baru. Sel memori lama masih ada, dan snapshot adalah variabel yang merujuk pada nilai "lama" tersebut. Dalam hal ini, snapshot adalah tampilan "ke masa lalu".

Berikut adalah bagaimana Anda akan mendefinisikan dan menggunakan fungsi calculate_length yang mengambil snapshot ke array sebagai parameter daripada mengambil kepemilikan dari nilai yang mendasarinya. Pada contoh ini, fungsi calculate_length mengembalikan panjang array yang dilewatkan sebagai parameter. Karena kita melewatinya sebagai snapshot, yang merupakan tampilan tidak berubah dari array, kita dapat yakin bahwa fungsi calculate_length tidak akan mengubah array, dan kepemilikan array tetap ada di dalam fungsi utama.

Nama file: src/lib.cairo

{{#include ../listings/ch04-understanding-ownership/no_listing_09_snapshots/src/lib.cairo}}

Catatan: Hanya mungkin untuk memanggil metode len() pada snapshot array karena itu didefinisikan seperti itu dalam trait ArrayTrait. Jika Anda mencoba memanggil metode yang tidak didefinisikan untuk snapshot pada snapshot, Anda akan mendapatkan kesalahan kompilasi. Namun, Anda dapat memanggil metode yang mengharapkan snapshot pada tipe non-snapshot.

Keluaran dari program ini adalah:

[DEBUG]	                               	(raw: 0)

[DEBUG]	                              	(raw: 1)

Pengembalian berhasil, mengembalikan []

Pertama, perhatikan bahwa semua kode tuple dalam deklarasi variabel dan nilai kembalian fungsi telah hilang. Kedua, perhatikan bahwa kita melewatkan @arr1 ke calculate_length dan, dalam definisinya, kita menggunakan @Array<u128> daripada Array<u128>.

Mari kita perhatikan lebih dekat panggilan fungsi di sini:

{{#rustdoc_include ../listings/ch04-understanding-ownership/no_listing_09_snapshots/src/lib.cairo:function_call}}

Sintaks @arr1 memungkinkan kita membuat snapshot dari nilai dalam arr1. Karena snapshot adalah tampilan tidak berubah dari nilai pada titik waktu tertentu, aturan biasa dari sistem tipe linear tidak diberlakukan. Khususnya, variabel snapshot selalu Drop, tidak pernah Destruct, bahkan snapshot kamus.

Sama halnya, tanda fungsi menggunakan @ untuk menunjukkan bahwa tipe dari parameter arr adalah snapshot. Mari tambahkan beberapa anotasi penjelas:

fn calculate_length(
    array_snapshot: @Array<u128>
) -> usize { // array_snapshot adalah snapshot dari Array
    array_snapshot.len()
} // Di sini, array_snapshot keluar dari cakupan dan di-drop.
// Namun, karena ini hanya merupakan tampilan dari apa yang asli array `arr` berisi, `arr` asli masih bisa digunakan.

Cakupan di mana variabel array_snapshot valid adalah sama dengan cakupan parameter fungsi apa pun, tetapi nilai mendasar dari snapshot tidak di-drop saat array_snapshot berhenti digunakan. Ketika fungsi memiliki snapshot sebagai parameter alih-alih nilai sebenarnya, kita tidak perlu mengembalikan nilai tersebut untuk memberikan kepemilikan kembali dari nilai asli, karena kita tidak pernah memiliki kepemilikannya.

Operator Desnap

Untuk mengonversi snapshot kembali ke variabel reguler, Anda dapat menggunakan operator desnap *, yang berfungsi sebagai kebalikan dari operator @.

Hanya tipe Copy yang dapat didesnap. Namun, dalam kasus umum, karena nilai tidak dimodifikasi, variabel baru yang dibuat oleh operator desnap menggunakan kembali nilai lama, sehingga proses desnapping adalah operasi yang sepenuhnya gratis, sama seperti Copy.

Pada contoh berikut, kita ingin menghitung luas persegi panjang, tetapi kita tidak ingin mengambil kepemilikan persegi panjang dalam fungsi calculate_area, karena kita mungkin ingin menggunakan persegi panjang tersebut lagi setelah pemanggilan fungsi. Karena fungsi kita tidak mengubah instance persegi panjang, kita dapat melewatkan snapshot dari persegi panjang ke fungsi, dan kemudian mengubah snapshot kembali menjadi nilai menggunakan operator desnap *.

{{#include ../listings/ch04-understanding-ownership/no_listing_10_desnap/src/lib.cairo}}

Namun, apa yang terjadi jika kita mencoba memodifikasi sesuatu yang kita lewati sebagai snapshot? Cobalah kode pada Listing 4-6. SPOILER: Ini tidak berhasil!

Nama file: src/lib.cairo

{{#include ../listings/ch04-understanding-ownership/listing_03_06/src/lib.cairo}}

Listing 4-6: Mencoba memodifikasi nilai snapshot

Berikut adalah kesalahannya:

error: Invalid left-hand side of assignment.
 --> ownership.cairo:15:5
    rec.height = rec.width;
    ^********^

Kompilator mencegah kita untuk memodifikasi nilai yang terkait dengan snapshot.

Referensi Mutable

Kita bisa mencapai perilaku yang kita inginkan pada Listing 4-6 dengan menggunakan referensi mutable alih-alih snapshot. Referensi mutable sebenarnya adalah nilai yang dapat diubah yang dilewatkan ke fungsi yang secara implisit dikembalikan pada akhir fungsi, mengembalikan kepemilikan ke konteks pemanggil. Dengan cara ini, mereka memungkinkan Anda untuk mengubah nilai yang dilewatkan sambil tetap memegang kepemilikannya dengan mengembalikannya secara otomatis pada akhir eksekusi. Di Cairo, sebuah parameter dapat dilewati sebagai referensi mutable menggunakan modifier ref.

Catatan: Di Cairo, sebuah parameter hanya dapat dilewati sebagai referensi mutable menggunakan modifier ref jika variabel dideklarasikan sebagai mutable dengan mut.

Pada Listing 4-7, kita menggunakan referensi mutable untuk mengubah nilai bidang height dan width dari instan Rectangle dalam fungsi flip.

{{#include ../listings/ch04-understanding-ownership/listing_03_07/src/lib.cairo}}

Listing 4-7: Penggunaan referensi mutable untuk mengubah nilai

Pertama, kita ubah rec menjadi mut. Kemudian kita lewatkan referensi mutable dari rec ke flip dengan ref rec, dan perbarui tanda tangan fungsi untuk menerima referensi mutable dengan ref rec: Rectangle. Ini membuatnya sangat jelas bahwa fungsi flip akan mengubah nilai dari instan Rectangle yang dilewatkan sebagai parameter.

Keluaran dari program ini adalah:

[DEBUG]
                                (raw: 10)

[DEBUG]	                        (raw: 3)

Seperti yang diharapkan, bidang height dan width dari variabel rec telah ditukar.

Ringkasan Kecil

Mari kita ringkas apa yang telah kita diskusikan tentang sistem tipe linear, kepemilikan, snapshot, dan referensi:

  • Pada suatu waktu tertentu, sebuah variabel hanya dapat dimiliki oleh satu pemilik.
  • Anda dapat melewati sebuah variabel berdasarkan nilai, snapshot, atau referensi ke fungsi.
  • Jika Anda melewatinya berdasarkan nilai, kepemilikan variabel dipindahkan ke fungsi.
  • Jika Anda ingin tetap memegang kepemilikan variabel dan tahu bahwa fungsi Anda tidak akan memutasi variabel tersebut, Anda dapat melewatinya sebagai snapshot dengan @.
  • Jika Anda ingin tetap memegang kepemilikan variabel dan tahu bahwa fungsi Anda akan memutasi variabel tersebut, Anda dapat melewatinya sebagai referensi mutable dengan ref.