Skrip pengguna asinkron di Rust murni tanpa kerangka kerja dan SMS

Halo, Habr!

Kadang-kadang, ketika mengembangkan layanan jaringan dan antarmuka pengguna, kita harus berurusan dengan skenario interaksi yang agak rumit yang berisi cabang dan loop. Skenario seperti itu tidak cocok dengan mesin keadaan sederhana - tidak cukup untuk menyimpan semua data dalam objek sesi, juga disarankan untuk melacak rute sistem untuk masuk ke satu keadaan atau lainnya, dan dalam beberapa kasus dapat kembali beberapa langkah, ulangi dialog dalam satu lingkaran, dan sebagainya. .d. Sebelumnya, untuk tujuan ini, Anda harus mengembangkan struktur data Anda sendiri yang meniru mesin stack, atau bahkan menggunakan bahasa skrip pihak ketiga. Dengan munculnya kemampuan asinkron di hampir semua bahasa pemrograman, menjadi mungkin untuk menulis skrip dalam bahasa yang sama di mana layanan ini ditulis. Script, dengan stack dan variabel lokalnya, sebenarnya adalah sesi pengguna, yaitu, ia menyimpan data dan rute. Misalnya, goroutine dengan pemblokiran pembacaan dari saluran dengan mudah menyelesaikan masalah ini, tetapi pertama, utas hijau tidak gratis , dan kedua, kami menulis di Rust, di mana tidak ada utas hijau, tetapi ada generator dan async / menunggu .

Misalnya, kami akan menulis http-bot sederhana yang menampilkan formulir html di browser, menanyakan pertanyaan pengguna sampai dia menjawab bahwa ia merasa baik. Program ini adalah http-server single-threaded paling sederhana, kami menulis skrip bot dalam bentuk generator Rust. Biarkan saya mengingatkan Anda bahwa generator JavaScript memungkinkan pertukaran data dua arah, yaitu di dalam generator Anda dapat memberikan pertanyaan: my_generator.next (my_question);
dan kembalikan respons darinya: menghasilkan my_response;
Di Rust, transfer nilai di dalam generator belum dilaksanakan (fungsi resume () tidak memiliki parameter, meskipun ada diskusi untuk memperbaikinya), jadi kami mengatur pertukaran data melalui sel bersama, di mana struktur dengan data yang diterima dan dikirim terletak. Skrip bot kami dibuat oleh fungsi create_scenario () , yang mengembalikan instance generator, pada dasarnya penutupan di mana parameter dipindahkan - pointer ke sel data udata . Untuk setiap sesi pengguna, kami menyimpan sel kami sendiri dengan data dan instance generator kami sendiri, dengan status stack sendiri dan nilai-nilai variabel lokal.

#[derive(Default, Clone)] struct UserData { sid: String, msg_in: String, msg_out: String, script: String, } type UserDataCell = Rc<RefCell<UserData>>; struct UserSession { udata: UserDataCell, scenario: Pin<Box<dyn Generator<Yield = (), Return = ()>>>, } type UserSessions = HashMap<String, UserSession>; fn create_scenario(udata: UserDataCell) -> impl Generator<Yield = (), Return = ()> { move || { let uname; let mut umood; udata.borrow_mut().msg_out = format!("Hi, what is you name ?"); yield (); uname = udata.borrow().msg_in.clone(); udata.borrow_mut().msg_out = format!("{}, how are you feeling ?", uname); yield (); 'not_ok: loop { umood = udata.borrow().msg_in.clone(); if umood.to_lowercase() == "ok" { break 'not_ok; } udata.borrow_mut().msg_out = format!("{}, think carefully, maybe you're ok ?", uname); yield (); umood = udata.borrow().msg_in.clone(); if umood.to_lowercase() == "ok" { break 'not_ok; } udata.borrow_mut().msg_out = format!("{}, millions of people are starving, maybe you're ok ?", uname); yield (); } udata.borrow_mut().msg_out = format!("{}, good bye !", uname); return (); } } 

Setiap langkah skrip terdiri dari tindakan sederhana - dapatkan tautan ke konten sel, simpan input pengguna dalam variabel lokal, atur teks respons, dan berikan kontrol ke luar, melalui hasil . Seperti dapat dilihat dari kode, generator kami mengembalikan tuple kosong (), dan semua data ditransmisikan melalui sel umum dengan penghitung referensi Ref <Cell <... >> . Di dalam generator, Anda perlu memastikan bahwa peminjaman konten sel peminjam () tidak melewati titik leleh , jika tidak, tidak mungkin memperbarui data dari luar generator - karena itu, sayangnya, Anda tidak dapat menulis satu kali di awal algoritme, biarkan udata_mut = udata.borrow_mut () , dan Anda harus meminjam nilai setelah setiap hasil.

Kami menerapkan loop acara kami sendiri (membaca dari soket), dan untuk setiap permintaan yang masuk, kami membuat sesi pengguna baru atau menemukan sesi yang ada di samping, memperbarui data di dalamnya:

 let mut udata: UserData = read_udata(&mut stream); let mut sid = udata.sid.clone(); let session; if sid == "" { //new session sid = rnd.gen::<u64>().to_string(); udata.sid = sid.clone(); let udata_cell = Rc::new(RefCell::new(udata)); sessions.insert( sid.clone(), UserSession { udata: udata_cell.clone(), scenario: Box::pin(create_scenario(udata_cell)), } ); session = sessions.get_mut(&sid).unwrap(); } else { match sessions.get_mut(&sid) { Some(s) => { session = s; session.udata.replace(udata); } None => { println!("unvalid sid: {}", &sid); continue; } } } 

Selanjutnya, kami mentransfer kontrol di dalam generator yang sesuai, dan kami memperbarui data yang diperbarui kembali ke soket. Pada langkah terakhir, ketika seluruh skrip selesai, kami menghapus sesi dari hashmap dan menyembunyikan kolom input dari halaman html menggunakan skrip js.

 udata = match session.scenario.as_mut().resume() { GeneratorState::Yielded(_) => session.udata.borrow().clone(), GeneratorState::Complete(_) => { let mut ud = sessions.remove(&sid).unwrap().udata.borrow().clone(); ud.script = format!("document.getElementById('form').style.display = 'none'"); ud } }; write_udata(&udata, &mut stream); 

Kode kerja lengkap di sini:
github.com/epishman/habr_samples/blob/master/chatbot/main.rs

Saya meminta maaf untuk parsing http "pertanian kolektif", yang bahkan tidak mendukung input Cyrillic, tetapi semuanya dilakukan menggunakan alat bahasa standar, tanpa kerangka kerja, perpustakaan, dan sms. Saya tidak terlalu suka string kloning, dan skrip itu sendiri tidak terlihat cukup kompak karena penggunaan yang banyak dariinjam_mut () dan klon () . Rastaman yang mungkin berpengalaman akan dapat menyederhanakan ini (misalnya, menggunakan makro). Masalah utamanya adalah bahwa masalahnya diselesaikan dengan cara minimal, dan saya berharap bahwa segera kita akan menerima set lengkap alat asinkron dalam rilis stabil.

PS
Untuk mengkompilasi, Anda memerlukan bangunan malam:
 rustup default nightly rustup update 

Kawan-kawan dari English Stack Overflow membantu saya menangani generator:
stackoverflow.com/questions/56460206/how-can-i-transfer-some-values-into-a-rust-generator-at-each-step

Source: https://habr.com/ru/post/id455010/


All Articles