【Rust】hex::encodeとhex::decode

hex::encodeした値をhex::decodeした場合、元のStringを .to_owned().into_bytes() としないといけない。

pub static TEST_STRING : &'static str = "hogehoge";

#[tokio::main]
async fn main() {
    let hex_string = hex::encode(TEST_STRING);
    println!("{}", hex_string);

    let hex_string_decode = hex::decode(hex_string).unwrap();
    assert_eq!(TEST_STRING.to_owned().into_bytes(), hex_string_decode);
}

assert_eq!(TEST_STRING, hex_string_decode); ではないので、注意が必要。

【Rust】ipv6へのreqwestのURLの指定

async fn post_test() -> Result<(), Box<dyn std::error::Error>> {
    let result = reqwest::get("http://[::ffff:c0a8:210a]:3000/health").await;
    println!("{:?}", result);
    Ok(())
}

Ok(Response { url: Url { scheme: “http”, cannot_be_a_base: false, username: “”, password: None, host: Some(Ipv6(::ffff: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”: “10”, “date”: “Wed, 05 Mar 2025 11:29:56 GMT”} })

ちなみに、ipv4だと 以下のようになる。
let result = reqwest::get(“http://192.168.33.10:3000/health”).await;

【Rust】reqwest::Client::new() をforループで実行できるのか?

tokio::spawnのthreadで、reqwest::Clientをループで回す。結論から言うとできる。

//
    let handle = tokio::spawn(async move {
        let _ = post_test().await;
    });
//

async fn post_test() {
    for i in 0..3 {
        let n = Name { family: "tanaka".to_string(), first:"taro".to_string(), age: i as i32};
        let json = json!(n);
        let client = reqwest::Client::new();
        let resp = client.post("http://httpbin.org/post")
            .json(&json)
            .send()
            .await;
        let body = resp.expect("REASON").text().await.unwrap();   
        println!("{}", body);
    }
}

//
“json”: {
“age”: 2,
“family”: “tanaka”,
“first”: “taro”
},
//

存在しないipなど、異常系の場合は、reqwest::getでErrで返ってくるので、成功した時だけ送信する。

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

    let url = ["https://hogehogehogehogehoge.org/".to_string(), "http://httpbin.org/post".to_string()];
    for i in 0..url.len() {
        let result = reqwest::get(url[i].clone()).await;
        match result {
            Err(error) => {
                println!("error, {}", url[i].clone());
            }
            Ok(_) => {
                let n = Name { family: "tanaka".to_string(), first:"taro".to_string(), age: i as i32};
                let json = json!(n);
                let client = reqwest::Client::new();
                let resp = client.post(url[i].clone())
                    .json(&json)
                    .send()
                    .await?;
                let body = resp.text().await.unwrap();   
                println!("{}", body);
            }
        }
    }
    Ok(())
}

一番しっくりするが、なんかベストプラクティスではなさそうな感じがする…

【Rust】SocketAddrによるipv4とipv6の判定

SocketAddrには、is_ipv4、is_ipv6があるが、SocketAddrV6にはない。
socket.ip() で取得した場合、ipv6は [ip]:port としなければならないので、reqwestで送る際も、ipv4/ipv6の判定処理が必要。

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6};

#[tokio::main]
async fn main() {
    let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192,168,33,10)), 3000);
    println!("{}", socket);
    println!("{}", socket.port());
    println!("{}", socket.ip());
    println!("{}", socket.is_ipv4());
    println!("{}", socket.is_ipv6());

    let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080);
    println!("{}", socket);
    println!("{}", socket.port());
    println!("{}", socket.ip());
    println!("{}", socket.is_ipv4());
    println!("{}", socket.is_ipv6());

    // let socket_v6 = SocketAddrV6::new(Ipv6Addr::new(0000,0000,0000,0000,0000,ffff,c0a8,210a), 3000, 0, 0);

    let socket_v6 = SocketAddrV6::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0);

    println!("{}", socket_v6);
    println!("{}", socket_v6.port());
    println!("{}", socket_v6.ip());
}

###ipv4
192.168.33.10:3000
3000
192.168.33.10
true
false

###ipv6
[::1]:8080
8080
::1
false
true

###ipv6
[2001:db8::1]:8080
8080
2001:db8::1

axumの場合も同様

async fn handle_index(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> axum::response::Html<String> {
    println!("{}", addr);
    println!("{}", addr.port());
    println!("{}", addr.ip());

【Rust】axumでページ遷移してもtokio::spawnは実行される?

axumで重い処理を tokio::spawn で別スレッドにしてページのレンダリングを先にした場合、ユーザが別ページに飛んだ場合でも、別スレッドの関数は止まらない。

async fn handle_index() -> axum::response::Html<String> {

    let handle = tokio::spawn(async move {
        let _ = delay().await;
    });
    println!("main thread");
    
    let tera = tera::Tera::new("templates/*").unwrap();
    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    let output = tera.render("app.html", &context);
    return axum::response::Html(output.unwrap());
}

async fn delay() {
    thread::sleep(Duration::from_secs(10));
    println!("sub thread");
}

async fn handle_sub() -> axum::response::Html<String> {
    
    let tera = tera::Tera::new("templates/*").unwrap();

    let data = 100;
    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    let output = tera.render("app.html", &context);
    return axum::response::Html(output.unwrap());
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.57s
Running `target/debug/axum`
main thread
sub thread

そりゃそうだと思っていても、実際にテストしないと安心できませんね。

【Rust】ファイルの拡張子の取得

### 駄目な例
ファイル名の”.”以降の値を取得する

let pos = file_name.find('.').unwrap();
let file_extension = &file_name[(pos+1)..].to_string();

これだと、img.test.png という画像ファイルだった場合、拡張子が test.png になってしまう。
一度、ファイルを保存して、use std::path::PathBuf; を使って、ちゃんと取得する。

                let pathbuf = PathBuf::from(format!("./tmp/{}", file_name));
                let ext_string = pathbuf
                    .extension()
                    .unwrap()
                    .to_string_lossy()
                    .into_owned();
                println!("{}", ext_string);
                let ex_filename = format!("./tmp/{}", file_name);
                let new_filename = format!("./tmp/nft.{}", ext_string);
                fs::rename(ex_filename, new_filename).unwrap();

ファイル名に、”.”が入らないなんてのは勝手な思い込みだ。

【Rust】Unitテストでtype_name_of_valを使って型をチェックする

std::any::type_name::のassertの使い方がよくわからない。type_name_of_valだと、簡単にチェックできる。
https://doc.rust-lang.org/beta/std/any/fn.type_name_of_val.html

use std::any::type_name_of_val;

#[tokio::main]
async fn main() {
    let aws_credential = check_aws_credential().await;
    print_typename(aws_credential);
    assert!(type_name_of_val(&aws_credential).contains("bool"));
}

fn print_typename<T>(_: T) {
    println!("{}", std::any::type_name::<T>());
}

assert_eq!(true || false, hoge); みたいなことはできない。この場合、leftはtrueになってしまう。

【Rust】.envファイルの設定有無をチェックする

.envの設定の有無によって、処理を切り替える。is_empty()で値がセットされているか確認ができる。

#[tokio::main]
async fn main() {
    let aws_credential = check_aws_credential().await;
    println!("{}", aws_credential);
}

async fn check_aws_credential() -> bool {
    let _ = dotenv();
    let aws_access_key = env::var("AWS_ACCESS_KEY_ID").unwrap();
    let aws_secret_key = env::var("AWS_SECRET_ACCESS_KEY").unwrap();
    let aws_bucket_name = env::var("AWS_BUCKET_NAME").unwrap();
    let aws_region = env::var("AWS_REGION").unwrap();

    if aws_access_key.is_empty() || aws_secret_key.is_empty() || aws_bucket_name.is_empty() || aws_region.is_empty() {
        return false;
    }
    return true
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.70s
Running `target/debug/app`
false

なるほど!

【Rust】サーバ側からTera/JavaScriptにデータを渡すとき

JSに渡すときに、let value = {{ data }} と書くと宜しくない。let value = “{{ data }}” とダブルクオテーションで囲って渡してあげる必要がある。

サーバ側

    let tera = tera::Tera::new("templates/*").unwrap();

    let data = 100;

    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    context.insert("data", &data);

    let output = tera.render("index.html", &context);
    return axum::response::Html(output.unwrap());

html/JS側

//
<input type="range" class="form-range" id="customRange1" min="0" max="{{data}}" v-model="amount">
//
<script>
    let value = "{{ data / 2 }}";
</script>

↓ これは動くには動くが駄目。

<script>
    let value = {{ data / 2 }};
</script>

【Rust】Vectorの要素のスライスとVecDequeのdrain(n..)

Vectorの場合は、以下のように書いて、要素のスライスができる。

    let vec: Vec<String> = vec!["A".to_string(), "B".to_string(),"C".to_string(),"D".to_string(),"E".to_string(),"F".to_string()];
    let data = &vec[..3];
    println!("{:?}", data);

Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.84s
Running `target/debug/app`
[“A”, “B”, “C”]

これをVecDequeでやろうとするとエラーになる。

    let vecdeque: VecDeque<String> = VecDeque::from(["A".to_string(), "B".to_string(),"C".to_string(),"D".to_string(),"E".to_string(),"F".to_string()]);
    let data = &vecdeque[..3];
    println!("{:?}", data);

error[E0308]: mismatched types
–> src/main.rs:37:26
|
37 | let data = &vecdeque[..3];
| ^^^ expected `usize`, found `RangeTo<{integer}>`
|
= note: expected type `usize`
found struct `RangeTo<{integer}>`

let mut vecdeque: VecDeque<String> = VecDeque::from(["A".to_string(), "B".to_string(),"C".to_string(),"D".to_string(),"E".to_string(),"F".to_string()]);
    vecdeque.drain(3..);
    println!("{:?}", vecdeque);

[“A”, “B”, “C”]