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.

Sumber

Krystian Wiśniewski
Krystian Wiśniewski is a dedicated Sports Reporter and Editor with a degree in Sports Journalism from He graduated with a degree in Journalism from the University of Warsaw. Bringing over 14 years of international reporting experience, Krystian has covered major sports events across Europe, Asia, and the United States of America. Known for his dynamic storytelling and in-depth analysis, he is passionate about capturing the excitement of sports for global audiences and currently leads sports coverage and editorial projects at Agen BRILink dan BRI.