Rust勉強中の細かなメモ
Rust勉強中の細かなメモを書いていく。知ったこと気づいたこととか。
ベクタをスライスにするには&
をつける。
たとえば、以下のような引数を求められているとき。
pub fn load_from_memory(buffer: &[u8]) -> ImageResult<DynamicImage>
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"]
引数に&str
もString
も両方どちらでも渡せるようにしたいとき、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 notSend
help: the trait
std::marker::Send
is not implemented for(dyn std::error::Error + 'static)
note: required for the cast to the object typedyn futures::Future<Output = std::vec::Vec<u32>> + std::marker::Send
rustc
ちょっと調べたけど、よくわからない……。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();
とできる。下手に使うとわけがわからなくなりそうだけど、ちゃんと使えばソースのディレクトリをちゃんと切りつつ、使う側で楽にわかりやすくできそう。
BufWriter
でwrite_all
やwrite
を使おうとすると以下のようなエラーがでる。
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;
を使えばいいとわかる。
同じようなことで、ImageBuffer
のwidth
やheight
を使おうとすると以下のようなエラーメッセージが出る。
| ^^^^^^ 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;
を書けば使えるようになる。
DynamicImage
のwrite_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)
がsmall
、ImageOutputFormat::Jpeg(99)
がlargeとなっているので、デフォルトとなっているImageFormat::Jpeg
を使っておけばとりあえずはよさそうだろうか。
画像をバイトスライスにするにはBufWriter
のwrite_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で保存することなどを考えてわけたけれど、単純に画像をローカルに保存するだけなら、save
やsave_with_format
でいい。
image.save(file_path);
trait
のメソッドは通常では非同期にできない(asyncをつけられない)。
Rustの非同期プログラミングをマスターする - OPTiM TECH BLOG
まず、トレイトメソッドは戻り値の型を具体的に指定しなければなりません。 しかし非同期関数の戻り値はimpl Future<Output=T>になるので型が分かりません。 結果としてトレイトメソッドを非同期関数化出来ません。
それを便利に楽にやってくれるのが dtolnay/async-trait: Type erasure for async trait methods 。cargo add async-trait
で入れて使う。
use async_trait::async_trait;
して(忘れない)、#[async_trait]
をtrait
とimpl
の両方に記載すれば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 〜>
を使ったり、Sync
やSend
をつけたらとりあえずコンパイルは通せた。ちゃんとした意味は今後、確認する。
#[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のドキュメント。
コマンドでファイルを指定して実行とかはないっぽい? テストの名前を指定はできるので個別に動かしたい場合は名前を書けばいけそう。他のモジュールに同じ名前のテストがあったら一緒に動きそうだけど。
フロントも含めたようなリポジトリで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
について。
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になっているので、match
やunwrap_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
をまとめただけ。
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
になったりする。
あとpanic
はEmpty reply from server
になって何も返さなかったりする(これはcurlでの例
)。
上記でres.set_body(msg.to_string());
と1行にした場合に出る警告は以下のような感じ。
cannot borrow
res
as mutable because it is also borrowed as immutablemutable 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"] }
たとえば上記ではattributes
やderive
の機能を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を潰してしまわないように先にした方がいいかもしれない。
クロージャで捕まえるみたいなものもあった。
std::panic::catch_unwind - Rust
let result = panic::catch_unwind(|| {
panic!("oh no!");
});
assert!(result.is_err());
ログは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!()
を呼ぶと、Level
はERROR
で、かつ、info_span
の内容が追記された形で出力される。
上記のようなものをリクエストごとに呼ばれるミドルウェアとして用意すれば、非同期のリクエストごとにちゃんと固有のIDが振られたログを出すことができる(スリープなどでタイミングをずらしても問題ないことを確認した)。
UUIDについて。
uuid
クレートを利用する。
uuid-rs/uuid: Generate and parse UUIDs.
上で書いたリクエストごとにログへIDを入れたりに使っている。
cargo
でuuid = { 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::Display
のfmt(&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とか)。
タイムゾーンを持たないものとしてはNaiveDateTime
とNaiveDate
がある。
さらにもっとタイムゾーンを使うときは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
を作ったりその逆をしたりするときのフォーマットルールは下記参照。
パターンマッチでアーリーリターン的な。
以下の例ではエラーのときに、のようにしているけど別にそれに限ったことではない。
これでできるというだけで、これが良い書き方なのかはまだ迷いがある。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 - 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 structsample::errors::Error
note: expected enum
std::result::Result<_, anyhow::Error>
found enumstd::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を使ったりすればいい。
詳細はこちらの参考記事を見る。
// 以下の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 })
}
}
文字列からバイト配列つくりたいとき。
参考記事(戻したいときも載ってる):Rustでバイト列から文字列へ - Qiita
let str = "aaa";
let bytes: &[u8] = s.as_bytes();
serde_json
のjson!
マクロを使うと簡単に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)]
以外の場所で使おうとしてもコンパイルエラーになってくれるようなので、テスト用の関数が使われてしまうことは多分ない。