Rumah >Peranti teknologi >industri IT >Cara menggunakan pembolehubah global secara idiomatically dalam karat
Dalam artikel ini, saya akan membincangkan perangkap yang dikompilasi karat ingin menyelamatkan kita dari. Kemudian saya akan menunjukkan kepada anda penyelesaian terbaik untuk senario yang berbeza.
Takeaways Key
anda boleh melompat ke bahagian tertentu artikel ini melalui pautan berikut:
No Globals: Refactor ke Arc / Rc
cubalah untuk diri sendiri di taman permainan!
Ini adalah sintaks tidak sah untuk karat. Kata kunci biarkan tidak boleh digunakan dalam skop global. Kita hanya boleh menggunakan statik atau const. Yang terakhir mengisytiharkan pemalar yang benar, bukan pemboleh ubah. Hanya statik memberi kita pemboleh ubah global.
alasan di sebalik ini adalah yang membolehkan peruntukan pembolehubah pada timbunan, semasa runtime. Perhatikan bahawa ini tetap berlaku apabila memperuntukkan timbunan, seperti dalam let t = box :: new ();. Dalam kod mesin yang dihasilkan, masih terdapat penunjuk ke dalam timbunan yang disimpan di timbunan.
Pembolehubah global disimpan dalam segmen data program. Mereka mempunyai alamat tetap yang tidak berubah semasa pelaksanaan. Oleh itu, segmen kod boleh termasuk alamat tetap dan tidak memerlukan ruang pada timbunan sama sekali.
Baiklah, jadi kita dapat memahami mengapa kita memerlukan sintaks yang berbeza. Karat, sebagai bahasa pengaturcaraan sistem moden, ingin menjadi sangat jelas mengenai pengurusan ingatan.
mari kita cuba lagi dengan statik:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
pengkompil tidak gembira, namun:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
HM, jadi nilai permulaan pembolehubah statik tidak dapat dikira pada masa runtime. Maka mungkin biarkan ia tidak dikenali?
error<span>[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants </span> <span>--> src/main.rs:3:24 </span> <span>| </span><span>3 | static start: String = Utc::now().to_string(); </span> <span>| ^^^^^^^^^^^^^^^^^^^^^^ </span>
ini menghasilkan ralat baru:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Jadi itu tidak berfungsi sama ada! Semua nilai statik mesti dimulakan sepenuhnya dan sah sebelum mana -mana kod pengguna dijalankan.
Jika anda datang ke karat dari bahasa lain, seperti JavaScript atau Python, ini mungkin kelihatan tidak perlu. Tetapi mana -mana guru C boleh menceritakan kisah tentang kegagalan perintah permulaan statik, yang boleh membawa kepada perintah permulaan yang tidak ditentukan jika kita tidak berhati -hati.
Contohnya, bayangkan sesuatu seperti ini:
<span>Compiling playground v0.0.1 (/playground) </span>error<span>: free static item without body </span> <span>--> src/main.rs:21:1 </span> <span>| </span><span>3 | static START_TIME; </span> <span>| ^^^^^^^^^^^^^^^^^- </span> <span>| | </span> <span>| help: provide a definition for the static: `= <expr>;` </span>Dalam coretan kod ini, tidak ada perintah permulaan yang selamat, disebabkan oleh kebergantungan bulat.
Jika ia adalah C, yang tidak peduli dengan keselamatan, hasilnya ialah: 1 b: 1 c: 2. dalam setiap unit kompilasi.
Sekurang -kurangnya ia ditakrifkan apa hasilnya. Walau bagaimanapun, "Fiasco" bermula apabila pembolehubah statik adalah dari fail .cpp yang berbeza, dan oleh itu unit kompilasi yang berbeza. Kemudian pesanan tidak ditentukan dan biasanya bergantung pada susunan fail dalam baris perintah kompilasi.
Dalam karat, sifar-initializing bukanlah sesuatu. Lagipun, sifar adalah nilai yang tidak sah untuk pelbagai jenis, seperti kotak. Selain itu, dalam karat, kami tidak menerima isu pesanan pelik. Selagi kita menjauhkan diri dari yang tidak selamat, pengkompil hanya boleh membenarkan kita menulis kod yang waras. Dan itulah sebabnya pengkompil menghalang kita daripada menggunakan inisialisasi runtime yang mudah.
Tetapi bolehkah saya mengelakkan permulaan dengan menggunakan tiada, bersamaan dengan penunjuk null? Sekurang -kurangnya ini semua sesuai dengan sistem jenis karat. Tentunya saya hanya dapat menggerakkan permulaan ke bahagian atas fungsi utama, kan?
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
ah, baik, ralat yang kita dapat ialah ...
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Pada ketika ini, saya boleh membungkusnya dalam blok {...} yang tidak selamat dan ia akan berfungsi. Kadang -kadang, ini adalah strategi yang sah. Mungkin untuk menguji jika baki kod berfungsi seperti yang diharapkan. Tetapi ia bukan penyelesaian idiomatik yang saya ingin tunjukkan kepada anda. Oleh itu, mari kita meneroka penyelesaian yang dijamin selamat oleh pengkompil.
Anda mungkin sudah menyedari bahawa contoh ini tidak memerlukan pembolehubah global sama sekali. Dan lebih kerap daripada tidak, jika kita dapat memikirkan penyelesaian tanpa pembolehubah global, kita harus mengelakkannya.
Idea di sini adalah untuk meletakkan perisytiharan di dalam fungsi utama:
error<span>[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants </span> <span>--> src/main.rs:3:24 </span> <span>| </span><span>3 | static start: String = Utc::now().to_string(); </span> <span>| ^^^^^^^^^^^^^^^^^^^^^^ </span>
Satu-satunya masalah ialah Peminjam-Checker:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Kesalahan ini tidak betul -betul jelas. Pengkompil memberitahu kita bahawa benang yang dilahirkan boleh hidup lebih lama daripada nilai start_time, yang tinggal di bingkai timbunan fungsi utama.
Secara teknikal, kita dapat melihat bahawa ini adalah mustahil. Benang disatukan, oleh itu benang utama tidak akan keluar sebelum benang kanak -kanak selesai.
Tetapi pengkompil tidak cukup pintar untuk mengetahui kes ini. Secara umum, apabila benang baru dilahirkan, penutupan yang disediakan hanya boleh meminjam item dengan hayat statik. Dalam erti kata lain, nilai yang dipinjam mesti hidup untuk jangka hayat program penuh.
Bagi sesiapa sahaja yang belajar tentang karat, ini boleh menjadi titik di mana anda ingin menjangkau pembolehubah global. Tetapi terdapat sekurang -kurangnya dua penyelesaian yang lebih mudah daripada itu. Yang paling mudah adalah untuk mengklonkan nilai rentetan dan kemudian menggerakkan pemilikan rentetan ke dalam penutupan. Sudah tentu, itu memerlukan peruntukan tambahan dan memori tambahan. Tetapi dalam kes ini, ia hanya rentetan pendek dan tiada prestasi kritikal.
Tetapi bagaimana jika ia adalah objek yang lebih besar untuk dikongsi? Sekiranya anda tidak mahu mengklonkannya, bungkusnya di belakang penunjuk pintar yang dikira rujukan. RC adalah jenis yang dikira rujukan tunggal. ARC adalah versi atom yang selamat berkongsi nilai antara benang.
Jadi, untuk memuaskan pengkompil, kita boleh menggunakan arka seperti berikut:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
cubalah untuk diri sendiri di taman permainan!
Ini telah menjadi rundown cepat mengenai cara berkongsi keadaan di antara benang sambil mengelakkan pembolehubah global. Di luar apa yang saya tunjukkan kepada anda setakat ini, anda mungkin juga memerlukan mutabiliti dalaman untuk mengubah keadaan bersama. Liputan penuh mutabiliti dalaman adalah di luar skop artikel ini. Tetapi dalam contoh ini, saya akan memilih arka
Dalam pengalaman saya, kes penggunaan yang paling biasa untuk keadaan global bukan pembolehubah tetapi pemalar. Dalam karat, mereka datang dalam dua perisa:
Kedua-duanya boleh dimulakan dengan pemalar masa kompilasi. Ini boleh menjadi nilai mudah, seperti 42 atau "Hello World". Atau ia boleh menjadi ungkapan yang melibatkan beberapa pemalar dan fungsi kompilasi masa yang ditandakan sebagai Const. Selagi kita mengelakkan kebergantungan bulat. (Anda boleh mendapatkan lebih banyak maklumat mengenai ekspresi berterusan dalam rujukan karat.)
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Biasanya, const adalah pilihan yang lebih baik - melainkan jika anda memerlukan mutabiliti dalaman, atau anda secara khusus ingin mengelakkan inlining.
Sekiranya anda memerlukan mutabiliti dalaman, terdapat beberapa pilihan. Bagi kebanyakan primitif, terdapat varian atom yang sama yang terdapat dalam STD :: Sync :: Atomic. Mereka menyediakan API yang bersih untuk memuat, menyimpan, dan mengemas kini nilai secara atom.
Dalam ketiadaan atom, pilihan biasa adalah kunci. Perpustakaan standard Rust menawarkan kunci baca-tulis (RWLOCK) dan Kunci Pengecualian Bersama (MUTEX).
Walau bagaimanapun, jika anda perlu mengira nilai pada runtime, atau memerlukan peruntukan timbunan, maka const dan statik tidak membantu.
Kebanyakan aplikasi yang saya tulis hanya mempunyai satu benang. Dalam hal ini, mekanisme penguncian tidak diperlukan.
Sebagai contoh, meminjam tidak selamat dari pembolehubah global dapat memberi kita banyak rujukan yang boleh dimainkan secara serentak. Kemudian kita boleh menggunakan salah satu daripada mereka untuk melangkah ke atas vektor dan satu lagi untuk mengeluarkan nilai dari vektor yang sama. Iterator kemudiannya boleh melampaui sempadan memori yang sah, kemalangan yang berpotensi yang karat selamat akan dicegah.
tetapi perpustakaan standard mempunyai cara untuk "global" menyimpan nilai untuk akses selamat dalam satu thread. Saya bercakap mengenai penduduk tempatan. Di hadapan banyak benang, setiap benang mendapat salinan bebas pembolehubah. Tetapi dalam kes kita, dengan satu benang, hanya ada satu salinan.
penduduk tempatan dicipta dengan thread_local! Makro. Mengaksesnya memerlukan penggunaan penutupan, seperti yang ditunjukkan dalam contoh berikut:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>Bukan yang paling mudah dari semua penyelesaian. Tetapi ia membolehkan kita melakukan kod inisialisasi sewenang -wenangnya, yang akan berjalan tepat pada waktunya apabila akses pertama ke nilai berlaku.
thread-thread adalah sangat baik ketika datang ke mutabilitas dalaman. Tidak seperti semua penyelesaian lain, ia tidak memerlukan penyegerakan. Ini membolehkan menggunakan refcell untuk mutabiliti dalaman, yang mengelakkan pengunci overhead mutex.
Prestasi mutlak thread-thread sangat bergantung pada platform. Tetapi saya melakukan beberapa ujian cepat pada PC saya sendiri yang membandingkannya dengan mutabiliti dalaman yang bergantung kepada kunci dan mendapati ia menjadi 10x lebih cepat. Saya tidak mengharapkan hasilnya dibalikkan pada mana -mana platform, tetapi pastikan untuk menjalankan tanda aras anda sendiri jika anda sangat peduli dengan prestasi.
Berikut adalah contoh cara menggunakan refcell untuk mutabiliti dalaman:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>cubalah untuk diri sendiri di taman permainan!
Sebagai nota sampingan, walaupun benang dalam webassembly berbeza dari benang pada platform x86_64, corak ini dengan thread_local! Refcell juga terpakai apabila menyusun karat untuk berjalan di penyemak imbas. Menggunakan pendekatan yang selamat untuk kod multi-threaded akan berlebihan dalam kes itu. .
Satu kaveat mengenai thread-thread adalah pelaksanaannya bergantung pada platform. Biasanya, ini adalah apa-apa yang anda perhatikan, tetapi sedar bahawa drop-semantik adalah platform yang bergantung kepada itu.Semua yang dikatakan, penyelesaian untuk global multi-threaded jelas juga berfungsi untuk kes-kes yang dibaca tunggal. Dan tanpa mutabilitas dalaman, mereka seolah-olah sekejap-sekejap-negara.
jadi mari kita lihat yang seterusnya.
pembolehubah global multi-thread dengan inisialisasi runtime
Contoh dalam dokumentasi rasmi adalah titik permulaan yang baik. Sekiranya anda juga memerlukan mutabiliti dalaman, anda perlu menggabungkan pendekatan itu dengan kunci baca-tulis atau mutex. Begini cara yang mungkin kelihatan:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>cubalah untuk diri sendiri di taman permainan!
Jika anda mencari sesuatu yang lebih mudah, saya boleh mengesyorkan salah satu daripada dua peti, yang akan saya bincangkan di bahagian seterusnya.
Berdasarkan populariti dan rasa peribadi, saya ingin mengesyorkan dua perpustakaan yang saya fikir adalah pilihan terbaik untuk pembolehubah global yang mudah dalam karat, sehingga 2021.
Sekali sel sedang dipertimbangkan untuk perpustakaan standard. .
Berikut adalah contoh menggunakan once_cell pada pengkompil yang stabil, dengan ketergantungan tambahan:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>cubalah untuk diri sendiri di taman permainan!
Atas ialah kandungan terperinci Cara menggunakan pembolehubah global secara idiomatically dalam karat. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!