Open63

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]){}

とする。

逆のパターンでスライスをベクタにしたいとき(構造体に持たせたいときとか?)はto_vec()を使う。

let bytes: &[u8] = "aaa".as_bytes();
let bytes: Vec<u8> = bytes.to_vec();
犬子蓮木犬子蓮木

ベクタを規定のサイズで二重にしたいとき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エイリアス(alias)を使おう。

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

犬子蓮木犬子蓮木

traitや#[async_trait]を使っているとコンパイルサイズが決まらないとかSendがないとかエラーが出ることがある。以下のような感じでBox<dyn 〜>を使ったり、SyncSendをつけたらとりあえずコンパイルは通せた。ちゃんとした意味は今後、確認する。


#[async_trait]
pub trait SamplePort: Sync + Send {
    async fn sample(&self, value: String) -> Result<()>;
}

struct SampleAdaptor {
    id: String,
    name: String,
    sample: Box<dyn Sample + Sync + Send>,
}

#[async_trait]
impl SamplePort for SampleAdaptor {
    async fn sample(&self, value: String) -> Result<()> {
         // なんらかの実装
    }
}
犬子蓮木犬子蓮木

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 {
    fn sample(value: &str) -> String;
}

pub struct Adaptor {};

impl Port for Adaptor{
    fn 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)] をつけているモジュール内のテスト関数を全部実行している? 直下のテストだけ実行されるのかと思っていた。

Run testのボタンは、その下の関数名で全体のモジュール名とテスト名を部分一致させて実行しているようだ。なのでtest()のような関数名として、その上でRun testのボタンを押すと、モジュールのパスにtestの文字が入っているテストは全部実行される。逆に、この関数名を一位になるようにしたり、動かしたいものだけの共通の部分名を持った名前とすれば、望んだものを実行させることができる。

これはcargo testの名称指定の実行がそのような仕様になっているので、このボタンもそれを利用しているのだと思う。

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

Cargoのtestのドキュメント。
https://doc.rust-lang.org/cargo/commands/cargo-test.html

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

犬子蓮木犬子蓮木

フロントも含めたようなリポジトリでVSCodeのtestボタンを押すとCargo.tomlが見つからないなどのエラーになることがある。

これについてはVSCodeのWorkspace機能を使って、Cargo.tomlがルートにあるような構成に見せかける設定とすることで使えるようになる。

犬子蓮木犬子蓮木

スリープの仕方。

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すればエラーメッセージでテストできた。もっといいやり方がありそう。

#[test]
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とか)。

タイムゾーンを持たないものとしてはNaiveDateTimeNaiveDateがある。

さらにもっとタイムゾーンを使うときは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

文字列からNativeDateを作ったりその逆をしたりするときのフォーマットルールは下記参照。
https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html

犬子蓮木犬子蓮木

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

以下の例ではエラーのときに、のようにしているけど別にそれに限ったことではない。
これでできるというだけで、これが良い書き方なのかはまだ迷いがある。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だと見えている型とは別の型にさらに包まれているからとかか?

犬子蓮木犬子蓮木

あまりよくないことだけど、いろいろな都合で現在は使っていないコードのnever usedの警告を消したいときは#[allow(dead_code)]を利用する。

#[allow(dead_code)]
fn sample() -> i32 {
    3
}
犬子蓮木犬子蓮木

構造体の一部だけ変更した新しい構造体を欲しい場合はスプレッド演算子らしきものが使える。
ただ別に元のものを変えていいならmutをつければいいのであまり使わないかもしれない。


#[derive(Debug)]
struct Sample {
    name: String,
    value: i32
}

fn sample1() {
    let sample = Sample { name : "aaa".to_string(), value: 4 };
    let sample = Sample {
        name: "bbb".to_string(),
       ..sample
    };
    println!("{:?}", sample);
}
// -> Sample { name: "bbb", value: 4 }

fn sample2() {
    let mut sample = Sample { name : "aaa".to_string(), value: 4 };
    sample.name = "bbb".to_string();
    println!("{:?}", sample);
}
// -> Sample { name: "bbb", value: 4 }


犬子蓮木犬子蓮木

テスト時にpanicすることを確認したい場合、#[should_panic]をつける。

#[test]
#[should_panic]
fn check_invalid() {
    sample_with_panic();
}

これでテストはできるんだけど、VSCodeのテストボタンから実行したときに、テストが成功(panicが期待どおりに発生)していてもスタックトレースのようなものを画面に出してくるのがちょっと気になる。

このマクロを使わずにクロージャ内で補足してResultにする形を試してみたけどそっちも同じでトレースが表示された。panicが発生する段階でしょうがないのかもしれない。

あまりpanicを積極的に使うことはないのでとりあえずは我慢。

犬子蓮木犬子蓮木

自分で用意した構造体をHashMapのキーなどに使いたいとき。Hashのdeliveを使ったりすればいい。

詳細はこちらの参考記事を見る。
https://doc.rust-lang.org/std/hash/trait.Hash.html

// 以下のHashの部分をつける
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Sample {
    value1: String
    value2: NaiveDate,
}

項目を省いたり独自に調整したい場合は以下のようにもできる。

use std::hash::{Hash, Hasher};

impl Hash for Sample {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.value1.hash(state);
    }
}

どの言語でも言えることだけど、ハッシュとイコールの対応のあれこれはちゃんと気をつけること。

犬子蓮木犬子蓮木

ミュータブルなグローバル変数のようなものが欲しいときonce_cellを使う。
あまり盛大に使うものではないけれど、テスト時にインタフェースのメソッドをMockとして用意し、その中でonce_cellを利用した変数を使用すれば、習得する値や設定された値の確認などが行える。もっとちゃんとするならMock用のクレートを探して方がいいかもしれないけど。

matklad/once_cell: Rust library for single assignment cells and lazy statics without macros

use once_cell::sync::OnceCell;

#[derive(Clone, Debug, PartialEq, Eq)]
struct DBContainer {
	data: SampleData,
}

pub trait DBPort {
    fn save(&self, data: SampleData) -> Result<()>;
}

static DB_CONTAINER: OnceCell<DBContainer> = OnceCell::new();
struct DBTestAdaptor {}
impl DBPort for DBTestAdaptor {
	fn save(&self, data: SampleData) -> Result<()> {
		let db_container = DBContainer { data };
		let _ = DB_CONTAINER.set(db_container);
		Ok(())
	}
}

#[test]
fn check() -> Result<()> {
	let data = SampleData{ value: "aaaa".to_string() };
	let db_port = DBTestAdaptor {};
	db_port.save(data.clone());
	assert_eq!(data, DB_CONTAINER.get().unwrap());
	Ok(())
}

犬子蓮木犬子蓮木

&strから構造体を作成するときに、単純にメソッドを用意するより標準のFromStrトレイトを利用するといいらしい。
以下では20210516のようなものから構造体を作るときの例。

このメソッドを使う側にもuse std::str::FromStr;を書かないといけない?

use anyhow::Result;
use chrono::NaiveDate;
use std::str::FromStr;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct YearMonthDay {
    value: NaiveDate,
}

impl YearMonthDay {

    // 単純に同じような作成する動作を用意するだけならここにfrom_strを書けばいいけれど、それよりの下のようにした方がいい。

    pub fn yyyy_mm_dd(&self) -> String {
        self.value.format("%Y%m%d").to_string()
    }
}

impl FromStr for YearMonthDay {
    // ここのエラーはそのときに必要なものに変えることができる
    type Err = anyhow::Error;
    fn from_str(yyyy_mm_dd: &str) -> Result<Self, Self::Err> {
        let value = NaiveDate::parse_from_str(yyyy_mm_dd, "%Y%m%d")?;
        Ok(Self { value })
    }
}
犬子蓮木犬子蓮木

serde_jsonjson!マクロを使うと簡単にJSONを作れる。
serde_json - Rust

Cargo.tomlは以下の感じ。JSONのを使うだけならserde_deriveはいらないかも。

serde = { version = "1.0.126", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0"

use serde_json::json;
use serde_json::Value;
use itertools::Itertools;

fn book_to_json(
    book: Book,
) -> Value {
    json!({
        "id" : book.id,
        "book_name" : book.book_name,
        "author_name" : book.author_name,
        "publisher_name" : book.publisher_name,
    })
}

fn books_to_json(
    books: Vec<Book>,
   user_id: String,
) -> Value {
    let books: Vec<Value> = books
        .books
        .into_iter()
        .map(|book| book_write_json(book))
        .collect_vec();
    // 別のところで作ったValueやVec<Value>を埋め込むこともできる
    json!({
        "books": books,
        "user_id": user_id
    })
}

Valueに.to_string_pretty()することで整形したJSONを文字列で取得できるとドキュメントに書いてあるのだけど、なぜかメソッドがないと言われる。なにか足りてない?

犬子蓮木犬子蓮木

base64のデコード。

marshallpierce/rust-base64: base64, in rust を使う。cargo add base64とかで入れる。

base64で記載されたファイルから読み込む場合、最後に余計な改行とか入っているとデコード時にエラーになるので注意。

let content_base64 = fs::read_to_string("/パス/base64のファイル名")?;
println!("{}", content_base64);
let bytes = base64::decode(content_base64).unwrap();
let content = String::from_utf8(bytes.to_vec()).unwrap();
println!("{}", content);
犬子蓮木犬子蓮木

テスト用のモジュールに書いた関数は別のテスト用モジュールからは呼び出せて、テスト用ではないモジュール(通常のコード、テスト対象となるコードがある場所)からではインポートがエラーになるようなので、便利に使えそう。

たとえばnew type pattern(Stringなどを構造体に持たせるのではなく、ひとつひとつに型を用意する形)などを使っていて、初期化するのがめんどうな場合、テスト時だけさぼるとか、&strをStringに変換するto_string()をテスト時だけ減らすとかできる。

これが推奨されるのか、なにかアンチパターンが潜んでいるのかはわからない。

// sample1モジュール(sample1.rsファイル)にいるとする

pub struct Book {
    pub id: Id,
    pub book_name: BookName,
}

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

    pub fn book_for_test(
        id: &str,
        book_name: &str,
    ) -> AmazonBook {
        AmazonBook {
            asin_id: AsinId::new(asin_id),
            book_name: BookName::new(book_name),
        }
    }
}


// 以下はsample2モジュール(sample2.rsファイル)にいるとする

use crate::sample1::{ Book, Id, BookName };

// これはコンパイルエラー
// use crate::smaple1::test::book_for_test;

// 本番用コードでは以下のようにちゃんと書く、
// この例ではあまり意義は感じられないけど、こうすることでより型安全になる
fn sample() {
    let book = Book{
      id : Id("sample_id".to_string()),
      book_name : BookName("book_name".to_string()),
    };
    println!("{}", book);
}

// でもテストでこれをいっぱい書くのはめんどくさい
#[cfg(test)]
pub mod tests {
    use super::*;
    use crate::smaple1::test::book_for_test;

    fn sample() {
        // let book = Book{
        //     id : Id("sample_id".to_string()),
        //     book_name : BookName("book_name".to_string()),
        // }
        // 上記の代わりに以下のように書ける
        let book = book_for_test("sample_id", "sample_book");
        println!("{}", book);
    }
}

上記のuse crate::smaple1::test::book_for_test;#[cfg(test)]以外の場所で使おうとしてもコンパイルエラーになってくれるようなので、テスト用の関数が使われてしまうことは多分ない。