【Rust】カレンダーを作る【Algorithm】

fn leap_year(year: u32) -> bool {
    if year % 4 == 0 && year % 100 != 0 || year % 400 == 0 {
        return true;
    } 
    false
}

fn day_of_week(year: usize, month: usize) -> usize {
    let mut days_of_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

    if leap_year(year.try_into().unwrap()) {
        days_of_month[2] = 29;
    }

    let day = 1;
    let mut days = 0;

    for y in 1..year {
        if leap_year(y.try_into().unwrap()) {
            days += 366
        } else {
            days += 365
        }
    }

    for m in 1..month {
        days += days_of_month[m as usize]
    }

    days += day;
    return days % 7
}

fn main() {
    let days_of_week_name = ["日".to_string(), "月".to_string(),"火".to_string(),"水".to_string(),"木".to_string(),"金".to_string(),"土".to_string()];

    let mut days_of_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    let year = 2040;
    let month = 3;

    if leap_year(year.try_into().unwrap()) {
        days_of_month[2] = 29;
    }
    let first_day = day_of_week(year, month);

    println!("{}年 {}月", year, month);
    println!("日 月 火 水 木 金 土");
    let emp = " ".to_string();

    print!("{}", emp.repeat(first_day*3));
    for day in 1..(1 + days_of_month[month]) {
        print!("{:<2} ", day);
        if(day + first_day - 1) % 7 == 6 {
            print!("\n");
        }
    }
    print!("\n");
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/basic`
2040年 3月
日 月 火 水 木 金 土
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

これは中々面白い!!! そんなに難しくないけど、中々思いつかない!

【Rust】axumのlayer middlewareにstateを渡す方法

from_fn_with_state という書き方がある。
https://docs.rs/axum/latest/axum/middleware/fn.from_fn_with_state.html

    let app_state = model::connection_pool().await.unwrap();
    let app = Router::new()
        .route("/", get(handler))
        .layer(middleware::from_fn_with_state(app_state.clone(),auth_middleware))
        .layer(CsrfLayer::new(config))
        .with_state(app_state);
//

pub async fn auth_middleware(State(state): State<model::AppState>, req: Request,
    next: Next) -> impl IntoResponse {

    let res = next.run(req).await;
    res   
}

なるほどね、先回りしてよく作られてるw

【Rust】with_state(app_state)とCsrfConfigを共存させたい時

### ダメな例
with_stateを複数繋げるのは上手くいかない

 
    let config = CsrfConfig::default();
    let app = axum::Router::new()
        .merge(public_router)
        .merge(private_router)
        .with_state(config.clone())
        .with_state(app_state);
pub async fn handle_hoge(token: CsrfToken, State(state): State<model::AppState>)-> Response {

### 上手くいく例
csrfの方をstateではなく、layerで渡してあげる。

axum_csrf = {version = “0.11.0”,features = [“layer”]}

use axum_csrf::{CsrfToken, CsrfConfig, CsrfLayer};

    let config = CsrfConfig::default();

    let app = Router::new()
        .merge(public_router)
        .merge(private_router)
        .layer(CsrfLayer::new(config))
        .with_state(app_state);
pub async fn handle_hoge(token: CsrfToken, State(state): State<model::AppState>)-> Response {

なるほどね~

【Rust】Durationをmillisecondでcountする

.subsec_millis() でミリセカンドで取得できる。
.subsec_nanos() だとナノセカンドで値が大きすぎてしまう。

use std::{time};

    let now = time::Instant::now();
    let mut post_url = format!("http://www.jsontest.com/");
    let client = reqwest::Client::new();
    let resp = client.post(post_url)
        .header(reqwest::header::CONTENT_TYPE, "application/json")
        .json(&str)
        .send()
        .await
        .unwrap();
    println!("{}", now.elapsed().subsec_millis());

278

【Rust】anyhowを使ったエラーハンドリング

anyhow = “1.0.97”
https://docs.rs/anyhow/latest/anyhow/

anyhowを使うと、エラー処理が簡単にできるようになる。
anyhow::Errorはエラー型のラッパー、あらゆるエラー型からanyhow::Errorに変換できる。変換できる。
Resultにすると、?演算子で異なる複数の型のエラーを呼び出し元に移譲できる。
contextやwith_contextを使うと、Noneをanyhow::Errorとして移譲することも可能
anyhow::ResultがResultの型エイリアスとして定義されているので、use anyhow::Result;とすると、ResultをResultと書ける。

use anyhow::{ensure, Context, Result};
use std::fs;

#[tokio::main]
async fn main()  {

    let path_123 = "./data/temp/123.txt";
    println!("{}", with_unwrap(&path_123));
    
    // let path_abc = "./data/temp/abc.txt";
    // println!("{}", with_unwrap(&path_abc));

    let path_non_existent = "./data/to/non_existent.txt";
    println!("{}", with_unwrap(&path_non_existent));
}

fn with_unwrap(path: &str) -> i32 {
    let s = fs::read_to_string(path).unwrap();
    let i = s.parse::<i32>().unwrap();

    i * 10
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.76s
Running `target/debug/app`
1230
thread ‘main’ panicked at src/main.rs:16:30:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: “No such file or directory” }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
vagrant@vagrant:~/dev/rust/app$

エラーメッセージが、InvalidDigit、No such file or directoryでそれぞれ異なる。

#[tokio::main]
async fn main()  {

    let path_123 = "./data/temp/123.txt";
    println!("{}", with_unwrap(&path_123).unwrap());
    
    let path_abc = "./data/temp/abc.txt";
    println!("{:?}", with_unwrap(&path_abc).unwrap_err());

    let path_non_existent = "./data/to/non_existent.txt";
    println!("{:?}", with_unwrap(&path_non_existent).unwrap_err());
}

fn with_unwrap(path: &str) -> Result<i32> {
    let s = fs::read_to_string(path)?;
    let i = s.parse::<i32>()?;

    Ok(i * 10)
}

1230
invalid digit found in string
No such file or directory (os error 2)

### anyhow::Contextでコンテキストを追加
with_contextは遅延評価でエラーの時のみ指定

fn with_unwrap(path: &str) -> Result<i32> {
    let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
    let i = s.parse::<i32>()
            .with_context(|| format!("Failed to parse {s:?}"))?;

    Ok(i * 10)
}

1230
Failed to parse “abc”

Caused by:
invalid digit found in string
Failed to read “./data/to/non_existent.txt”

Caused by:
No such file or directory (os error 2)

### OptionをResultに変換
contextおよび with_contextはResultだけでなく、Optionからも呼べる。Noneも呼び出し元に移譲できる。

async fn main()  {

    let path_max = "data/temp/max.txt";
    fs::write(path_max, i32::MAX.to_string()).unwrap();
    println!("{:?}", with_anyhow_option(path_max).unwrap_err());

    let path_123 = "./data/temp/123.txt";
    println!("{}", with_unwrap(&path_123).unwrap());
    
    let path_abc = "./data/temp/abc.txt";
    println!("{:?}", with_unwrap(&path_abc).is_err());

    let path_non_existent = "./data/to/non_existent.txt";
    println!("{:?}", with_unwrap(&path_non_existent).is_err());
}

fn with_anyhow_option(path: &str) -> Result<i32> {
    let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
    let i = s.parse::<i32>()
            .with_context(|| format!("Failed to parse {s:?}"))?;
    let i = i.checked_add(100).context("Overflow occurred")?;

    Ok(i)
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.86s
Running `target/debug/app`
Overflow occurred
1230
true
true

### マクロ(anyhow!, bail!, ensure!)で単発のエラーを返す
ensureで値に応じてエラーとすることもできる。

fn with_anyhow_macro(path: &str) -> Result<i32> {
    let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
    let i = s.parse::<i32>()
            .with_context(|| format!("Failed to parse {s:?}"))?;
    let i = i.checked_add(100).context("Overflow occurred")?;

    ensure!(i >= 1000, "Value must be at least 1000, got {}", i);

    Ok(i)
}

match resultでの処理

    let path_123 = "./data/temp/123.txt";
    let result = with_anyhow_macro(&path_123);
    match result {
        Ok(n) => {
            println!("{}", n);
        }
        Err(error) => {
            println!("{}", error);
        }
    }

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.81s
Running `target/debug/app`
Value must be at least 1000, got 223

【Rust】シャミアの秘密計算(SSS)をRustで動かしてみる

private keyを複数のシェアに分割して、それをrestoreして元のprivate keyに戻すということは割と簡単にできそうではある。

shamirsecretsharing = “0.1.5”

use shamirsecretsharing::*;

async fn main()  {

    let private_key = "i3HdSBYb5fmuD4RxAf77VnYbyAy6b8ab5LEddfQLM2VaYrUUFNNp6Pk22dsTAibj".to_string();
    let data = private_key.into_bytes();
    println!("{:?}", data);

    let count = 3;
    let treshold = 2;
    let mut shares = create_shares(&data, count, treshold).unwrap();
    
    for share in shares.clone() {
        print_base_encode(share);
    }

    let restored = combine_shares(&shares.clone()).unwrap();
    println!("{:?}", String::from_utf8(restored.unwrap()).unwrap());
}

fn print_base_encode(s: Vec<u8>) {
    // println!("{:?}", s.clone());
    let base64_encoded = base64::encode(&s);
    println!("base64 encoded: {}", base64_encoded);
    // println!("{:?}", BASE64_STANDARD.decode(base64_encoded).unwrap());
}

base64 encoded: AZv7eBLYhiBxgjpD3kI0YbLnnC88DnTWPghszvPWHLt4D+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=

base64 encoded: AhM+urNxZnzJsekDjygSpCqresvZz1Kg/zFE8/zdbLkWD+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=

base64 encoded: A2t9DSUWz0ihoFHKSQ7556tm0Zdzebl7SSZcEfkttU7FD+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=
“i3HdSBYb5fmuD4RxAf77VnYbyAy6b8ab5LEddfQLM2VaYrUUFNNp6Pk22dsTAibj”

【Rust】reqwestによるPostのレスポンスタイムの差

幾つかのサイトにデータをPostして、レスポンスタイムに差があるかを調査する。

    let now = time::Instant::now();
    let mut post_url = format!("http://www.jsontest.com/");
    let client = reqwest::Client::new();
    let resp = client.post(post_url)
        .header(reqwest::header::CONTENT_TYPE, "application/json")
        .json(&str)
        .send()
        .await
        .unwrap();
    println!("{:?}", resp);
    println!("{:?}", now.elapsed());

↓レスポンスタイムの差

同じサーバ内
67.997892ms
76.798063ms
73.945108ms

http://httpbin.org/post
626.023117ms
560.575466ms
1.050126063s

https://dummyjson.com/posts
695.071869ms
825.34323ms
676.196368ms

http://www.jsontest.com/
229.820077ms
256.854971ms
203.667686ms

当たり前だが、同じサーバ内のレスポンスは早い。
それ以外でも、サイトごとによって、レスポンスタイムが早いものと遅いものの違いが生じている。
なるほど、なかなか面白いね。

【Rust】バイト列からリトルエンディアンへの変換

    let s = "sato".to_string().into_bytes();
    println!("{:?}", s);
    let l = LittleEndian::read_u32(&s);
    println!("{}", l);
    let b = BigEndian::read_u32(&s);
    println!("{}", b);

    let mut wtr = vec![];
    wtr.write_u32::<LittleEndian>(l).unwrap();
    println!("{:?}", wtr);

[115, 97, 116, 111]
1869898099
1935766639
[115, 97, 116, 111]

構造体をバイト列にする

#[derive(Serialize, Deserialize, Debug)]
struct Name {
    family: Vec<u8>,
    first: Vec<u8>,
}

    let name1 = Name {family: "sato".to_string().into_bytes(), first: "taro".to_string().into_bytes()};
    println!("{:?}", name1);
    let family = String::from_utf8(name1.family).unwrap();
    println!("{}", family);

Name { family: [115, 97, 116, 111], first: [116, 97, 114, 111] }
sato

なるほどー 前処理、後処理が多くなるが、安全性は高まりそう。

【Rust】reqwest Postのレスポンス(body payload)を表示する

“{:?}”, resp だとstatusだが、resp.text().await.unwrap()とすると、bodyを表示できる。

    let mut post_url = format!("http://192.168.33.10:3000/health");
    let client = reqwest::Client::new();
    let resp = client.post(post_url)
        .header(reqwest::header::CONTENT_TYPE, "application/json")
        .json(&str)
        .send()
        .await
        .unwrap();
    println!("{:?}", resp);
    println!("{}", resp.text().await.unwrap());

Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.43s
Running `target/debug/axum`
Response { url: Url { scheme: “http”, cannot_be_a_base: false, username: “”, password: None, host: Some(Ipv4(192.168.33.10)), port: Some(3000), path: “/health”, query: None, fragment: None }, status: 200, headers: {“content-type”: “text/plain; charset=utf-8”, “content-length”: “8”, “date”: “Thu, 13 Mar 2025 13:44:24 GMT”} }
All good

【Rust】tokio::spawnのawait

下のように書くと、DNSエラーとなる。

#[tokio::main]
async fn main()  {
    let res = tokio::spawn(async move {
        let v = get_myip().await.unwrap();
        println!("{}", v);
    });    
}   

pub async fn get_myip() -> Result<String, Box<dyn std::error::Error>> {

    let ip: Ip = serde_json::from_str(&reqwest::get("https://httpbin.org/ip")
        .await?
        .text()
        .await?)?;
    Ok(ip.origin)
}

しかし、実行されるまで待って、res.await.unwrap();とすれば、エラーは解消される。

    let res = tokio::spawn(async move {
        let v = get_myip().await.unwrap();
        println!("{}", v);
    });
    res.await.unwrap();

レスポンスがないままtokio::spawnが終了してしまうからっぽい。
うーん、奥が深い… httpbin.org に問題があるかと思ったけど、全然違った…