RustとStreamlitを組み合わせてお手軽にWebアプリを作る

こんにちは。misty1999です。普段はRustとTypescript/Vueを用いて開発をしていますが、Streamlitを用いてお手軽にフロントエンドを作ることができるのは魅力的です。一方パフォーマンスを考える場合や堅牢な型システムを用いた開発を行いたい場合など、バックエンドにPython以外を使いたいこともあると思います。そこで、Streamlitでフロントエンドだけでも代替できないか？と思って試してみたところ思った以上にお手軽だったので紹介します。

```python:frontend/app.py
import streamlit as st
import requests

st.title("Streamlit + Rust Backend Demo")

input_value = st.number_input("数値を入力してください:", value=0)

if st.button("計算"):
    response = requests.post(
        "http://localhost:8000/process",
        json={"value": input_value}
    )
    
    if response.status_code == 200:
        result = response.json()["value"]
        st.success(f"計算結果: {result}")
    else:
        st.error("エラーが発生しました")
```
```rust:backend/src/main.rs
use actix_web::{web, App, HttpServer, Responder};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Data {
    value: i32,
}

async fn process_data(data: web::Json<Data>) -> impl Responder {
    let result = data.value * 2;
    web::Json(Data { value: result })
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Server running at http://localhost:8000");
    HttpServer::new(|| {
        App::new()
            .route("/process", web::post().to(process_data))
    })
    .bind("127.0.0.1:8000")?
    .run()
    .await
}
```
frontendは`app.py`を実行して、バックエンドも`cargo run`するだけでお手軽に動くWebアプリが作れます。このWebアプリでは、`let result = data.value * 2;`の部分でフロントエンドから渡ってきた数値を2倍にして返しています。
これだけではつまらないので、ビュフォンの針で円周率の値を推定してみます。
```python:frontend/app.py
import streamlit as st
import requests

st.title("Streamlit + Rust Backend Demo")

num_trials = st.number_input("試行回数を入力してください:", value=100)

if st.button("計算"):
    response = requests.post(
        "http://localhost:8000/buffon_needle",
        json={"value": num_trials}
    )
    
    if response.status_code == 200:
        final_estimate = response.json()
        st.success(f"最終的なπの推定値: {final_estimate}")
    else:
        st.error("エラーが発生しました")
```
```rust:backend/src/main.rs
use actix_web::{web, App, HttpServer, Responder};
use serde::{Deserialize, Serialize};
use rand::Rng;

#[derive(Serialize, Deserialize)]
struct Data {
    value: i32,
}

async fn buffon_needle(data: web::Json<Data>) -> impl Responder {
    let num_trials = data.value;
    let mut hits = 0;
    let needle_length = 1.0; // 針の長さ
    let line_spacing = 1.0; // 線の間隔

    for _ in 1..=num_trials {
        let center = rand::thread_rng().gen_range(0.0..line_spacing); // 針の中心位置
        let angle = rand::thread_rng().gen_range(0.0..std::f64::consts::PI); // 針の角度

        // 針の両端の位置を計算
        let x1 = center - (needle_length / 2.0) * angle.cos();
        let x2 = center + (needle_length / 2.0) * angle.cos();

        // 針が線を交差するかどうかを判定
        if x1 < 0.0 || x2 > line_spacing || x1 > line_spacing || x2 < 0.0 {
            hits += 1; // 交差した場合、ヒットを増やす
        }
    }
    let pi_estimate = 2.0 * num_trials as f64 / hits as f64; // 最終的なπの推定値
    web::Json(pi_estimate) // 最終的な推定値を返す
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Server running at http://localhost:8000");
    HttpServer::new(|| {
        App::new()
            .route("/buffon_needle", web::post().to(buffon_needle))
    })
    .bind("127.0.0.1:8000")?
    .run()
    .await
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3563801/5d2a7679-a3f6-ba42-2fff-46fa3aab4a16.png)

いい感じでした。

### まとめ
適当に書いたバックエンドの動作を確認したいときにStreamlitが有用なんじゃないかなと思いました。