Rustの便利クレート

RustにはCargoという優れたパッケージマネージャーがあるので細かい多数の依存パッケージが問題になることはあまりありません。というよりパッケージを細かく分割することが推奨されています。ちょっとしたボイラープレートを取り除くような小さなライブラリも大量にあります。これらは積極的に使うべきです。

問題があるとすれば悪意のあるようなパッケージの存在ですが、これらに対処するcargo-auditcargo-crevというツールもあります。

本記事では

  • 誰かが紹介するか誰かが使っているのを見る、あるいは何かのtrendingに載っているのを見るなどしない限り出会わない
  • 日本語の情報があまり無い
  • 用途がニッチすぎない
  • synとかはしょうがないとして)あまりbloatを引き起こさない
  • deprecatedとかWIPとか書かれたやつは含まない
  • 総DL数が100k以上

のような便利なクレートを50個紹介したいと思います。もちろんこのようなクレートは他にも沢山あります。暇があればcrates.ioを眺めてみるのもいいでしょう。

執筆当時のRustのバージョンは1.40.0です。

対象読者

  • Rustに触れたことが無い

    「Rustではこういうことができる」ということに触れてもらえれば。

  • チュートリアル and/or 書籍を読んだばかり

    頭の片隅にでも置いておくと後で少しだけ楽になるかもしれません。

  • Rustに慣れた人

    知らないクレートがあるかもしれません。

Changelog

ボイラープレート削減

itertools

crates.io docs.rs license downloads

Pythonのmore-itertoolsのようなものです。Iterator拡張します。良く使われているのはjoinformatですがその他のメソッドも大変便利です。一度Itertoolsのメソッドを眺めるのも良いでしょう。

+use itertools::Itertools as _;
+
 static VALUES: &[u32] = &[1, 2, 3];
 ​
-let joined = VALUES
-    .iter()
-    .map(ToString::to_string)
-    .collect::<Vec<_>>()
-    .join(", ");
+let joined = VALUES.iter().join(", ");
 assert_eq!(joined, "1, 2, 3");

またiterate, assert_equalといった関数やizip!, iproduct!といったマクロも便利です。

 static ACTUAL: &str = "  foo bar  baz ";
 static EXPECTED: &str = "foo bar baz";
 ​
-assert_eq!(
-    ACTUAL.split_whitespace().collect::<Vec<_>>(),
-    EXPECTED.split_whitespace().collect::<Vec<_>>(),
-);
+itertools::assert_equal(ACTUAL.split_whitespace(), EXPECTED.split_whitespace());

itertools-num

crates.io docs.rs license downloads

itertoolsと同じくIteratorを拡張しますが提供するのはcumsumlinspace、これだけです。

@bluss氏本人によるクレートですがitertoolsから分離されている理由はよくわかりません。num-traitsへの依存を嫌ったのでしょうか..?

+use itertools_num::ItertoolsNum as _;
+
 static XS: &[u32] = &[1, 2, 3, 4, 5];
-let cumsum = XS
-    .iter()
-    .scan(0, |sum, x| {
-        *sum += x;
-        Some(*sum)
-    })
-    .collect::<Vec<_>>();
+let cumsum = XS.iter().cumsum().collect::<Vec<u32>>();
 assert_eq!(cumsum, &[1, 3, 6, 10, 15]);

 const START: f64 = 2.0;
 const STOP: f64 = 3.0;
 const NUM: usize = 5;
-let linspace = (0..NUM)
-    .map(|i| START + (STOP - START) * (i as f64) / ((NUM - 1) as f64))
-    .collect::<Vec<_>>();
+let linspace = itertools_num::linspace(START, STOP, NUM).collect::<Vec<_>>();
 assert_eq!(linspace, &[2.0, 2.25, 2.5, 2.75, 3.0]);

fallible-iterator

crates.io docs.rs license downloads

Iterator<Item = Result<_, _>>変換して扱いやすくします。

+use fallible_iterator::FallibleIterator as _;
+
 use std::io::{BufRead as _, Cursor};

 let stdin = Cursor::new("1\n2\n3\n101\n");
 ​
-let xs = stdin
-    .lines()
-    .map(|line| {
-        let x = line?.parse::<u32>()?;
-        // https://github.com/rust-lang/rust/issues/64260
-        Ok(if x <= 100 { Some(x) } else { None })
-    })
-    .flat_map(Result::transpose)
-    .collect::<anyhow::Result<Vec<_>>>()?;
+let xs = fallible_iterator::convert(stdin.lines())
+    .map_err(anyhow::Error::from)
+    .map(|line| line.parse::<u32>().map_err(Into::into))
+    .filter(|&x| Ok(x <= 100))
+    .collect::<Vec<_>>()?;

 assert_eq!(xs, &[1, 2, 3]);

walkdir

crates.io docs.rs license downloads

ディレクトリを捜索するときに使えるユーティリティです。

use walkdir::WalkDir;

for entry in WalkDir::new("foo").min_depth(1).max_depth(3) {
    println!("{}", entry?.path().display());
}

anyhow

crates.io docs.rs license downloads

thiserrorと合わせて)post failureの最大手です。

2019-10-05ZにInitial commitされた後2日で1.0.0がリリースされたみたいです。その後先発のsnafuを抜いて最近ではPlaygroundでも使えるようになりました

See:

-use std::fmt;
-
-use thiserror::Error;
-
-type Result<T> = std::result::Result<T, Box<dyn std::error::Error + 'static>>;
-
-trait ResultExt {
-    fn with_context<F: FnOnce() -> E, E: fmt::Display>(self, f: F) -> Self;
-}
-
-impl<T> ResultExt for Result<T> {
-    fn with_context<F: FnOnce() -> E, E: fmt::Display>(self, f: F) -> Self {
-        #[derive(Debug, Error)]
-        #[error("{}", .0)]
-        struct WithContext(String, #[source] Box<dyn std::error::Error + 'static>);
-
-        self.map_err(|e| WithContext(f().to_string(), e).into())
-    }
-}
+use anyhow::Context as _;
-fn main() -> Result<()> {
+fn main() -> anyhow::Result<()> {
     todo!();
 }

either

crates.io docs.rs license downloads

Either<_, _>try_left!, try_right!を提供します。

即席の構造体としてタプルが使えるようにEitherは即席の(バリアント2つの)直和型として使えます。

またEitherには使いやすくするためのメソッドがいくつか付いていて、またstdの各トレイトについてimpl<L: Trait, R: Trait> Trait for Either<L, R>という定義がなされています。

実装が非常に小さいのもありitertoolsrayonをはじめとした多くのクレートに使われ、またre-exportされています。

ちなみに即席直和型を専用構文付きで入れようという議論がpre 1.0時代からあります

-enum Either<L, R> {
-    Left(L),
-    Right(L),
-}
+use either::Either;

defmac

crates.io docs.rs license downloads

を生成するマクロを生成するマクロを提供します。

Rustでは&mut _巻き込むクロージャはその対象を専有する必要がありますがmacro_rules!なら名前さえ見えていれば良いので、代用としてよく使われます()。CellRefCellを使うという手もありますが。

ただしmacro_rules!での定義はrustfmt下で最低5行を要します。defmac!なら1+行に収まるので見た目がスッキリします。

またパターンで引数を与えることもできます。defmac!はこの『引数』をmatch (..) { (..) => .. }と展開するマクロに展開します。

+use defmac::defmac;
+
 let mut st = 0;
-macro_rules! incr {
-    ($n:expr $(,)?) => {
-        st += $n;
-    };
-}
+defmac!(incr n => st += n);

 incr!(1);
 assert_eq!(st, 1);

matches

crates.io docs.rs license downloads

let p = match x {
    $pat => true,
    _ => false,
};

のようなものを

let p = matches!(x, $pat);

のようにできます。

実装は非常に簡素です。27(= 9 + 9 + 11)行しかありません。

また現在core::matches! (i.e. std::matches!)というのが提案されていてnightlyのRustで使うことができます。こちらはifガートがサポートされています。(&&と違いマッチした値が使えます)

-let p = 'a' <= c && 'z' <= c || '0' <= c && c <= '9' || c == '-' || c == '_';
+use matches::matches;
+
+let p = matches!(c, 'a'..='z' | '0'..='9' | '-' | '_');

if_chain

crates.io docs.rs license downloads

ifif letを『まとめる』マクロif_chain!を提供します。

let x: Option<i32> = todo!();

if let Some(x) = x {
    if p(x) {
        f(x);
    } else {
        g();
    }
} else {
    g();
}

use if_chain::if_chain;

let x: Option<i32> = todo!();

if_chain! {
    if let Some(x) = x;
    if p(x);
    then {
        f(x);
    } else {
        g();
    }
}

のようにできます。else節が必要なくてもインデントを2段までに抑える効果があります。

難点はRustの構文としては不正なため現在のrustfmtでは中がフォーマットできないことです。

maplit

crates.io docs.rs license downloads

std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}に対してvec!のようなマクロを提供します。

また各key, valueを指定の関数に通すconvert_args!マクロも便利です。

ちなみにpre 1.0時代からこんな提案があります。もしかしたら標準ライブラリに似たようなのが入るかもしれません。

-use std::collections::HashMap;
+use maplit::hashmap;
-let mut map = HashMap::new();
-map.insert("foo", 10);
-map.insert("bar", 20);
-map.insert("baz", 30);
+let map = hashmap!(
+    "foo" => 10,
+    "bar" => 20,
+    "baz" => 30,
+);

structopt

crates.io docs.rs license downloads

clap(コマンドラインパーサーのデファクトスタンダード)のラッパーです。

v0.3でAPIが大きく変わりました。attributeは一部の名前を除きclap::Argのメソッドとして扱われます。

またclapのv0.3でstructoptと同じAPIのderive macroが追加される予定です

How does clap compare to structopt?

Simple! clap is stuctopt. With the 3.0 release, clap imported the structopt code into it's own codebase as the clap_derive crate. Since structopt already used clap under the hood, the transition was nearly painless, and is 100% feature compatible.

If you were using structopt before, the only thing you should have to do is change the attributes from #[structopt(...)] to #[clap(...)].

Also the derive statements changed from #[derive(Structopt)] to #[derive(Clap)]. There is also some additional functionality that's been added to the clap_derive crate. See the documentation for that crate, for more details.

とのことなので安心して使いましょう。

数行で用意できるので引数を取らないアプリケーションで--helpのためだけに使うのも良いでしょう。

use structopt::StructOpt;

#[derive(StructOpt)]
struct Opt {}

fn main() -> anyhow::Result<()> {
    Opt::from_args();
    Ok(())
}

ちなみにコマンドラインパーサーにはclapの他に以下のものがあります。

See:

-use clap::{App, Arg};
+use structopt::StructOpt;
 use url::Url;

 use std::ffi::OsStr;
 use std::path::PathBuf;

 static ARGS: &[&str] = &[
     "",
     "--norobots",
     "--config",
     "../config.yml",
     "https://example.com",
 ];
 ​
-let matches = App::new(env!("CARGO_PKG_NAME"))
-    .version(env!("CARGO_PKG_VERSION"))
-    .author(env!("CARGO_PKG_AUTHORS"))
-    .about(env!("CARGO_PKG_DESCRIPTION"))
-    .arg(
-        Arg::with_name("norobots")
-            .long("norobots")
-            .help("Ignores robots.txt"),
-    )
-    .arg(
-        Arg::with_name("config")
-            .long("config")
-            .value_name("PATH")
-            .default_value("./config.yml")
-            .help("Path to the config"),
-    )
-    .arg(
-        Arg::with_name("url")
-            .validator(|s| s.parse::<Url>().map(drop).map_err(|e| e.to_string()))
-            .help("URL"),
-    )
-    .get_matches_from_safe(ARGS)?;
-let norobots = matches.is_present("norobots");
-let config = PathBuf::from(matches.value_of_os("config").unwrap());
-let url = matches.value_of("url").unwrap().parse::<Url>().unwrap();
+#[derive(StructOpt)]
+#[structopt(author, about)]
+struct Opt {
+    #[structopt(long, help("Ignores robots.txt"))]
+    norobots: bool,
+    #[structopt(
+        long,
+        value_name("PATH"),
+        default_value("./config.yml"),
+        help("Path to the config")
+    )]
+    config: PathBuf,
+    #[structopt(help("URL"))]
+    url: Url,
+}
+let Opt {
+    norobots,
+    config,
+    url,
+} = Opt::from_iter_safe(ARGS)?;

 assert_eq!(norobots, true);
 assert_eq!(config, OsStr::new("../config.yml"));
 assert_eq!(url, "https://example.com".parse().unwrap());

derivative

crates.io docs.rs license downloads

ドキュメント(docs.rsには何も書かれていない)

stdのderive macroの代替品を提供します。これらのマクロは型境界等を色々とカスタマイズできます。

See: Rustのderiveはあまり頭がよくない

use std::fmt;

struct Foo {
    name: String,
    content: ExternalNonDebuggableItem,
}

impl fmt::Debug for Foo {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("Foo")
            .field("name", &self.name)
            .field("content", &format_args!("_"))
            .finish()
    }
}

use derivative::Derivative;

use std::fmt;

#[derive(Derivative)]
#[derivative(Debug)]
struct Foo {
    name: String,
    #[derivative(Debug(format_with = "fmt_underscore"))]
    content: ExternalNonDebuggableItem,
}

fn fmt_underscore(_: impl Sized, fmt: &mut fmt::Formatter) -> fmt::Result {
    write!(fmt, "_")
}

derive_more

crates.io docs.rs license downloads

derivativeと同じくstdのトレイトに対するderive macroを提供しますが、こちらはFrom, Into, Deref, FromStr, Displaystdでderive macroが用意されていないものが対象です。

-use std::fmt;
+use derive_more::Display;
+#[derive(Display)]
+#[display(fmt = "({}, {})", _0, _1)]
 struct Point2(f64, f64);
-
-impl fmt::Display for Point2 {
-    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        write!(fmt, "({}, {})", self.0, self.1)
-    }
-}

thiserror

crates.io docs.rs license downloads

std::error::Errorを対象とするderive macroを提供します。

use std::path::PathBuf;
use std::{fmt, io};

#[derive(Debug)]
enum Error {
    ReadFile(PathBuf, io::Error),
    Command(PathBuf, io::Error),
    Reqwest(reqwest::Error),
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::ReadFile(path, _) => write!(fmt, "Failed to read {}", path.display()),
            Self::Command(path, _) => write!(fmt, "Failed to execute {}", path.display()),
            Self::Reqwest(err) => write!(fmt, "{}", err),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::ReadFile(_, source) | Self::Command(_, source) => Some(source),
            Self::Reqwest(err) => err.source(),
        }
    }
}

use thiserror::Error;

use std::io;
use std::path::PathBuf;

#[derive(Debug, Error)]
enum Error {
    #[error("Failed to read {}", .0.display())]
    ReadFile(PathBuf, #[source] io::Error),
    #[error("Failed to execute {}", .0.display())]
    Command(PathBuf, #[source] io::Error),
    #[error("{}", .0)]
    Reqwest(#[from] reqwest::Error),
}

num-derive

crates.io docs.rs license downloads

num-traitsの各トレイトを実装するderive macro集です。このクレートはnumには含まれていません。

注意: rust-num/num-derive#34

#[macro_use]
extern crate num_derive;

#[derive(FromPrimitive, ToPrimitive)]
enum Color {
    Red,
    Blue,
    Green,
}

strum

crates.io docs.rs license downloads

enum文字列への/からの変換に特化したderive macro集です。

少し前までstrumとstrum-macroの2つのクレートを使う形でしたが0.16.0からfeature-gatedの形でderive macroをre-exportするようになりました。ドキュメントはそのままのようですが..

[dependencies]
strum = { version = "0.17.1", features = ["derive"] }

serdeと同様の問題を抱えていますが(前はメソッドを直に生やしてたのがトレイトを経由するようになり、それらがマクロ名と衝突する)皆がfeatureを有効化すれば良い話なので使いましょう

See: serdeの `derive` feature と名前空間の困った関係

-use std::convert::Infallible;
-use std::str::FromStr;
+use structopt::StructOpt;
+use strum::{EnumString, EnumVariantNames, VariantNames as _};

 #[derive(StructOpt)]
 struct Opt {
     #[structopt(
         long,
         value_name("FORMAT"),
         default_value("human"),
         possible_values(Format::VARIANTS),
         help("Output format")
     )]
     format: Format,
 }
 ​
+#[derive(EnumString, EnumVariantNames)]
+#[strum(serialize_all = "kebab-case")]
 enum Format {
     Human,
     Json,
 }
-
-impl Format {
-    const VARIANTS: &'static [&'static str] = &["human", "json"];
-}
-
-impl FromStr for Format {
-    type Err = Infallible;
-
-    fn from_str(s: &str) -> Result<Self, Infallible> {
-        match s {
-            "human" => Ok(Self::Human),
-            "json" => Ok(Self::Json),
-            _ => todo!(),
-        }
-    }
-}

derive-new

crates.io docs.rs license downloads

コンストラクタを"new"という名のメソッドとして生やすderive macroです。

derivativeやderive_moreにもコンストラクタを生やすderive macroがあるのですがそれらより高性能です。

-use derive_new::new;
+#[derive(new)]
 struct Item {
     param: u64,
 }
 ​
-impl Item {
-    fn new(param: u64) -> Self {
-        Self { param }
-    }
-}
-
 let item = Item::new(42);

getset

crates.io docs.rs license downloads

getterとsetterを生やすderive macroです。

難点は個々のgetter/setterにdocが設定できない(フィールドのdocが使われる)点と&Deref::Target(e.g. &String&str)を返すgetterを作れない点です。

+use getset::{CopyGetters, MutGetters, Setters};
+
+#[derive(CopyGetters, MutGetters, Setters)]
 pub(crate) struct Item {
+    #[get_copy = "pub(crate)"]
+    #[get_mut = "pub(crate)"]
+    #[set = "pub(crate)"]
     param: u64,
 }
-
-impl Item {
-    #[inline]
-    pub(crate) fn param(&self) -> u64 {
-        self.param
-    }
-
-    #[inline]
-    pub(crate) fn param_mut(&mut self) -> &mut u64 {
-        &mut self.param
-    }
-
-    #[inline]
-    pub(crate) fn set_param(&mut self, param: u64) {
-        self.param = param;
-    }
-}

derive_builder

crates.io docs.rs license downloads

builder patternのbuilderを生成するマクロです。

.build()の返り値はResult<_, _>のみのようです。

use derive_builder::Builder;

#[derive(Builder)]
struct Foo {
    required_param: String,
    #[builder(default)]
    optional_param: Option<String>,
}

let item = FooBuilder::default()
    .required_param("foo".to_owned())
    .optional_param(Some("bar".to_owned()))
    .build()?;

型を強化する

ref-cast

crates.io docs.rs license downloads

StringPathBufのnewtypeを作りたくなったとします。ここでは必ず絶対パスを示すAbsPathBufを作りたいとします。

use std::ffi::OsString;
use std::path::Path;

/// An owned, mutable absolute path.
pub(crate) struct AbsPathBuf(OsString);

impl AbsPathBuf {
    #[inline]
    fn unchecked(s: OsString) -> Self {
        Self(s)
    }

    #[inline]
    fn new(s: OsString) -> Option<Self> {
        if Path::new(&s).is_absolute() {
            Some(Self::unchecked(s))
        } else {
            None
        }
    }
}

AbsPathBufと来たらAbsPathを作りたくなります。これもいいですが

pub(crate) struct AbsPath<'a>(&'a OsStr);

せっかくなのでこうしたいです。

pub(crate) struct AbsPath(OsStr);

そうすると少し困ったことになります。&OsStr&AbsPathBufからどうやって&AbsPathを得るのでしょうか?

impl AbsPath {
    fn unchecked(s: &OsStr) -> &Self {
        todo!("what to do?")
    }
}

impl AbsPathBuf {
    fn as_abs_path(&self) -> &AbsPath {
        todo!("what to do?")
    }
}

実はunsafeな手段しかありません。

#[repr(transparent)]
pub(crate) struct AbsPath(OsStr);

impl AbsPath {
    fn unchecked(s: &OsStr) -> &Self {
        // https://rust-lang.github.io/rust-clippy/current/index.html#transmute_ptr_to_ptr
        unsafe { &*(s as *const OsStr as *const AbsPath) }
    }
}

impl AbsPathBuf {
    fn as_abs_path(&self) -> &AbsPath {
        AbsPath::unchecked(&self.0)
    }
}

ここで注意しなければならないこととして、安全性を確保するために#[repr(C)]#[repr(transparent)]を付ける必要があります。さて、何とかできましたがunsafeが出てしまいました。少し収まりが悪いです。

ここでこのクレートの出番です。ref-castはこのunsafe操作を肩代りしてくれます。何が嬉しいのかと言うとコードの見た目からunsafeが消えます。実は#[forbid(unsafe_code)]はマクロの展開後を見ないようなのでsafety-danceにも多分参加できます。

ちなみに"validation"も含めて簡易に実装できるようにする、validated-sliceというクレート(作者は@lo48576氏)があるみたいです。

+use ref_cast::RefCast
+
 use std::ffi::OsStr;

 /// An absolute path.
+#[derive(RefCast)]
 #[repr(transparent)]
 pub(crate) struct AbsPath(OsStr);

 impl AbsPath {
     fn unchecked(s: &OsStr) -> &Self {
-        // https://rust-lang.github.io/rust-clippy/current/index.html#transmute_ptr_to_ptr
-        unsafe { &*(s as *const OsStr as *const AbsPath) }
+        Self::ref_cast(s)
     }
 }

dimensioned

crates.io docs.rs license downloads

物理量を表現できます。

use dimensioned::si;
use static_assertions::{assert_impl_all, assert_not_impl_any};

use std::ops::{Add, Div};

assert_eq!((3.0 * si::M / (2.0 * si::S)).to_string(), "1.5 m*s^-1");

assert_impl_all!(si::Meter<f64>: Div<si::Second<f64>, Output = si::MeterPerSecond<f64>>);
assert_not_impl_any!(si::Meter<f64>: Add<si::Second<f64>>);

ascii

crates.io docs.rs license downloads

ASCII文字列を表現するAsciiChar, AsciiStr, AsciiStringを提供します。それぞれの表現はu8, [u8], Vec<u8>でチェック以外のオーバーヘッドはありません。

これらはASCII文字列を扱うにあたって[u8]strの両方の長所を持ちます。

use ascii::{AsciiChar, AsciiStr};

let mut s = AsciiStr::from_ascii("abcde\n").unwrap().to_owned();
s[2] = AsciiChar::C;
assert_eq!(s, "abCde\n");
assert_eq!(s.trim_end(), "abCde");

unicase

crates.io docs.rs license downloads

case-insensitiveに2つの文字列を等値判定する関数とそれをPartialEqに使うnewtypeを提供します。

use unicase::UniCase;

let a = UniCase::new("Maße");
let b = UniCase::new("MASSE");
let c = UniCase::new("mase");

assert_eq!(a, b);
assert!(b != c);

ordered-float

crates.io docs.rs license downloads

Ord (: Eq)を実装するNotNan<_: Float>OrderedFloat<_: Float>を提供します。

Rustの比較演算子用のインターフェイスにはEq, Ordの他にPartialEq, PartialOrdがあります。具体的な要請はリンク先を参照してください。比較演算子にはPartialEqPartialOrdが使われます。

何故このような区分があるのかというと浮動小数点数のためです。f32, f64PartialEq, PartialOrdを実装していますがEq, Ordは実装していません。つまりソート等が行なえません。というのもIEEE 754の浮動小数点数においては==の反射律すら成り立たないからです。他の言語においても配列のソートや順序を使うデータ構造の構築にNaNを混ぜるとその結果は保証されません。C++に至っては鼻から悪魔が出てきます。Rustではこのような関数やデータ構造に要素がtotal orderであることを要求することで『浮動小数点数をソートしたら何か変』という事態を防いでいます。

さて、我々Rustユーザーがソート等にどうやって浮動小数点数を使うのかというと..ソートに関しては雑にこのようなことができます。この場合、NaNが混じっていると.unwrap()の部分でpanicします。

let mut xs = vec![2.0, 1.0, 0.0];
xs.sort_by(|a, b| a.partial_cmp(b).unwrap());

これがBTreeMapBinaryHeap等のデータ構造に使うとなるとこうはいきません。f32/f64に対するnewtypeが必要になります。そこでこのクレートの出番です。

NotNan<_>は名の通りです。四則演算の結果NaNになったのなら整数型と同様にpanicします。ただしこちらはrelease buildでもチェックされます。OrderedFloat<_>NaNを許容しますがそれを「最大の値であり、自身と等しい」としています。

im

crates.io docs.rs license downloads

std::collections::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, vec::Vec}のimmutable data版を提供します。

OCamlやHaskell等を使っている方は馴染み深いのではないでしょうか。それぞれの中身はこんな感じのようです。

imim-rcに分けられていますがその違いは前者はArc、後者はRcを使っていることです。
im-rcのデータ型はSyncでもSendでもなく、複数のスレッドから参照するのはもちろん、他スレッドにmoveすることもできません。

ちなみにArcRcにすることで得られる恩恵は"20-25% increase in general performance"だそうです。

-let mut map = maplit::hashmap!();
-map.insert("foo", 42);
+let map = im::hashmap!();
+let map = map.update("foo", 42);

indexmap

crates.io docs.rs license downloads

IndexMap, IndexSetとそれらを生成するmaplit風のコンストラクタ用マクロindexmap!, indexset!を提供します。

JavaのLinkedHashMap, C#のOrderedDictionary, PythonのOrderedDictのようなものです。

Vec<(K, V)>にしたいけどKの重複が無いことを示したい」というとき等に便利です。またserde(特に"ser"の方)と相性がいいです。

-use itertools::Itertools as _;
-use serde::{Deserialize, Deserializer, Serialize};
-
-use std::hash::Hash;
-use std::iter::FromIterator;
-
-#[derive(Serialize)]
-#[serde(transparent)]
-pub(crate) struct UniqueVec<T>(Vec<T>);
-
-impl<T: Clone + Eq + Hash> FromIterator<T> for UniqueVec<T> {
-    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
-        Self(iter.into_iter().unique().collect())
-    }
-}
-
-impl<'de, T: Clone + Eq + Hash + Deserialize<'de>> Deserialize<'de> for UniqueVec<T> {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        Vec::<_>::deserialize(deserializer).map(FromIterator::from_iter)
-    }
-}
+use indexmap::IndexSet;

今のRustでは不可能な抽象化を擬似的に表現する

typenum

crates.io docs.rs license downloads

「型パラメータとしての整数」を表現します。

4桁以下なら定数が揃っていますがそれより大きい値は2つの既存の値を+-*/したりビットで表現することで生み出します。

generic-array

crates.io docs.rs license downloads

[T, _]のように振る舞う、長さをtypenum::UIntで表現するGenericArray<T, _>を提供します。

ちなみにGenericArray<T, _>: IntoIterator<Item = T>です。(これを書いているときのRust 1.40の時点では[T; _]: IntoIteratorではありません。)

stdの挙動を改善する

crossbeam

crates.io docs.rs license downloads

並行処理を行なう低レベルのライブラリです。

rayon(並列処理用のライブラリ), tokio(非同期処理用のライブラリ)に使われています。crossbeamが提供する機能はそれらより低レベルで、std::syncの改善に焦点をあてています。std::sync + std::threadで摩耗したのなら使うと幸せになれるかもしれません。

MPSC (Multi-Producer Single-Consumer) channelの欠点を解消し、パフォーマンスも向上したMP M C (Multi-Producer Multi -Consumer) channle, MPMC queue, work-stealing deque, GC, 複数のスレッドをspawnしてその場でそれらの終了を待つ、'staticを要求しない関数など提供するものは多岐にわたります。

実際に使うときはbloatを避けるため、個別のsubcrate (crossbeam-*)を使うと良いでしょう。

See:

-let (tx, rx) = std::sync::mpsc::channel();
+let (tx, rx) = crossbeam_channel::unbounded();
 tx.send(42)?;
 assert_eq!(rx.recv()?, 42);

libm

crates.io docs.rs license downloads

libmのRust実装です。

最近になってからコンパイラに使われるようにようになったみたいです。(丸め方法等を除いて)プラットフォームに依らない挙動をしていて、数値を扱うクレートに結構使われています

-let val = 10f64.powf(-9.0);
+let val = libm::pow(10.0, -9.0);
 assert_eq!(error.to_string(), "0.000000001");

remove_dir_all

crates.io docs.rs license downloads

Windows用に改善したstd::fs::remove_dir_allを提供します。Windows以外にはstd::fs::remove_dir_allをそのままre-exportしてます。

-std::fs::remove_dir_all("./dir")?;
+remove_dir_all::remove_dir_all("./dir")?;

which

crates.io docs.rs license downloads

実行ファイルを探します。

Windowsではrust-lang/rust#37519があるのでそれを回避する目的で使えます。

デフォルトでエラーがFailです。default-features = falsestd::error::Errorになりますが依存クレートのアップデートで壊れる可能性が付き纏います。

use anyhow::anyhow;

let rustup = which::which("rustup")
    .map_err(|e| anyhow!("{}", e.kind()).context("failed to find `rustup`"))?;

テストに役立つ

pretty_assertions

crates.io docs.rs license downloads

pretty_assertions::{assert_eq!, assert_ne!}を提供します。

これらが失敗したとき、メッセージがDebugのpretty-printed表示のカラフルなdiffで表示されます。

strに対しては下のdifferenceを使うことをおすすめします。

-assert_eq!(4, 2 + 2);
+pretty_assertions::assert_eq!(4, 2 + 2);

difference

crates.io docs.rs license downloads

strのdiffが取れる他、それを使ったassert_diff!を提供しています。

+use difference::assert_diff;
+
 static EXPECTED: &str = "foo\nbar\n";
 static ACTUAL: &str = "foo\nbar\n";
 ​
-assert_eq!(EXPECTED, ACTUAL);
+assert_diff!(EXPECTED, ACTUAL, "\n", 0);

static_assertions

crates.io docs.rs license downloads

コンパイル時の"assertion"を行なうマクロを多数提供します。

use static_assertions::{assert_cfg, assert_eq_align, assert_eq_size, const_assert_eq};

assert_cfg!(
    any(unix, windows),
    "There is only support for Unix or Windows",
);
assert_eq_size!(i64, u64);
assert_eq_align!([i32; 4], i32);
const_assert_eq!(1, 1);

approx

crates.io docs.rs license downloads

f32f64の等値判定が楽にできます。

ところでこれを使ってPartialEqを実装したくなりますが実はPartialEqも推移律は要求されるので良くありません。approxはPartialEqの代わりに絶対誤差(+ 相対誤差)の指定の元で等値判定するAbsDiffEqRelativeEqというトレイトを提供しています。こちらを実装しましょう。

use approx::{abs_diff_eq, relative_eq};

abs_diff_eq!(1.0, 1.0);
relative_eq!(1.0, 1.0);

version-sync

crates.io docs.rs license downloads

READMEを含むドキュメントに書かれている現在のパッケージ($CARGO_PKG_NAME)を指しているバージョンのうち、現在のバージョン($CARGO_PKG_VERSION)と異なるものを検出してくれます。

assert_cmd

crates.io docs.rs license downloads

binクレートの入力→出力を確かめるintegration-testに使えます。

tempdir

crates.io docs.rs license downloads

名の通りです。

TempDirはdrop時に対象の一時ディレクトリを削除します。(self) -> io::Result<()>として削除することもできます。binクレートのテストにも使うことができます。

use tempdir::TempDir;

let tempdir = TempDir::new("appliction-name")?;
let tempdir_path = tempdir.path();

パフォーマンスの向上

take_mut

crates.io docs.rs license downloads

Rustでは&mut Tの値にはそのまま(T) -> Tの関数を適応することができません。Copyでもない限り何らかの壊れていない値を代わりに詰めておく必要があります。良く使われる方法がstd::mem::replace(またはRust 1.40で安定化されたstd::mem::take)でダミーの値を詰めることですがそのような生成に副作用を伴わず軽量な値が常に用意できるとは限りません。

take_mut::takeならダミーの値を用意しなくても(T) -> Tの関数を適応できます。返せなくなったとき、すなわちpanicした場合プログラムをabortさせます。

あとパニックしたときのリカバリを設定できるtake_or_resoverやスコープ内でRefCellのようにmove操作を実行時に判定するscopeという関数もあります。

See: Rustのパニック機構

 use either::Either;

 let mut data: Either<LargeData, LargeData> = todo!();
 if let Either::Left(data) = &mut data {
-    *data = convert(data.clone());
+    take_mut::take(data, convert);
 }

 fn convert(_: LargeData) -> LargeData {
     todo!()
 }

buf_redux

crates.io docs.rs license downloads

std::io::{BufReader, BufWriter, LineWriter}と入れ替えるだけで速くなるみたいです。

-use std::io::{BufReader, BufWriter, LineWriter};
+use buf_redux::{BufReader, BufWriter, LineWriter};

stable_deref_trait

crates.io docs.rs license downloads

このような構造体TextWordを考えてみます。

use std::str::FromStr;

struct Text(Vec<Word>);

enum Word {
    Plain(String),
    Number(String),
    Whitespace(String),
    Lf,
}

impl Text {
    fn diff(&self, other: &Self) -> Result<(), Diff> {
        todo!()
    }
}

impl FromStr for Text {
    type Err = anyhow::Error;

    fn from_str(_: &str) -> anyhow::Result<Self> {
        todo!("parse with `nom` or `combine` or something")
    }
}

ここでStringを沢山作るのは良くないと考えてWordをこのようにするとします。

enum Word<'a> {
    Plain(&'a str),
    Number(&'a str),
    Whitespace(&'a str),
    Lf,
}

Box::leakでリークするのも良くないと考えてWordにライフタイムパラメータを持たせるとします。そうするとTextはこうなります。

struct Text<'a>(Vec<Word<'a>);

そうするとテキスト全体を表わすStringの置き場所が困るので以下のようにしたいです。

struct Text(String, Vec<Word<'this>);

ここで自己参照することで問題を解決します。ここからはunsafeな操作になります。自己参照というとpinがありますが今回はこれを使わなくても安全性を確保できます。まず細心の注意を払いstd::mem::transmuteでライフタイムパラメータを強制的に'staticにします。ここでmutableな操作と自身のライフタイムを伴わない形での&strの持ち出しを防ぐためmodを切って『安全ではない』範囲を最小にします。

use self::inner::TextInner;

struct Text(TextInner);

enum Word<'a> {
    Plain(&'a str),
    Number(&'a str),
    Whitespace(&'a str),
    Lf,
}

mod inner {
    use super::Word;

    use std::mem;

    pub(super) struct TextInner {
        string: String,
        words: Box<[Word<'static>]>, // 実際は`'static`ではない!
    }

    impl TextInner {
        // `impl FnOnce(&str) -> Box<[Word]>`は`impl for<'a> FnOnce(&'a str) -> Box<[Word<'a>]>`の略。
        // `&str`や`Word`を外に持ち出すことはできない。自己参照でなくても良く使われる。
        pub(super) fn new(string: String, words: impl FnOnce(&str) -> Box<[Word]>) -> Self {
            // - `TextInner`はimmutable
            // - 各`&str`は`&'static str`として流出することはない。
            unsafe {
                Self {
                    words: mem::transmute(words(&string)),
                    string,
                }
            }
        }

        pub(super) fn string(&self) -> &str {
            &self.string
        }

        pub(super) fn words<'a>(&'a self) -> &[Word<'a>] {
            &self.words
        }
    }
}

あとの問題はTextInnerがmoveしたときにstring: Stringからの&strが壊れないかです。ここでこのクレートとstatic_assertionsの出番です。これらを使って安心を得ます。

+use stable_deref_trait::StableDeref;
+use static_assertions::assert_impl_all;
+
+assert_impl_all!(String: StableDeref<Target = str>);

StableDerefは(unsafeな操作でライフタイムを誤魔化しながら)自身がmoveしても&Deref::Targetが壊れないものだけに実装されているunsafe traitです。Stringは無理矢理moveしても&<String as Deref>::Target(= &str)は壊れないのでString: StableDerefです。ここで&Stringは壊れることに注意してください。上の例で&strの代わりに&Stringを持つとアウトです。

またCloneStableDerefStableDerefのサブセットで、cloneしても問題ないもの(&_, Rc<_>, Arc<_>)に対して実装されています。

rental

crates.io docs.rs license downloads

stable_deref_traitで紹介した方法を自動でやるマクロです。modごと生成します。またコンストラクタのフィールドの順番も問題無いように並びかえてくれます。

#[macro_use]
extern crate rental;

rental! {
    mod inner {
        use super::Word;

        #[rental]
        pub(super) struct TextInner {
            string: String,
            words: Box<[Word<'string>]>,
        }
    }
}

owning_ref

crates.io docs.rs license downloads

Object: StableDeref&PartOfObjectDerefの組をOwningRef<Object, PartOfObjectDeref>またはOwningRefMut<〃, 〃>として持てます。ただし"ref"の側は&_/&mut _である必要があり、上のText, Word<'_>の例等では使えません。

use anyhow::anyhow;
use once_cell::sync::Lazy;
use owning_ref::OwningRef;
use regex::Regex;

let pair = OwningRef::new("  foo  ".to_owned()).try_map(|s| {
    static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("([a-z]+)").unwrap());
    REGEX
        .captures(&s)
        .map(|caps| caps.get(1).unwrap().as_str())
        .ok_or_else(|| anyhow!("not matched"))
})?;

assert_eq!(&*pair, "foo");
assert_eq!(pair.as_owner(), "  foo  ");

smallvec

crates.io docs.rs license downloads

C++のboost::container::small_vectorのようなものです。

ある長さまで要素を『直に』持ち、残りをVecのようにヒープアロケートします。

「大体は要素数1だけど複数ある場合も考えなくてはならない」といった場合にフィットします。

+use smallvec::SmallVec;
+
 use std::iter;

 let probably_single_values = (0..100)
     .map(|i| generate_values(i).collect())
-    .collect::<Vec<Vec<_>>>();
+    .collect::<Vec<SmallVec<[_; 1]>>>();

arrayvec

crates.io docs.rs license downloads

C++のboost::container::static_vectorのようなものです。

こちらはヒープアロケーションを行なわず一定数までしか要素を持てません。

no-std環境の他「本当に高々<コンパイル時定数>個の要素しか持たない」といった場合にも使えます。

-let mut at_most_100 = vec![];
+use arrayvec::ArrayVec;
+
+let mut at_most_100 = ArrayVec::<[_; 100]>::new();
 at_most_100.push(42);

類似クレート:

arraydeque

crates.io docs.rs license downloads

arrayvecVecDeque版のようなものです。

容量が無いときにエラーにするか反対側の要素を削除(排出)するかを選べます

use arraydeque::ArrayDeque;

let mut deque = ArrayDeque::<[_; 10]>::new();
deque.push_front(1)?;
deque.push_back(1)?;

let mut deque = ArrayDeque::<[_; 10], arraydeque::Wrapping>::new();
assert!(deque.push_front(1).is_none());
assert!(deque.push_back(1).is_none());

enum-map

crates.io docs.rs license downloads

unit variantのみからなるenumをキーとする、中身が[_; $num_variants]のマップを作れます。

-use maplit::btreemap;
+use enum_map::{enum_map, Enum};
-#[derive(PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Enum)]
 enum Key {
     A,
     B,
     C,
 }
 ​
-let map = btreemap! {
+let map = enum_map! {
     Key::A => 1,
     Key::B => 2,
     Key::C => 3,
 };

enumset

crates.io docs.rs license downloads

enum-mapのset版のようなものです。(作者は別です)

中身はu8, u16, u32, u64, u128のうち全variantが収まる最小のもので表現されるビット列です。

-use maplit::btreeset;
+use enumset::EnumSetType;
-#[derive(PartialEq, Eq, PartialOrd, Ord)]
+#[derive(EnumSetType)]
 enum Value {
     A,
     B,
     C,
 }
 ​
-let set = btreeset!(Value::A, Value::B);
-assert!(set.contains(&Value::A));
-assert!(set.contains(&Value::B));
-assert!(!set.contains(&Value::C));
+let set = Value::A | Value::B;
+assert!(set.contains(Value::A));
+assert!(set.contains(Value::B));
+assert!(!set.contains(Value::C));

phf

crates.io docs.rs license downloads

コンパイル時に生成できるhash mapとhash setです。

-use maplit::hashmap;
-use once_cell::sync::Lazy;
-use std::collections::HashMap;
+use phf::phf_map;
-static MAP: Lazy<HashMap<&str, i32>> = Lazy::new(|| {
-    hashmap!(
-        "foo" => 1,
-        "bar" => 2,
-    )
-});
+static MAP: phf::Map<&str, i32> = phf_map!(
+    "foo" => 1,
+    "bar" => 2,
+);

 assert_eq!(MAP["foo"], 1);
 assert_eq!(MAP["bar"], 2);
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account