Rust, sebagai bahasa yang dikompilasi, menyulitkan untuk mengubah perilaku program secara dinamis. Pada artikel ini, kami menyematkan penerjemah Skema kecil bernama Skema Taruhan di Rust untuk mengubah perilaku program secara dinamis tanpa menghentikan prosesnya.
Anda dapat menemukan kode berikut di artikel ini di itu examples/hot-reload
direktori di repositori Skema Stak.
Daftar isi
Apa itu Skema?
Skema adalah bahasa pemrograman fungsional dan dialek Lisp. Ia dikenal dengan spesifikasi bahasanya yang sederhana dan dukungannya kelanjutan kelas satu sebagai fitur bahasa. Standar R7RS-kecil apakah spesifikasi terbarunya tersedia.
Apa itu Skema Taruhan?
Skema Taruhan adalah implementasi Skema yang kompatibel dengan standar R7RS-kecil. Awalnya dikembangkan sebagai fork Skema Ribbit. Skema Stak memiliki beberapa fitur berikut.
- Penerjemah Skema yang dapat disematkan dalam program Rust
- Jejak memori kecil
- Keamanan berbasis kemampuan
- Penerjemah Skema Stak tidak menyediakan API eksternal apa pun (misalnya terhadap sistem operasi) secara default.
- Untuk mengaktifkan API tersebut untuk I/O, sistem file, dll., Anda perlu mengaktifkannya pada inisialisasi mesin virtual penerjemah.
Menanamkan skrip Skema dalam program Rust
Dalam contoh ini, kita akan menulis program server HTTP di Rust dan menyematkan skrip Skema di dalamnya untuk mengubah perilaku server HTTP secara dinamis.
Menginisialisasi peti
Pertama, inisialisasi peti biner untuk membuat server HTTP dengan perintah berikut.
cargo init http-server
cd http-server
Menambahkan dependensi
Untuk menambahkan Stak Scheme sebagai perpustakaan ke peti Rust, jalankan perintah berikut di terminal Anda.
cargo add stak
cargo add --build stak-build
cargo install stak-compile
Itu stak
peti adalah perpustakaan yang menjalankan juru bahasa Skema dari Rust. Itu stak-build
peti adalah perpustakaan yang mengkompilasi skrip Skema build.rs
membangun skrip (disebutkan di bagian selanjutnya) sehingga Anda dapat menyematkannya di program Rust. Itu stak-compile
perintah adalah kompiler Skema-ke-bytecode untuk Skema Stak.
Mempersiapkan server HTTP
Selanjutnya mari kita siapkan server HTTP yang ditulis dalam Rust. Dalam contoh ini, kami menggunakan perpustakaan HTTP Tokio axum
. Pertama, tambahkan dependensi dengan perintah berikut.
cargo add --features rt-multi-thread tokio
cargo add axum
Kemudian, tambahkan kode berikut ke src/main.rs
.
use axum::{routing::post, serve, Router};
use core::error::Error;
#(tokio::main)
async fn main() -> Result<(), Box<dyn Error>> {
serve(
tokio::net::TcpListener::bind("0.0.0.0:3000").await?,
Router::new().route("/calculate", post("Hello, world!")),
)
.await?;
Ok(())
}
Kirim permintaan HTTP dengan curl
perintah untuk mengonfirmasi bahwa server berfungsi dengan benar.
cargo run &
curl -f -X POST # -> Hello, world!
kill %1
Menambahkan skrip build
Stak Scheme mengharapkan pengembang untuk menambahkan skrip Skema dengan .scm
ekstensi file di src
direktori. Alih-alih menyematkan file skrip ini secara langsung ke dalam program Rust, Stak Scheme mengkompilasi file-file ini ke dalam kode byte file terlebih dahulu. Untuk melakukannya, tambahkan kode berikut menggunakan stak-build
peti ke build.rs
mengajukan.
use stak_build::{build_r7rs, BuildError};
fn main() -> Result<(), BuildError> {
build_r7rs()
}
Ini akan mengkompilasi file Skema menjadi file bytecode yang disimpan di target
direktori setiap kali Anda menjalankan cargo build
memerintah.
Membuat penangan permintaan HTTP di Skema
Selanjutnya, tambahkan skrip Skema dari penangan permintaan HTTP di src
direktori. Tambahkan kode berikut ke src/handler.scm
mengajukan.
(import
(scheme base)
(scheme read)
(scheme write))
(write (apply + (read)))
read
adalah prosedur yang mem-parsing ekspresi S dari input standar. Dan, write
adalah prosedur yang menuliskan nilai ke output standar. Itu (apply + xs)
ekspresi menghitung jumlah angka dalam daftar xs
.
Selanjutnya untuk merujuk dan menjalankan skrip di atas dari Rust, tambahkan kode berikut ke src/main.rs
mengajukan.
// Other `use` statements...
use axum::{http::StatusCode, response};
use stak::{
device::ReadWriteDevice,
file::VoidFileSystem,
include_module,
module::{Module, UniversalModule},
process_context::VoidProcessContext,
r7rs::{SmallError, SmallPrimitiveSet},
time::VoidClock,
vm::Vm,
};
// The `main` function, etc...
// Heap size for the Scheme interpreter.
const HEAP_SIZE: usize = 1 << 16;
// Import the Scheme script.
// This macro embeds bytecodes of the script in a resulting Rust program.
static MODULE: UniversalModule = include_module!("handler.scm");
async fn calculate(input: String) -> response::Result<(StatusCode, String)> {
// Prepare buffers for in-memory stdout and stderr.
let mut output = vec!();
let mut error = vec!();
run_scheme(
&MODULE.bytecode(),
input.as_bytes(),
&mut output,
&mut error,
)
.map_err(|error| error.to_string())?;
let error = decode_buffer(error)?;
Ok(if error.is_empty() {
(StatusCode::OK, decode_buffer(output)?)
} else {
(StatusCode::BAD_REQUEST, error)
})
}
/// Run a Scheme program.
fn run_scheme(
bytecodes: &(u8),
input: &(u8),
output: &mut Vec<u8>,
error: &mut Vec<u8>,
) -> Result<(), SmallError> {
// Initialize heap memory for a Scheme virtual machine.
// In this case, it is allocated on a stack on the Rust side.
let mut heap = (Default::default(); HEAP_SIZE);
// Initialize a virtual machine of the Scheme interpreter.
let mut vm = Vm::new(
&mut heap,
// Initialize primitives compliant with the R7RS standard.
SmallPrimitiveSet::new(
ReadWriteDevice::new(input, output, error),
// Disable unused primitives, such as file systems, for security this time.
VoidFileSystem::new(),
VoidProcessContext::new(),
VoidClock::new(),
),
)?;
// Initialize a virtual machine with bytecodes.
vm.initialize(bytecodes.iter().copied())?;
// Run the bytecodes on the virtual machine.
vm.run()
}
/// Convert a buffer of standard output or standard error into a string.
fn decode_buffer(buffer: Vec<u8>) -> response::Result<String> {
Ok(String::from_utf8(buffer).map_err(|error| error.to_string())?)
}
Juga, ubah main
berfungsi sebagai berikut.
#(tokio::main)
async fn main() -> Result<(), Box> {
serve(
tokio::net::TcpListener::bind("0.0.0.0:3000").await?,
- Router::new().route("/calculate", post("Hello, world!")),
+ Router::new().route("/calculate", post(calculate)),
)
.await?;
Ok(())
}
Kirim permintaan HTTP untuk melihat cara kerjanya.
cargo run &
curl -f -X POST --data '(1 2 3 4 5)' # -> 15
kill %1
Anda dapat melihat bahwa program Rust mengeksekusi skrip Skema dan menghitung jumlah angka dalam daftar yang Anda masukkan dalam permintaan HTTP.
Pemuatan ulang modul panas
Bundel JavaScript (misalnya Webpack dan Vite) memiliki fitur yang disebut Pemuatan Ulang Modul Panas. Fungsionalitas ini secara dinamis mencerminkan konten file sumber yang dimodifikasi untuk program yang berjalan, seperti server HTTP.
Stak Scheme menyediakan fungsionalitas yang sama di dalamnya stak
Dan stak-build
perpustakaan. Dengan menggunakannya, Anda dapat mengubah perilaku program Rust secara dinamis. Pertama, aktifkan hot-reload
fitur untuk stak
peti di Cargo.toml
mengajukan.
(dependencies)
stak = { version = "0.4.4", features = ("hot-reload") }
Selanjutnya, restart server HTTP.
# Stop the server process if it is already running.
cargo run &
Gunakan curl
perintah untuk mengonfirmasi jumlah dihitung sekarang.
curl -f -X POST --data '(1 2 3 4 5)' # -> 15
Selanjutnya, ubah kode-kode yang ada di handler.scm
file untuk menghitung produk angka, bukan jumlah.
+ (write (apply + (read)))
- (write (apply * (read)))
Bangun kembali skrip Skema menggunakan cargo
memerintah tanpa me-restart server.
cargo build
Sekali lagi, periksa hasilnya dengan mengirimkan permintaan HTTP melalui curl
memerintah.
curl -f -X POST --data '(1 2 3 4 5)' # -> 120
Berbeda dengan sebelumnya, kita melihat bahwa hasil kali angka-angka dalam daftar dikembalikan!
Kesimpulan
- Kami telah mengintegrasikan Skema Stak dalam program Rust.
- Dengan menggunakan Stak Scheme, Anda dapat mengubah perilaku program Rust secara dinamis.
- Skemanya luar biasa!
Ucapan Terima Kasih
Saya ingin mengucapkan terima kasih khusus kepada yhara, satu warnadan komunitas pemroses bahasa pemrograman Zulip atas bantuannya.
Referensi
- Jika Anda tidak peduli dengan jejak memori, kepatuhan standar, dll., ada penerjemah Skema yang lebih kaya yang ditulis dalam Rust.
- Lua dan mruby sering digunakan untuk tujuan serupa.
- Meskipun tujuannya sedikit berbeda, Anda dapat mencapai sesuatu yang serupa dengan penerjemah WASM kecil dan kompiler WASM untuk bahasa tingkat tinggi yang sesuai, termasuk bahasa yang diketik secara statis. Namun, Anda harus menulis kode lem Anda sendiri.