Open
50

Rust勉強中の細かなメモ

Rust勉強中の細かなメモを書いていく。知ったこと気づいたこととか。

ベクタをスライスにするには&をつける。

[https://doc.rust-lang.org/std/vec/struct.Vec.html#slicing]

たとえば、以下のような引数を求められているとき。

pub fn load_from_memory(buffer: &[u8]) -> ImageResult<DynamicImage>

[https://docs.rs/image/0.23.12/image/fn.load_from_memory.html]

let image_bytes: Vec<u8> = res.body_bytes().await?;
let image = image::load_from_memory(&image_bytes)?;

同じ理由でcargo clippyで指摘されるもの。Vecの参照は引数で使うならスライスにする。

fn sample(&Vec<AAA>) {}

ではなく、

fn sample(&[AAA]){}

とする。

ベクタを規定のサイズで二重にしたいときitertoolsのchunksを使う。

use itertools::Itertools;

fn vec_to_double_vec<T>(vec: Vec<T>, size: usize) -> Vec<Vec<T>> {
    vec.into_iter()
        .chunks(size)
        .into_iter()
        .map(|chunk| chunk.collect_vec())
        .collect_vec()
}

fn output() {
        let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
        let vec_vec = vec_to_double_vec(vec, 3);
        println!("{:?}", vec_vec);
}

// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11]]

並び替えをランダムにしたいとき。

use rand::seq::SliceRandom;

fn shuffle() {
	let ids = "a,b,c,d,e,f,g";
	let mut v: Vec<&str> = ids.split(',').collect();
	let mut rng = rand::thread_rng();
	v.shuffle(&mut rng);
	println!("{:?}", v);
}

// ["b", "g", "f", "a", "e", "d", "c"]

引数に&strStringも両方どちらでも渡せるようにしたいとき、impl Into<String>を使うことでそのように指定できる。

fn sample_into(
    value: impl Into<String>,
)  {
    let value = value.into();
    // なんらかの処理 
}

これを使わないと呼び出し側で&strのときto_string()が必要になったり、Stringのときに一度&strにしてから内部でto_string()してコストがかかったりするので、上記を使うと良い。

どういうときが両方渡せるようにすべきときで、どういうときが片方に固定したほうがいいのかはちょっとまだわかってない。全部、Intoにするとかはダメなんだろうか。ダメだからそうはなっていない感じはある。

asyncファンクションをイテレータのmapなどで使いたいときはこんな感じ。asyncファンクションを使うmapと使わないmapを続けるのは無理っぽいので必要なところまで作ってawaitしてからイテレータにする。

async fn plus_one_async(num: i32) -> i32 {
	num + 1
}
fn plus_three(num: i32) -> i32 {
	num + 3
}

async fn sample() {
	let results = future::join_all((0..5).map(|i| plus_one_async(i)))
		.await
		.into_iter()
		.map(|i| plus_three(i))
		.collect_vec();
	println!("Got {:?}", results);
}

fn run() {
	async_std::task::block_on(sample());
}

// [4, 5, 6, 7, 8]

分けてもいいかもしれない。好みやときどきの状況によって。

async fn sample2() {
        let results = future::join_all((0..5).map(|i| plus_one_async(i))).await;
        let results = results.into_iter().map(|i| plus_three(i)).collect_vec();
        println!("Got {:?}", results);
}

これで値を返すようなメソッドで中でResultとかをあれこれしていたら#[async_trait]をつけたtraitでは使えなかった。

future cannot be sent between threads safely

future returned by __sample is not Send

help: the trait std::marker::Send is not implemented for (dyn std::error::Error + 'static)
note: required for the cast to the object type dyn futures::Future<Output = std::vec::Vec<u32>> + std::marker::Sendrustc

ちょっと調べたけど、よくわからない……。futureとか#[async_trait]のマクロの構造上のむずかしさなのか?
Resultがなかったり、trait以外ではコンパイルが通ったりで、どこが原因でなにを直せばいいかは理解できていない。

とりあえず、forを使って、中でひとつずつawaitしつつ、vecに入れる形にしたらコンパイルは通るようにはなった。

イテレータでのmapなどで、値をそのまま関数の引数で使う場合は、省略できる。

let vec = ids
            .into_iter()
            .map(|id| id_to_image_url(id))
            .map(|url| get_image_from_url(url))
	    .collect_vec();

ではなく、

let vec = ids
            .into_iter()
            .map(id_to_image_url)
            .map(get_image_from_url)
	    .collect_vec();

とすることができる。

fn sample() -> Result<(ImageBuffer<image::Rgb<u8>, std::vec::Vec<u8>>, u32, u32), ParameterError> {}

みたいな長くて複雑な型を使っているとclippyにwarning: very complex type used. Consider factoring parts into type definitionsと言われる。そもそも見にくいしわかりにくいのでtypeエイリアスを使おう。

type ImageBufferRgb8 = ImageBuffer<Rgb<u8>, Vec<u8>>;

fn sample() -> Result<(ImageBufferRgb8, u32, u32), ParameterError> {}

どこまでまとめるかは他との兼ね合い次第だろうか。

include_bytes!はコンパイル時に引数のファイルを読み込んでバイトスライスにしてくれる。

let file_bytes : &[u8] = include_bytes!("../読み込みたいファイル") as &[u8];

コンパイル時なので、以下のように変数を引数にしたりはできない。

let file_path = "../読み込みたいファイル";
let file_bytes : &[u8] = include_bytes!(file_path) as &[u8];

ディレクトリでのモジュールの切り方。

sample_mod/sample.rsのようなディレクトリ構造でファイルを切りたいときは以下のようにする。

lib.rs
sample_mod
  sample_code.rs
sample_mod.rs

ディレクトリ名と同じ、sample_mod.rsのファイルも作り中に以下のように記載する。

// sample_mod.rs
pub mod sample_code;

lib.rsでは以下のように書いて使う。

mod sample_mod;
use sample_mod::sample_code;

ディレクトリがさらに下に切られる場合とかも同じだろうか? 試したときに追記する。

昔は、mod.rsをディレクトリ内に置く方法があったらしい。今は互換のために残されているとのこと。こっちのほうが好みだけどあまり使わないほうがいいのだろうか。

参考 [Rustのモジュールの使い方 2018 Edition版 | κeenのHappy Hacκing Blog https://keens.github.io/blog/2018/12/08/rustnomoju_runotsukaikata_2018_editionhan/]

別のモジュールを使う場合はuse crate::〜を使う。たとえば

sample_mod1
  sample_code_1.rs
sample_mod2
  sample_code_2.rs

のように2つファイルがあり、sample_code_1.rsでsample_code_2.rsを使う場合は、

// sample_code_1.rs

use crate::sample_code_2;

のようにする。ディレクトリと同じ名前のファイルなどは別途用意すること。
同じディレクトリ内の別モジュールも同様?

別名をつけて、深い階層にあるものを短く公開することができる。

たとえば

aaa
  bbb
    ccc.rs

のようなモジュールの場合に、

aaa::bbb::ccc::sample_method_name();

のように読んだり、インポートが必要だけど、公開する側で、

pub mod aaa;
pub use self::aaa::bbb::ccc as abc;

としておけば

abc::sample_method_name();

とできる。下手に使うとわけがわからなくなりそうだけど、ちゃんと使えばソースのディレクトリをちゃんと切りつつ、使う側で楽にわかりやすくできそう。

BufWriterwrite_allwriteを使おうとすると以下のようなエラーがでる。

error[E0599]: no method named `write_all` found for mutable reference `&mut std::io::BufWriter<std::fs::File>` in the current scope
  --> src/usecase/day_list_images_maker.rs:58:10
   |
58 |     fout.write_all(b).unwrap();
   |          ^^^^^^^^^ method not found in `&mut std::io::BufWriter<std::fs::File>`
   |
   = help: items from traits can only be used if the trait is in scope
   = note: the following trait is implemented but not in scope; perhaps add a `use` for it:
           `use std::io::Write;`

メッセージの上の方では、存在しないとだけ言われているけど、helpのあたりもちゃんと読むとuse std::io::Write;を使えばいいとわかる。

同じようなことで、ImageBufferwidthheightを使おうとすると以下のようなエラーメッセージが出る。

   |                                                 ^^^^^^ method not found in `image::DynamicImage`
   |
   = help: items from traits can only be used if the trait is in scope
   = note: the following traits are implemented but not in scope; perhaps add a `use` for one of them:
           candidate #1: `use crate::image::GenericImageView;`
           candidate #2: `use imageproc::drawing::Canvas;`

error: aborting due to previous error; 4 warnings emitted

For more information about this error, try `rustc --explain E0599`.
error: could not compile `rust_text_image_sample`.

helpのところで言われているとおり、use crate::image::GenericImageView;を書けば使えるようになる。

DynamicImagewrite_toのフォーマット指定について、ImageOutputFormat::Jpeg(u8)ImageFormat::Jpegがなどでjpegを指定できる。ImageOutputFormatの方の数字はクオリティらしいけどどの数字がなんなのかはドキュメントに書いてない? 見つからない。

let image = DynamicImage::ImageRgb8(image)
let memory_writer = &mut BufWriter::new(Vec::new());
image.write_to(memory_writer, ImageFormat::Jpeg).unwrap();

image::ImageOutputFormat - Rust
image::ImageFormat - Rust

imageクレートのテストコードでは、

image/save_jpeg.rs at a890363ca2b6850bb9673ea9174d8042216efa0a · image-rs/image

ImageFormat::Jpegデフォルト、ImageOutputFormat::Jpeg(10)smallImageOutputFormat::Jpeg(99)がlargeとなっているので、デフォルトとなっているImageFormat::Jpegを使っておけばとりあえずはよさそうだろうか。

画像をバイトスライスにするにはBufWriterwrite_toでフォーマットを指定しつつベクタに書き出し、flushしてからget_refまたはget_mutで取得する。

    let image = DynamicImage::ImageRgb8(image);
    let memory_writer = &mut BufWriter::new(Vec::new());
    image.write_to(memory_writer, ImageFormat::Jpeg).unwrap();
    memory_writer.flush()?;
    let bytes = memory_writer.get_ref(); // :&[u8]

このバイトスライスをBufWriterでファイルに書き出せばちゃんと画像として保存される。

    let file_path = PathBuf::from("./画像.jpeg");
    let fout = &mut BufWriter::new(File::create(file_path)?);
    fout.write_all(bytes)?;
    fout.flush()?;

ただ、今回はクラウドストレージにAPIで保存することなどを考えてわけたけれど、単純に画像をローカルに保存するだけなら、savesave_with_formatでいい。

image.save(file_path);

traitのメソッドは通常では非同期にできない(asyncをつけられない)。

Rustの非同期プログラミングをマスターする - OPTiM TECH BLOG

まず、トレイトメソッドは戻り値の型を具体的に指定しなければなりません。 しかし非同期関数の戻り値はimpl Future<Output=T>になるので型が分かりません。 結果としてトレイトメソッドを非同期関数化出来ません。

それを便利に楽にやってくれるのが dtolnay/async-trait: Type erasure for async trait methodscargo add async-traitで入れて使う。

use async_trait::async_trait;して(忘れない)、#[async_trait]traitimplの両方に記載すればasyncが使えるようになる。

use async_trait::async_trait;

#[async_trait]
pub trait Port {
    async fn sample(st: String) -> String;
}

pub struct Adaptor;

#[async_trait]
impl Port for Adaptor {
    async fn sample(st: String) -> String {
        // ここに何らかの非同期処理があるとする
        st
    }
}

cargoで入れるときのクレート名の区切りがハイフンで、コードに書く方はアンダーバーなので気をつける。

Javaのインタフェースのようなものをするときはtraitを使う。ただ、implとしていても、単純にその型(以下の例ではPort)の変数に(Adaptorのものを)いれることはできない?

ジェネリクスで指定すれば関数の引数で入れられる。受けるところを参照にしたほうがいいかどうか、どちらでもコンパイル通るけどまだ判断がつかない。いまのところはしたほうがいい気持ち。



// これはできない
pub fn execute_ng() {
    let adaptor = Adaptor {};
    let port: Port = adaptor;
    let value = port.sample("aaaaaa");
}

// これはできる
pub fn execute_ok() {
    let adaptor = Adaptor {};
    let value = execute(&adaptor);
}

pub fn execute<T: Port>(port: &T) {
    let value = port.sample("aaaaa");
}

pub trait Port {
    sample(value: &str) -> String;
}

pub struct Adaptor {};

impl Port for Adaptor{
    sample(value: &str) -> String {
        // Stringを返すなんらかのコード
    }
}

ゆっくり考えると上でできないとしたものは別にできなくてもいいというか、型の記述を書かずに推論させれば実質できていることになるのではないかと気づいた。

error: could not find Cargo.toml in /Users/ユーザ名/Documents/プロジェクトルートディレクトリ or any parent directory
The terminal process "/bin/bash '-c', 'cargo test -- --nocapture it_works'" failed to launch (exit code: 101).

Rustだけではなく、フロント用のコードも含めたリポジトリを作成したら、VSCodeに現れるRun testボタンで個別のテスト実行がエラーになった。ターミナルからcargo testは正常に実行できる。

エラーメッセージを見る限り、Rust用のディレクトリの上のディレクトリにCargo.tomlファイルを探しに行ってしまっている様子。

VSCodeのワークスペース(Rustのワークスペースではない)機能使い、RustのCargo.tomlが置かれているディレクトリを基準に設定したら、Run testも動くようになった。

単体テストの書き方。Rustでは同じファイル内にテストを書ける。

同じファイル内でtestsモジュールなどを切った場合、use super::*;を記述すれば、テスト対象の関数をそのまま呼び出せる。

fn plus(value1: u32, value2:u32) -> u32 {
    value1 + value2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    // 「Run test」ボタンがここに表示される(VSCode)
    fn check_plus() {
        assert_eq!(plus(2, 2), 4);
    }
}

asyncの関数は上記の書き方ではコンパイルエラー(test関数をasyncにできない)になる。#[test]の代わりに非同期ランタイムを使用するクレートの#[async_std::test]などを用いる(tokioにもあるらしい)。
これで以下のようにすればテストコードはcargo testなどで実行できるのだけど、#[test]のときに出てきていたRun testのボタンは表示されない。これは拡張側が対応してくれないとどうしようもないかな。そして個別のクレート用に対応はしてくれなさそう。

async fn plus_async(value1: u32, value2:u32) -> u32 {
    value1 + value2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[async_std::test]
    // 「Run test」ボタンがここに表示されてほしいけどでない(VSCode)
    async fn check_plus_async() {
        assert_eq!(plus_async(2, 2).await, 4);
    }
}

この通常、テスト関数の上に表示されるRun testボタンは#[cfg(test)]をつけているモジュール内のテスト関数を全部実行している? 直下のテストだけ実行されるのかと思っていた。

ただそれがあることにより、ボタンが表示されないasync関数テストも近くにボタンがあれば実行できるので便利ではある。

Cargoのtestのドキュメント。

https://doc.rust-lang.org/cargo/commands/cargo-test.html

コマンドでファイルを指定して実行とかはないっぽい? テストの名前を指定はできるので個別に動かしたい場合は名前を書けばいけそう。他のモジュールに同じ名前のテストがあったら一緒に動きそうだけど。

スリープの仕方。

use std::thread::sleep;
use std::time::Duration;

fn sample_sleep() {
    sleep(Duration::from_millis(20000));
}

簡易な時間の図り方。差分を計算してくれるので自分で引き算とかを書く必要はない。単位も勝手にちょうどいいものにしてくれる。

// たくさん使うならインポートしてもいいけど、
// 少ない箇所でちょっと試すだけなら上から直接書くほうがよさそう
// use std::time::{Duration, Instant};
// use std::thread::sleep;

fn main() {
    let start = std::time::Instant::now();
  
    std::thread::sleep(std::time::Duration::from_millis(5000));
  
    let end = start.elapsed();
  
    println!("{:?}", end); 
}
// -> 5.000139505s

エラー関連の便利なクレートのanyhowについて。

anyhow - Rust

2020年ぐらいからのデファクトらしい。

use anyhow::Result;かコンテキストも使うならuse anyhow::{Context, Result};で使う。

use anyhow::Result;

fn sample() -> Result<()> { // Result<(), anyhow::Error>と書いてあるのと同じ
   sample_error1()?;
   sample_error2()?;
   Ok(())
}

これで、違う種類のエラーが混じっていても勝手にanyhow::Errorにまとめてくれる。便利。

contextを使うことで説明文を含めることができる。クロージャのwith_contextを使えば、エラーになったときだけ文章を作る処理を走らせるとかもできる。

use anyhow::{Context, Result};

fn sample(file_path: PathBuf) -> Result<()> { // Result<(), anyhow::Error>と書いてあるのと同じ
   sample_error1().context("エラーについての説明文")?;
   sample_error2(file_path).with_context(|| format!("エラーについての説明文。たとえばファイルパスを出力に入れたり→ {}", file_path))?;
   Ok(())
}

anyhow!マクロで簡単にエラー作ったりもできる。

use anyhow::{anyhow, Result};

fn sample(value: i32) -> Result<i32> {
    match value {
        1 => Ok(100),
        _ => Err(anyhow!("error massage, value = {}", value)),
    }
}

テストでエラーの比較をしたいとき。Errorやanyhow!のものは単純にassertで比較できないようなので、formatすればエラーメッセージでテストできた。もっといいやり方がありそう。

fn check_invalid() {
    let error_value = "error_value";
    let error = Err(anyhow!("error message sample, error_value = {}", error_value));
    assert_eq!(
        format!("{}", error.unwrap_err()),
        format!("error message sample, error_value = {}", error_value)
    );
}

Webフレームワークのtideの使い方メモ。
実際に使うときは適宜、ファイルを分けてる。

公式と
tide - Rust

他に試している人のブログを参考にした。下の記事と他にバージョンアップごとの記事が0.8.0まである。
async/await 時代の新しい HTTP サーバーフレームワーク tide を試す - Don't Repeat Yourself

とりあえず使ってみる。


use tide::prelude::*;
use tide::{Request, Response, StatusCode};

#[async_std::main]
async fn main() -> tide::Result<()> {
    execute().await
}

pub async fn execute() -> tide::Result<()> {
    tide::log::start();
    let mut app = tide::new();
    let app = router_setting_sample(app);
    app.listen("127.0.0.1:8080").await?;
    Ok(())
}

pub fn router_setting_sample(mut app: tide::Server<()>) -> tide::Server<()>{
    let mut root_path = app.at("/root_path");
    root_path.at("/sample1").post(sample_handler1);
    root_path.at("/sample2").post(sample_handler2);
    app
}

// handlerの関数については後述

tide::new()で大本のサーバ用変数を作り、そこにパスとハンドラの関数を追加していく感じ。ひとつの関数の中で全部書いたりできるけど、まとめるのがいいか、どうわけるのがいいかなどはまだよくわからない。

routerについてネスト用の関数(そのままnestというもの)もあって、ネストさせた感じで書けるのだけど、ネストさせると中でServerを新しくnewしたり、リターンの行が必要だったりで、あまりわかりやすく思えなかったのでとりあえず使わないことにした。

今回はサーバの起動箇所とrouterとハンドラをわけてみたものを、こちらのメモ用にひとつのファイルで動く形に直した。


#[derive(Debug, Deserialize)]
struct Target {
    sample_value: String,
}

pub async fn sample_handler1(mut req: Request<()>) -> tide::Result {
    let Target { sample_value } = req.body_json().await?;
    // このあたりで実際はコントローラ呼んだり、いろいろする
    let mut res = Response::new(StatusCode::Ok);
    res.set_body(format!("sample_handler1_body, sample_value = {}", sample_value));
    res.append_header("key: impl Into<HeaderName>", "value: impl ToHeaderValues");
    res.set_content_type("text/plain");
    Ok(res)
}

pub async fn sample_handler2(mut req: Request<()>) -> tide::Result {
    let Target { sample_value } = req.body_json().await?;
    // このあたりで実際はコントローラ呼んだり、いろいろする
    let res = Response::builder(StatusCode::Ok)
        .body(format!("sample_handler2_body, sample_value = {}", sample_value))
        .header("key: impl Into<HeaderName>", "value: impl ToHeaderValues")
        .content_type("text/plain")
        .build();
    Ok(res)
}

レスポンスについては、ミュータブルな変数を作り、そこに値を設定していく方法(sample_handler1)とビルダを呼んでメソッドチェーンで追加していく方法(sample_handler2)がある。他にもクロージャでミュータブルな変数を受け取り、それを変更して返す方法もある様子。

呼び出されている関数自身の名前を取る方法を探したけれど、標準では用意されていないらしい。

クレートはあった(試してない)。
function_name - Rust

ソースを見るといろいろやっているので簡単ではないようだ。

モジュールまでなら標準に用意されているマクロで簡単に取れる。

        // /aaa/bbb/ccc.rs に存在する場合
        let mod_path = module_path!();
        println!("module_path = {}", mod_path);

       // aaa::bbb::ccc

line!()のマクロで行番号を取れたりもするらしい。

環境変数の取得について。

PORTに値が設定されているとする。

use std::env;

// matchを使ったり
fn sample1() {
    let port = match env::var("PORT") {
        Ok(p) =>  p,
        Err(_e) => "8080".to_string()
    };
    // なんらかの処理
}

// もしくはunwrap_orを使ったり
fn sample2() {
    let listener = env::var("PORT")
        .map(|port| "0.0.0.0:".to_string() + &port)
        .unwrap_or("127.0.0.1:8080".to_string());
    // なんらかの処理
}

単純に取得するだけならenv::var("キー")だけど、Resultになっているので、matchunwrap_orなどで取り出す必要がある。

Rustの文字列の連結について。以下の左側の型はわかりやすくするために書いているだけで、実際は推論させることができるので書かないことが多い。


    let a: &str = "aaa";
    let b: &str = "bbb";

    // let ab: &str = a + b; // これはコンパイルエラー

&str&str+で足すことはできない。
結果をStringにする形で、String + &strという形で結合することはできる。ただし、順序を逆にした&str + Stringはできない

    let ab: String = a.to_string() + b;
    let ab: &str = &ab; // どうしても結果を&strにしたいなら、Stringに&をつけて変換

    // let ab: String = a + b.to_string(); // これはコンパイルエラー

String + Stringもできない。上で書いたようにあとから足す方を&strにすればいい。

    // &strからStringを作る方法も2種類ある
    let a: String = String::from(a);
    let b: String = b.to_string();

    // let ab: String = a + b; // これはコンパイルエラー
    let ab: String = a + &b;

なおこのとき前の方で足される側であるa: Stringの所有権がab :Stringに移るので、これ以降ではa: Stringの変数は使えなくなる(b: Stringは使える)。

他にformat!マクロを使う方法もある。

    let c: &str = "ccc";
    let d: &str = "ddd";
    let cd: &str = format!("{}{}", c, d);

    let c: String = String::from(c);
    let d: String = d.to_string();
    let cd: String = format!("{}{}", c, d);
    // let cd: &str = format!("{}{}", c, d); // これはVSCodeのエディタ上ではエラーにならないけどコンパイルの際にエラーになった

所有権も奪わないし、&str同士も連結できるし、 結果を&strにできるし、 足すだけでなく他の文字列を前後や間に入れたりもできて便利。全部、format!でいいのでは……? コストが+よりかかるのかどうかはわからない。

他にミュータブルなStringに対してpush_str を使う方法もあるらしいけど個人的にあまり使わなさそう。

メモリ使用量の確認。

sysinfoクレートを使用する。以下のコードはexampleをまとめただけ。

sysinfo - Rust

use sysinfo::SystemExt;

fn memory_check() {
    let mut system = sysinfo::System::new_all();
    system.refresh_all();
    println!("total memory: {} KB", system.get_total_memory());
    println!("used memory : {} KB", system.get_used_memory());
    println!("total swap  : {} KB", system.get_total_swap());
    println!("used swap   : {} KB", system.get_used_swap());
}

メモリ以外にもいろいろ取れる。

上記だと端末全体のメモリになってよくわからないときがあるので、実行してるプロセスのメモリを見たいときは以下のようにする。

let pid = format!("{}", std::process::id());

fn output_ps(pid: &str) {
    let output = std::process::Command::new("ps")
        .arg("aux")
        .arg(pid)
        .output()
        .expect("failed to execute process");

    let ps = output.stdout;
    println!("{}", std::str::from_utf8(&ps).unwrap());
}

PIDを取得して、そのPIDのps情報を出力する。

USER        PID        %CPU %MEM      VSZ          RSS      TT     STAT STARTED      TIME        COMMAND
user          88592  98.3     0.2            4421752  34940 s016  S+     2:12PM          0:03.86 target/debug/rust_sample

RSSの値がメモリの値(KB)。

パニックするときのバックトレースが見たい場合、環境変数のRUST_BACKTRACE=1をつけて実行する。

$ RUST_BACKTRACE=1 cargo run

RUST_BACKTRACE=fullにするとさらに詳細が出る。

独自エラーの実装について。
thiserrorクレートを使うといいらしい。cargo addして使う。

use thiserror::Error;

#[derive(Error, Debug)]
pub enum SampleMyError {
    #[error("Unimplemented Error.")]
    Unimplemented,
    #[error("Sample error. {msg})")]
    Sample1 {
        value: String,
        msg: String,
    },
    #[error(transparent)]
    Sample2(#[from] std::io::Error),
    #[error("unknow error")]
    Unknown,
}

#[derive(Error, Debug)]をつけることで、Errorトレイトを満たすためのめんどうなものを自動で用意してくれるとのこと。
#[error("Sample error. {msg})")]の部分はprintなどで出力されるときのメッセージで、{0}など数字でも指定できる様子。

くわしくは公式や参考記事を見る。
thiserror - Rust
Rust エラー処理2020

tideを使う際のResponseのエラーハンドリングについて。

公式の例を参考にしつつ、関数を切り分ける形で実装する。
ミドルウェアを用いて、独自実装のエラーごとにボディやステータスコードなどを設定する。

pub(crate) fn sample_router(mut app: tide::Server<()>) -> tide::Server<()> {
    let mut sample = app.at("/sample");
    // ミドルウェアの呼び出しはgetやpostなどのメソッドを指定するより前でないといけない様子
    sample.with(After(error_distributor));
    sample
        .at("/hc")
        .get(|_| async move { Ok("health check ok.\n") });
    sample
        .at("/sample_error")
        .get(sample_handler); // この中で独自実装のエラーが出て、Resultで返されるとする
   // 他の処理
}

// ステータスコードは例として意味を考えずばらばらに設定ししている
async fn error_distributor(mut res: Response) -> tide::Result {
    if let Some(err) = res.downcast_error::< SampleMyError >() {
        match err {
            SampleMyError::Unimplemented => {
                res.set_body("Unimplemented error!!");
                res.set_status(StatusCode::BadRequest);
            }
            SampleMyError::Sample1 {
                value: _,
                msg: _,
            } => {
                // ここをres.set_body(msg.to_string());のように1行で書くと警告が出るけどよくわかってない
                let msg = msg.to_string();
                res.set_body(msg);
                res.set_status(StatusCode::TooEarly);
            }
            // 何もしない場合の例。実際、エラーが来て何もしないことはあまりなさそうだけれど
            SampleMyError::Sample2(_) => {}
            SampleMyError::Unknown => {
                res.set_body("Unknown error!!");
                res.set_status(StatusCode::InternalServerError);
            }
        }
    }
    Ok(res)
}

これで、ハンドラが独自実装のエラーを返した場合にエラーの種別によって内容を変えることができた。

こういったエラーハンドリングを行わない場合、tideで判定できるものは判定する(たとえばPOSTのJSONを構造体に変換しようとして必須項目がなかった場合などに422 Unprocessable Entityになる)。判定できないものは(仮に上記のエラーハンドリングを行わずに独自実装のエラーをそのままフレームワークに任せた場合)、500 - Internal Server Errorになったりする。

あとpanicEmpty reply from serverになって何も返さなかったりする(これはcurlでの例)。

上記でres.set_body(msg.to_string());と1行にした場合に出る警告は以下のような感じ。

cannot borrow res as mutable because it is also borrowed as immutable

mutable borrow occurs here

note: #[warn(mutable_borrow_reservation_conflict)] on by default
warning: this borrowing pattern was not meant to be accepted, and may become a hard error in the future
note: for more information, see issue #59159 <https://github.com/rustlang/rust/issues/59159>rustc(mutable_borrow_reservation_conflict)

借用関連で将来エラーになるかもと言われるけどどう直すのがいいのかよくわかってない。2行に分けたら警告消えたけど、どうも正しい対応ではない気がする。でも公式の例も2行にわけてあり、これを1行にすると同じく警告が出るようなので2行にわけるのでいいのだろうか?

フィーチャーフラグについて。

クレートが、フィーチャーフラグを用いて機能を切り替えるようになっている場合、Cargo.tomlで設定できる。

[dependencies]
async-std = { version = "1.8.0", features = ["attributes"] }
serde = { version = "1.0", features = ["derive"] }

たとえば上記ではattributesderiveの機能をONにしている。クレートのコード上では、

#[cfg(feature="derive")]
// ここで関数定義や使用など

のように書かれている。

クレートがデフォルトでONにしているものもある。

[features]
default = ["h1-server", "cookies", "logger", "sessions"]

などのようにクレートのCargo.tomlに記載されている。

そして上記のように書かれているクレートで、「だけどloggerは別のを使いたいからOFFにしたい」というときは、以下のように自身のCargo.tomlに記述する。

[dependencies.{クレート名}]
version = "0.15.1"
default-features = false
features = ["h1-server", "cookies", "sessions"]

ひとつずつOFFにすることはできないようなので、default-features = falseで全部を切ってから、使うものを個別にfeaturesに指定する。上の例では、loggerを除く残り3つをONにしている。

もしくは、

{クレート名}.version = "0.15.1"
{クレート名}.default-features = false
{クレート名}.features = ["h1-server", "cookies", "sessions"]

とかでもよさそう。

上記だとcargo addでパースエラーが出たので以下がいい?

{クレート名} = { version = "0.16.0", default-features = false, features = ["h1-server", "cookies", "sessions"] }

panicについて、Javaでいう例外をキャッチして、ログ吐いて再スローみたいなものをめざしたかったけれど、キャッチに近いようなものではあまり情報が取れないらしい。

Rustのcatch_unwindでスタックトレースを取得する | Tkr Blog

フックで取得して〜のようなもので共通の処理は書ける。
Rustでデフォルトのパニック表示を損なわずにpanic時に行われる処理を増やす - ncaq

今のところサーバでpanicが出たときに共通のログを出したいだけなので今回はこれを利用する。

let default_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
	default_hook(panic_info);
	error!(
		type_ = "Panic",
		message = format!("{:?}", panic_info).as_str()
	);
}));

default_hook(panic_info);はJavaのthrowとは違って呼び出したときに投げるわけではないので、順番は出力したい順番でいい。ただし、default_hook(panic_info);でない方の処理が複雑でpanicを出しそうな場合は、元のpanicを潰してしまわないように先にした方がいいかもしれない。

ログはlogクレートとenv_loggerクレートを使うのがだいたい良いらしい。

ただ、アプリケーションサーバで、リクエストごとにトレース用のIDを用意し、すべてのログに仕込む、というようなことはできない(と思われる)。

そういうときはtracingクレートを使う。
tokio-rs/tracing: Application level tracing for Rust.

tracing = "0.1.23"
tracing-subscriber = "0.2.15"
tracing-futures = "0.2.5"

あたりをcargoでいれて使う。

tracing is a framework for instrumenting Rust programs to collect structured, event-based diagnostic information. tracing is maintained by the Tokio project, but does not require the tokio runtime to be used.

Tokioプロジェクトで作ってるけど、tokioランタイムはいらないので、どこでも使えるよ、と書いてある。

くわしいことはこちらを参考にした。
Rustで書かれたシステムのトレーシングを実装するtracing | Think IT(シンクイット)
tracing crateを利用したRustのlogging方法について - CADDi Tech Blog

tideで使う上での参考にしたのはこちら。
ethanboxx/tide-tracing: A simple middleware for tide using the tracing crate for logging.

tracing_subscriber::fmt()
        .json()
        .with_current_span(false)
        .flatten_event(true)
        .with_span_list(true)
        .init();
let app = tide::new();

tide::new()の前で上記のような感じで起動しておく。json()を呼ぶことでJSON形式にできる。その後の3つの関数はフラグを設定することで、出力有無や形式を変更できる。デフォルトでいいならtracing_subscriber::fmt().init()だけでいい。

単純にログを出すだけなら下記のように普通のlogクレートようにまくろ使える。

use tracing::{error, info, warn};

info!("メッセージ");
warn!("メッセージ");
error!("メッセージ");

以下のように名前と値のような形で設定することもできる。

let message_value = get_message(); 
error!(method = "POST", message = message_value.as_str());

-> {"timestamp":"Feb 24 15:39:13.685","level":"ERROR","method":"POST","message":"メッセージ"

変数で入れる場合は、as_str()をその場で呼ばないとコンパイルエラーになることが多い。このコンパイルエラーは、VSCodeで編集中はエラーにならず、cargo runしたときにはじめてわかったりするので注意。

そして共通の値を入れたいときは以下のようにspanのついた関数を使う。

// 省略、いくつかの値をリクエストから取得していたり
let request_id = Uuid::new_v4().to_simple().to_string();
Ok(async {
        // 省略、リクエストから処理を実行しレスポンスやステータスコードを取得したりしているとする
        let response = other1::run(request);
        let status = //
	if status.is_server_error() {
		error!(
			type_ = "Response",
			method = method.as_str(),
			status = status as u16,
			time = time.as_str(),
		);
	} else if status.is_client_error() {
            // 省略、warnで出したり
	} else {
		info!(
			type_ = "Response",
			method = method.as_str(),
			status = status as u16,
			time = time.as_str(),
		);
	}
	response
}
.instrument(info_span!("trace", request_id = request_id.as_str()))
.await)

// 別のモジュール
fn run(request: Request) -> Response {
    let response = other2::sample(request);
    info!("run"); // span_info!内なので、このログにも上記のrequest_idが追記で出力される
    response
}

// さらに別のモジュール
fn sample(request: Request) -> Response {
    let response = // 省略
    info!("sample"); // span_info!内なので、このログにも上記のrequest_idが追記で出力される
    response
}

.instrument(info_span!("メッセージなど"))をasyncで囲んだブロックにメソッドチェーンで呼ぶことで、このブロック内で呼んだすべてのログにinfo_span!の情報を足すことができる。この関数内で記述しているものだけでなく、この関数内で呼んだ、別のモジュールで呼び出したtraceing::info()などにも追加してくれる。

たとえば上記の最後にあるinfo!("sample"){"timestamp":"Feb 24 15:39:21.629","level":"INFO","message":"sample","target":"other2","spans":[{"request_id":"3435ef094c6b41be9be40ac35ba0a99e","name":"trace"}]}のような出力になる。targetのところは呼び出しモジュールが自動で勝手に出力されるもの。spansのところがinfo_span!で追記したもの。複数のinfo_spanで囲めばここに複数出力される。

spanのものには、通常のログと同じくwarn_span!error_span!もある(他にもある)。また、info_span!で囲んだ中でerror!()を呼ぶと、LevelERRORで、かつ、info_spanの内容が追記された形で出力される。

上記のようなものをリクエストごとに呼ばれるミドルウェアとして用意すれば、非同期のリクエストごとにちゃんと固有のIDが振られたログを出すことができる(スリープなどでタイミングをずらしても問題ないことを確認した)。

UUIDについて。

uuidクレートを利用する。
uuid-rs/uuid: Generate and parse UUIDs.

上で書いたリクエストごとにログへIDを入れたりに使っている。

cargouuid = { version = "0.8.2", features = ["serde", "v4"] }を入れる。v4を使わないならfeaturesは不要。

let uuid = Uuid::new_v4().to_simple().to_string();

これでUUIDを取得できる。いくつかある形式はto_simple()の部分を別のメソッドにすれば使うことができる。to_urn()とか。
uuid::Uuid - Rust

イテレータで文字列を結合したいとき。

// コンパイルエラーの例
let joined = values.iter().join(", ");

のようにして、joinがないと言われて、あれ、ないんだっけ? となるけど、イテレータにjoinはない。Vecにあるので、一度、collectで変換してからjoinする。

// 正しい例
let joined = values.iter().collect::<Vec<_>>().join(", ")

itertoolsでは直接、joinできるらしいけど、パフォーマンスが悪いとのこと。
Rustで文字列イテレータを連結するときに便利な itertools::join は結構遅い - Qiita

3倍程度の差を許容できるところかどうかで使い分ければよさそう。

println!format!の中で、{}などの中括弧や"のようなダブルクォーテーションを文字列として出したいとき。

中括弧は2つ重ねると出せる。

println!("{{ {} }}", "aa");
// -> { aa }

ダブルクォーテーションはバックスラッシュでエスケープ。

println!("\"{}\"", "aa");
// -> "aa"

{ "aa" : "aaaaa" }のようなものを出したいときは上記を合わせて、

let aa = "aaaaa";
println!("{{ \"aa\" : \"{}\" }}", aa);
// -> { "aa" : "aaaaa" }

とすればいい。

構造体の出力について。

printlnなどでの{}std::fmt::Displayfmt(&self, f: &mut fmt::Formatter) -> fmt::Resultを用意しないと使えない。
{:?}#[derive(Debug)]をつけることで自動的に使えるようになる。
fmt::Dispkayは自分できれいに表示形式を整えたいとき用か。

くわしくはこちら。
ディスプレイ - Rust By Example 日本語版

#[derive(Debug)]
struct Target {
    date: String,
}

impl fmt::Display for Target {
    // This trait requires `fmt` with this exact signature.
    // このトレイトは`fmt`が想定通りのシグネチャであることを要求します。
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "sample target => date : {}", self.date)
    }
}

// 以下はdateに20210127が入っている場合の出力例

println!("target={}", target);
// -> sample target => date : 20210127

println!("target={:?}", target);
// -> target=Target { date: "20210127" }

参照を外せない構造体の中身をミュータブルに更新したいとき。
とりあえずコンパイルは通ったし動いているけれど、こういう状況になるのが正しいのか、こういう方法でやりすごしていいのかはわからない。

use std::sync::Mutex;

pub struct State {
    pub value: Mutex<String>,
}

impl State {
    pub fn set_value(&self, v: String) {
        let mut value = self.value.lock().unwrap();
        *value = v;
    }
}

fn set_value_outer(state: &State, v : String) {
    state.set_value(v);
}


fn main() {
    let s = State {value : Mutex::new("aaa".to_string())};
    println!("{}", s.value.lock().unwrap());
    s.set_value("bbb".to_string());
    println!("{}", s.value.lock().unwrap());
    let s2 :&State = &s;
    set_value_outer(s2, "ccc".to_string());
    println!("{}", s2.value.lock().unwrap()); 
}

構造体がCloneできる状態でないとダメな場合は、Arcも合わせて使う。

use chrono::{DateTime, Local};
use std::sync::{Arc, Mutex};

#[derive(Clone)]
pub struct State {
    value: Arc<Mutex<String>>,
}

impl State {
    pub fn new() -> Self {
        let value = Arc::new(Mutex::new("first".to_string()));
        State { value: value }
    }
    pub fn set_value(&self, v: String) {
        let mut value = self.value.lock().unwrap();
        *value = v;
    }

    pub fn get_value(&self) -> String {
        (*self.value.lock().unwrap()).clone()
    }
}

fn sample(req: Request<State>) {
        let s = &req.state();
        let local_datetime: DateTime<Local> = Local::now();
        let time = format!("{}", local_datetime);
        println!("state.value = {}, time = {}", s.get_value(), time);
        let state_ = req.state();
        state_.set_value(time);
}


// 時間をあけて2回リクエストしたとする、前のリクエスト時にセットされたものが保持されているのがわかる
// -> state.value = first, time = 2021-03-18 12:04:01.726265 +09:00
// -> state.value = 2021-03-18 12:04:01.726265 +09:00, time = 2021-03-18 12:05:22.875833 +09:00

詳細がわかりそうなくわしいことはこちらの参考記事を読む。
[Arc<Mutex<T>>という形はデザインパターン - Rustコトハジメ] (https://rustforbeginners.hatenablog.com/entry/arc-mutex-design-pattern)

Rustのforはfor文ではなくfor式。ただ、じゃあ「値を返そう」と思ってもそれはできない。返り値の型は()固定なのでJavaとかのfor文と基本大差がないと考えるのがよさそう。Scalaのfor式みたいに便利ではない。

ループで値を返したい場合は、イテレータを使うのが基本のようだ。findを使えばfor文のbreakのように必要なところまでだけ処理して終わらせることができる。以下の例では条件と一致する4まで出力して、5以降は出力処理に入らず終わっている。

fn main() {
    let values : Vec<i32> = vec![1, 2, 3, 4, 5, 6];
    let value : Option<i32> = 
        values
            .iter()
            .map(|v| print(*v))
            .find(|v| *v == 4);
    println!("{:?}", value);        
}
// -> 1, 2, 3, 4, Some(4)

fn print(num : i32) -> i32 {
    print!("{}, ", num);
    num
}

イテレータを使うのがむずかしい場合はloopを使うとbreak 値のようにして返すことができるのでそちらを検討してもいいかもしれない。

文字列(&str)の一部を切り取る方法。
ベクタ? なのでインデクスの範囲指定で一部を切り取ることができる。
前方や後方から指定文字数削ったり、それを利用して、指定の文字の部分を削ったり。
範囲から溢れた指定をするとパニックになるので注意。

fn main() {
    let value = "aaaabbbbcccc";
    let value = &value[5..];
    println!("{}", value);
    
    let value = "aaaabbbbcccc";
    let value = &value[2..7];
    println!("{}", value);

    let aa = "aaaa";
    let value = "aaaabbbbcccc";
    let value = &value[aa.len()..];
    println!("{}", value);

}

// -> bbbcccc
// -> aabbb
// -> bbbbcccc

文字列の数値への変換。

型は推論ではなくちゃんと指定すること。unwrap()のところも利用箇所に応じてちゃんとしたほうがいい。

let num: i64 = "1615967167".parse().unwrap();

Unix timeの文字列を数値にしてからDatetimeへ変換。

use chrono::{DateTime, TimeZone, Utc};

let exp: i64 = "1615967167".parse().unwrap();
let dt = Utc.timestamp(exp, 0);
println!("{}", dt);
// -> 2021-03-17 07:46:07 UTC

Unix timeを取得。

let unixtime : i64 = Utc::now().timestamp();

// もう少しわけると以下な型の流れの感じ
let now : DateTime<Utc> = Utc::now();
let now : i64 = now.timestamp();

ミリ秒やナノ秒まで取得するものもある。timestamp_millisとか。

chronoでは日時を扱うDateTimeと日付を扱うDateがある。ジェネリクスの部分でタイムゾーンを指定する(UTCとかLocalとか)。

さらにもっとタイムゾーンを使うときはchrono-tzがいいらしい。

参考記事:Rustで日時を扱う - Qiita

utcとlocalの試すと以下の感じ。timestampはunixtimeなので同じになる。

fn time_disp() {
        let utc_date_time = Utc::now();
        let local_date_time = chrono::Local::now();
        println!(
            "utc_date_time = {}, local_date_time = {}, utc_timestamp = {}, local_timestamp = {}",
            utc_date_time,
            local_date_time,
            utc_date_time.timestamp(),
            local_date_time.timestamp()
        );
}
// -> utc_date_time = 2021-04-07 07:34:53.516659 UTC, local_date_time = 2021-04-07 16:34:53.516670 +09:00, utc_timestamp = 1617780893, local_timestamp = 1617780893

パターンマッチでアーリーリターン的な。

以下の例ではエラーのときに、のようにしているけど別にそれに限ったことではない。
これでできるというだけで、これが良い書き方なのかはまだ迷いがある。ifでしたほうがいいか、アーリーリターンしないで、正常・異常で返す形を揃えたほうがいいかなど。

let value = match result {
	Ok(value) => value,
	Err(err) => return {エラーメッセージとかなにか返したいものを用意},
}

// returnしない方は、なにもしたくないとき
let value = match result {
	Err(err) => return {エラーメッセージとかなにか返したいものを用意},
	_ => (),
}

文字列のパターンマッチについて。

&strは深く気にせずパターンマッチできる。
Stringの場合、比較相手を&strにすることは当然できないし、"sample".to_string()とかもできない。Stringをパターンマッチしたい場合は以下のように元の値の方を'&str'に変える。

fn main() {
    let value: String = "bbb".to_string();
    let value = match value.as_str() {
        "aaa" => 3,
        "bbb" => 4,
        _ => 5,
    };
    println!("{}", value);
}
// -> 4

enumについて。以下のように用意できる。

くわしくは公式? https://doc.rust-jp.rs/book-ja/ch06-01-defining-an-enum.html

assertで比較できるように#[derive(PartialEq, Eq)]を付けると良い。Clone, Debug, とかも付けられるなら付けたほうがいいらしい。この辺り、まだちゃんと理解が及んでいない。振る舞いを追加するためのもので付けられるなら付けたほうがいいとかどこかで見た気がする。ダメなパターンとかがよくわかってない。


#[derive(Clone, Debug, PartialEq, Eq)]
enum Sample1 {
    Name,
    Body,
    Image,
}

// 構造体のようにメソッドを作れる
impl Sample1 {
    fn new() -> Self {
        let sample = Sample1::Name; // なんらかの判定処理があって決まったとする
        sample
    }

    fn is_body(&self) -> bool {
        *self == Sample1::Body
    } 
}

fn print_sample1() {
    println!("{}", Sample1::new().is_body());
    println!("{}", Sample1::Body.is_body());
}
// -> false
// -> true

// 値を持たせたりもできる
#[derive(Debug)]
enum Sample2 {
    Value,
    Age(i32),
    Name { first: String, last: String },
}

fn print_sample2_name() {
    let sample2 = Sample2::Name{ first : "aaaa".to_string(), last : "bbb".to_string() }; 
    println!("{:?}", sample2);
}
// -> Name { first: "aaaa", last: "bbb" }

注意!!

同じキーの環境変数を設定するテストが複数ある場合、テストが並列に実行されるので、環境変数が混ざることがあり、タイミングによってテストが失敗するとかめんどうなことになる。
どうしようか。

Rustのテストを並列実行しない - Qiita

テストを並列実行しない方法はあるようだけど、影響ないところは並列実行したいのでなにか別の手段はないか。

rust - How can I avoid running some tests in parallel? - Stack Overflow
の2つ目のを試してみたけど上手くいかなかった。

環境変数はサーバ起動時に取得して保持し使い回す形にして、利用箇所のテスト時はそこを環境変数以外から設定できるようにする方針とした。


環境変数を使った関数のテストをしたいときはenv::set_varを使って一時的に設定する。
不要な環境変数を消したい場合はenv::remove_var("環境変数のキー");とかする。

use anyhow::{anyhow, Result};
use std::env;

#[derive(Clone, Debug, PartialEq, Eq)]
enum Env {
    Local,
    Dev,
    Prod,
}

impl Env {
    fn new() -> Result<Self> {
        let env = env::var("ENV").unwrap_or_default();
        let env = match env.as_str() {
            "local" => Env::Local,
            "dev" => Env::Dev,
            "prod" => Env::Prod,
            _ => {
                return Err(anyhow!(
                    "ENV invalid error, ENV = {}, ENV is local or dev or prod",
                    env
                ))
            }
        };
        Ok(env)
    }
}

#[cfg(test)]
mod env_tests {
    use super::*;

    #[test]
    fn check_env_local() -> Result<()> {
        env::set_var("ENV", "local");
        assert_eq!(Env::new()?, Env::Local);
        Ok(())
    }
}

anyhow::Resultを返したい関数で、ライブラリの独自エラー(ここではsample::errors::Error)のResultがある場合にそれをそのまま書くと以下のようなコンパイルエラーになる。

us anyhow::Result;

fn sample() -> Result<String> {
    let value : sample::Result<String> = // なんらかの処理
    value
}

mismatched types

expected struct anyhow::Error, found struct sample::errors::Error

note: expected enum std::result::Result<_, anyhow::Error>
found enum std::result::Result<_, sample::errors::Error>

一旦、?を使って、それをOk()するとコンパイル通る。なんらかの理屈で変換されているようだけど、なにがどうなのかよくわからない。エラーのときとOkのときにそれぞれ戻り値の型に合わせて変換するからで、独自Resultのときは両方を変換することはできないから、とかなんだろうか。

us anyhow::Result;

fn sample() -> Result<String> {
    let value : sample::Result<String> = // なんらかの処理、もしくはここで?を使っても良い
    Ok(value?)
}

async関数だと上記のようなものはできない気がする。async, awaitだと見えている型とは別の型にさらに包まれているからとかか?

作成者以外のコメントは許可されていません