
Halo semuanya. Baru-baru ini saya bertemu dengan bahasa pemrograman baru Rust. Saya perhatikan bahwa dia berbeda dari yang lain yang pernah saya temui sebelumnya. Karena itu, saya memutuskan untuk menggali lebih dalam. Saya ingin membagikan hasil dan kesan saya:
- Saya akan mulai dengan fitur utama, menurut saya, dari Rust
- Saya akan menjelaskan detail sintaks yang menarik
- Saya akan menjelaskan mengapa Rust tidak mungkin mengambil alih dunia
Saya akan segera menjelaskan bahwa saya telah menulis di Jawa selama sekitar sepuluh tahun, jadi saya akan berdebat dari menara lonceng saya.
Fitur pembunuh
Rust sedang mencoba untuk mengambil posisi antara antara bahasa tingkat rendah seperti C / C ++ dan Java / C # / Python / Ruby tingkat tinggi ... Semakin dekat bahasa ke perangkat keras, semakin banyak kontrol, semakin mudah untuk memprediksi bagaimana kode akan dieksekusi. Tetapi memiliki akses penuh ke memori jauh lebih mudah untuk menembak kaki Anda. Berbeda dengan C / C ++, Python / Java dan semua yang lainnya muncul. Mereka tidak perlu berpikir untuk membersihkan ingatan. Yang terburuk adalah NPE, kebocorannya tidak terlalu umum. Tetapi agar ini berhasil, Anda perlu, setidaknya, seorang pemulung, yang, pada gilirannya, mulai menjalani hidupnya sendiri, sejalan dengan kode pengguna, mengurangi kemungkinannya. Mesin virtual masih memberikan kemandirian platform, tetapi berapa banyak yang dibutuhkan adalah titik diperdebatkan, saya tidak akan meningkatkannya sekarang.
Karat adalah bahasa tingkat rendah, kompiler menghasilkan biner, yang tidak memerlukan trik tambahan untuk bekerja. Semua logika untuk menghapus objek yang tidak perlu diintegrasikan ke dalam kode pada saat kompilasi, mis. tidak ada pengumpul sampah saat runtime juga. Rust juga tidak memiliki referensi nol dan tipe yang aman, yang membuatnya bahkan lebih dapat diandalkan daripada Java.
Inti dari manajemen memori adalah gagasan memiliki referensi objek dan meminjam. Jika hanya satu variabel yang memiliki masing-masing objek, maka segera setelah berakhir pada akhir blok, semua yang ditunjukkannya dapat dihapus secara rekursif. Tautan juga dapat dipinjam untuk membaca atau menulis. Di sini prinsip satu penulis dan banyak pembaca bekerja.
Konsep ini dapat diperagakan dalam potongan kode berikut. Test () dipanggil dari metode main () , yang menciptakan struktur data rekursif MyStruct yang mengimplementasikan antarmuka destruktor. Drop memungkinkan Anda untuk mengatur logika untuk dieksekusi sebelum objek dihancurkan. Sesuatu yang mirip dengan finalizer di Jawa, hanya tidak seperti Java, saat pemanggilan metode drop () cukup pasti.
fn main() { test(); println!("End of main") } fn test() { let a = MyStruct { v: 1, s: Box::new( Some(MyStruct { v: 2, s: Box::new(None), }) ), }; println!("End of test") } struct MyStruct { v: i32, s: Box<Option<MyStruct>>, } impl Drop for MyStruct { fn drop(&mut self) { println!("Cleaning {}", self.v) } }
Kesimpulannya adalah sebagai berikut:
End of test Cleaning 1 Cleaning 2 End of main
Yaitu sebelum keluar dari tes (), memori secara rekursif dihapus. Kompilator menangani ini dengan memasukkan kode yang diperlukan. Apa itu Kotak dan Opsi akan uraikan nanti.
Dengan cara ini, Rust mengambil keamanan dari bahasa tingkat tinggi dan prediktabilitas dari bahasa pemrograman tingkat rendah.
Apa lagi yang menarik
Berikutnya, saya akan mencantumkan fitur-fitur bahasa dalam urutan pentingnya, menurut pendapat saya.
Ups
Di sini, Karat umumnya di depan sisanya. Jika sebagian besar bahasa sampai pada kesimpulan bahwa pewarisan berganda harus ditinggalkan, maka di Rust tidak ada warisan sama sekali. Yaitu suatu kelas hanya dapat mengimplementasikan antarmuka dalam jumlah berapa pun, tetapi tidak dapat mewarisi dari kelas lain. Dalam hal Jawa, ini berarti membuat semua kelas final. Secara umum, variasi sintaksis untuk mempertahankan OOP tidak begitu bagus. Mungkin ini yang terbaik.
Untuk menggabungkan data, ada struktur yang mungkin mengandung implementasi. Antarmuka disebut sifat dan mungkin juga berisi implementasi standar. Mereka tidak mencapai kelas abstrak, karena tidak dapat berisi bidang, banyak yang mengeluh tentang pembatasan ini. Sintaksnya adalah sebagai berikut, saya pikir komentar tidak diperlukan di sini:
fn main() { MyPrinter { value: 10 }.print(); } trait Printer { fn print(&self); } impl Printer { fn print(&self) { println!("hello!") } } struct MyPrinter { value: i32 } impl Printer for MyPrinter { fn print(&self) { println!("{}", self.value) } }
Dari fitur-fitur yang saya perhatikan, perlu diperhatikan hal-hal berikut:
- Kelas tidak memiliki konstruktor. Hanya ada inisialisasi yang menentukan nilai untuk bidang melalui kurung kurawal. Jika Anda memerlukan konstruktor, maka ini dilakukan melalui metode statis.
- Metode instance berbeda dari yang statis dengan memiliki & referensi diri sebagai argumen pertama.
- Kelas, antarmuka, dan metode juga dapat digeneralisasi. Tetapi tidak seperti Java, informasi ini tidak hilang pada saat kompilasi.
Beberapa keamanan lebih
Seperti yang saya katakan, Rust sangat memperhatikan keandalan kode dan mencoba mencegah sebagian besar kesalahan pada tahap kompilasi. Untuk ini, kemampuan untuk membuat tautan kosong dikecualikan. Itu mengingatkan saya pada jenis nullable dari Kotlin. Opsi digunakan untuk membuat tautan kosong. Sama seperti di Kotlin, ketika mencoba mengakses variabel seperti itu, kompiler akan memukul tangan, memaksa untuk memasukkan cek. Mencoba menarik nilai keluar tanpa memeriksa dapat menyebabkan kesalahan. Tetapi ini tentu saja tidak dapat dilakukan secara kebetulan, misalnya di Jawa.
Saya juga menyukai kenyataan bahwa semua variabel dan bidang kelas tidak dapat diubah secara default. Halo lagi Kotlin. Jika nilainya dapat berubah, ini harus ditunjukkan secara eksplisit dengan kata kunci mut . Saya pikir keinginan untuk tetap sangat meningkatkan keterbacaan dan prediktabilitas kode. Meskipun Opsi untuk beberapa alasan bisa berubah, saya tidak mengerti ini, berikut adalah kode dari dokumentasi:
let mut x = Some(2); let y = x.take(); assert_eq!(x, None); assert_eq!(y, Some(2));
Transfer
Karat disebut enum . Hanya di samping sejumlah nilai yang terbatas masih dapat berisi data dan metode sewenang-wenang. Jadi, itu adalah sesuatu antara enum dan kelas di Jawa. Opsi enum standar dalam contoh pertama saya hanya milik jenis ini:
pub enum Option<T> { None, Some(T), }
Ada konstruksi khusus untuk memproses nilai-nilai tersebut:
fn main() { let a = Some(1); match a { None => println!("empty"), Some(v) => println!("{}", v) } }
Juga
Saya tidak bermaksud untuk menulis buku teks tentang Rust, tetapi hanya ingin menekankan fitur-fiturnya. Pada bagian ini saya akan menjelaskan apa lagi yang berguna, tetapi, menurut saya, tidak begitu unik:
- Penggemar pemrograman fungsional tidak akan kecewa, ada lambda untuk mereka. Iterator memiliki metode untuk memproses koleksi, misalnya, filter dan for_each . Sesuatu seperti aliran Java.
- Konstruksi korek api juga dapat digunakan untuk hal-hal yang lebih kompleks daripada enum biasa, misalnya, untuk memproses pola.
- Ada sejumlah besar kelas bawaan, misalnya, koleksi: Vec, LinkedList, HashMap , dll.
- Anda dapat membuat makro
- Dimungkinkan untuk menambahkan metode ke kelas yang ada
- Inferensi tipe otomatis didukung
- Seiring dengan bahasa muncul kerangka pengujian standar
- Utilitas kargo bawaan digunakan untuk membangun dan mengelola dependensi
Terbang di salep
Bagian ini diperlukan untuk melengkapi gambar.
Masalah pembunuh
Kelemahan utama berasal dari fitur utama. Anda harus membayar semuanya. Dalam Rust, sangat tidak nyaman untuk bekerja dengan struktur data grafik yang bisa berubah, karena objek apa pun harus memiliki tidak lebih dari satu tautan. Untuk mengatasi keterbatasan ini, ada banyak kelas bawaan:
- Box - nilai abadi pada heap, analog pembungkus untuk primitif di Jawa
- Nilai variabel sel
- RefCell - nilai variabel yang dapat diakses dengan referensi
- Penghitung referensi - Rc , untuk banyak referensi ke satu objek
Dan ini adalah daftar yang tidak lengkap. Untuk sampel Rust pertama, saya dengan ceroboh memutuskan untuk menulis daftar yang terhubung sendiri dengan metode dasar. Pada akhirnya, tautan ke simpul menghasilkan Opsi <Rc <RefCell <ListNode> >> >> berikut :
- Opsi - untuk memproses tautan kosong
- Rc - untuk banyak tautan, seperti simpul terakhir dirujuk oleh simpul sebelumnya dan lembar itu sendiri
- RefCell - untuk tautan yang bisa diubah
- ListNode - elemen berikutnya itu sendiri
Ini terlihat begitu-begitu, total tiga pembungkus di sekitar satu objek. Kode untuk hanya menambahkan item ke akhir daftar sangat rumit, dan ada hal-hal yang tidak jelas di dalamnya, seperti kloning dan meminjam:
struct ListNode { val: i32, next: Node, } pub struct LinkedList { root: Node, last: Node, } type Node = Option<Rc<RefCell<ListNode>>>; impl LinkedList { pub fn add(mut self, val: i32) -> LinkedList { let n = Rc::new(RefCell::new(ListNode { val: val, next: None })); if (self.root.is_none()){ self.root = Some(n.clone()); } self.last.map(|v| { v.borrow_mut().next = Some(n.clone()) }); self.last = Some(n); self } ...
Di Kotlin, hal yang sama terlihat jauh lebih sederhana:
public fun add(value: Int) { val newNode = ListNode(null, value); root = root ?: newNode; last?.next = newNode last = newNode; }
Seperti yang saya ketahui kemudian, struktur seperti itu tidak tipikal untuk Rust, dan kode saya sepenuhnya non-idiomatis. Orang-orang bahkan menulis seluruh artikel:
Di sini Rust mengorbankan keterbacaan untuk keamanan. Selain itu, latihan seperti itu masih dapat menyebabkan tautan melingkar yang menggantung di memori, karena tidak ada pemulung yang akan membawanya pergi. Saya tidak menulis kode yang berfungsi di Rust, jadi sulit bagi saya untuk mengatakan betapa sulitnya menyulitkan kehidupan. Akan menarik untuk menerima komentar dari insinyur yang berlatih.
Kesulitan belajar
Proses panjang belajar Rust mengikuti sebagian besar dari bagian sebelumnya. Sebelum menulis apa pun, Anda harus meluangkan waktu untuk menguasai konsep kunci kepemilikan memori itu meresapi setiap baris. Sebagai contoh, daftar paling sederhana membawa saya beberapa malam, sedangkan di Kotlin hal yang sama ditulis dalam 10 menit, meskipun ini bukan bahasa kerja saya. Selain itu, banyak pendekatan akrab untuk menulis algoritma atau struktur data di Rust akan terlihat berbeda atau tidak akan berfungsi sama sekali. Yaitu ketika beralih ke hal itu, restrukturisasi pemikiran yang lebih dalam akan diperlukan, hanya menguasai sintaksis saja tidak akan cukup. Ini jauh dari JavaScript, yang menelan dan menanggung semuanya. Saya pikir Rust tidak akan pernah menjadi bahasa yang diajarkan anak-anak di sekolah pemrograman. Bahkan C / C ++ memiliki lebih banyak peluang dalam hal ini.
Pada akhirnya
Saya menemukan ide mengelola memori pada tahap kompilasi sangat menarik. Di C / C ++, saya tidak punya pengalaman, jadi saya tidak akan membandingkan dengan smart pointer. Sintaks umumnya menyenangkan dan tidak ada yang berlebihan. Saya mengkritik Rust karena kompleksitas penerapan struktur data grafik, tetapi saya menduga ini adalah fitur dari semua bahasa pemrograman non-GC. Mungkin perbandingan dengan Kotlin tidak sepenuhnya jujur.
Todo
Pada artikel ini, saya tidak menyentuh multithreading sama sekali, saya pikir ini adalah topik besar yang terpisah. Masih ada rencana untuk menulis beberapa jenis struktur data atau algoritma yang lebih rumit daripada daftar, jika Anda punya ide, silakan berbagi di komentar. Akan menarik untuk mengetahui jenis aplikasi apa yang umumnya ditulis dalam Rust.
Baca
Jika Anda tertarik dengan Rust, berikut adalah beberapa tautan:
UPD: Terima kasih atas komentar Anda. Saya belajar banyak hal berguna untuk diri saya sendiri. Ketidakakuratan dan kesalahan ketik yang diperbaiki, tautan tambahan. Saya pikir diskusi semacam itu sangat berkontribusi pada studi teknologi baru.