関連

範囲(Struct std::ops::Range)は少し変わった演算子....=を使います。..start..endendまで(endは含まない)で、start..=endend以下の範囲を作ります。

assert!(!(0..10).contains(&10));
assert!((0..=10).contains(&10));

Range はIteratorも持っているのでforループで回すことができます。

for i in 0..3 {
  println!("{}", i);
}
// 0
// 1
// 2
関連

Struct を Json など別の構造体へ変換するのに便利なクレートです。以下の2つを使うのでCargo.tomldependenciesなどへ追加します。

[dependencies]
serde = "1.0.104"
serde_json = "1.0.44"

Json

Struct を Json に変換

対象の Struct にはserde::Serializeを持たせます。

#[derive(serde::Serialize)]
struct User {
  name: String,
}

後はインスタンス化したUserserde_json::to_stringさせます。

let user = User {
  name: "nju33".into(),
};

println!("{}", serde_json::to_string(&user).unwrap());
// {"name":"nju33"}

Json (文字列)を Struct に変換

Struct を満たす Json 文字列を用意し、serde_json::from_strへそれを渡します。

let json = r#"{"name":"nju33"}"#;

println!("{:?}", serde_json::from_str::(json).unwrap());
// User { name: "nju33" 

Json 文字列はserde_json::jsonマクロを使うと JavaScript のオブジェクト定義のように書けます。

let json = serde_json::json!({
  "name": "nju33"
});

println!(
  "{:?}",
  serde_json::from_str::(&json.to_string()).unwrap()
);

ファイル内容を読み込む

std::fs::read_to_stringを使います。これはResultを返し、Okな時はファイルの中身、Errな時はstd::io::Errorを返します。

use std::fs;

// `foo.txt`の中身は`foo`
let foo = fs::read_to_string("foo.txt").unwrap();

assert_eq!(foo, "foo");

存在しないファイルパスを渡してしまうとErrを返します。

または、sttd::fs::Filestd::io::BufReaderで読み校方法もあります。BufReader::read_to_stringにはstd::io::Readがスコープに必要な為それも持ってきます。

use std::fs::File;
use std::io::{BufReader, Read};

let file = File::open("foo.txt").unwrap();
let mut buf_reader = BufReader::new(file);
let mut contents = String::new();
buf_reader.read_to_string(&mut contents).unwrap();

assert_eq!(contents, "foo");

ファイルへ内容を書き込む

fs::File::createによって書き込み専用モードで対象のFileを取得し、その::write_allへ書き込みたい文字列を渡します。ですが、::write_allstd::io::Writeが持つメソッドで使うためにはstd::io::Writeの取り込みが必要です。

use std::fs;
use std::io::Write;

let mut file = fs::File::create("foo.txt").unwrap();
file.write_all(b"nju33").unwrap();

let contents = fs::read_to_string("foo.txt").unwrap();

assert_eq!(contents, "nju33");
関連

Cargo.toml[dependencies]name="version"の形で記述すると、その後runしたりbuildする度、変更がある場合はその依存をダウンロードして解決をしてくれます。

例えばserdeというライブラリの場合、バージョンはcrates.io/crates/serdeから見ることができます。2019年12月現在だと最新のバージョンは、1.0.104なので、

[dependencies]
serde = "1.0.104"

のように書きます。

もし依存モジュールをgitやローカルから取得したい場合はインラインテーブルで指定する方法が用意されてます。それぞれgitpathというキーバリューを持つインラインテーブルを置きます。

# `git` から
add_a_module_by_git = { git = "git@..." }

# ローカルから
add_a_module_from_local = { path = "../path/to/module" }

インラインテーブルの中には他にもfeaturesなどのキーバリューを置くことがあります。その場合インラインテーブルでは読み書きしずらくなってくるので以下のように書く方法もあります。

[dependencies.serde]
version = "1.0.104"

ターミナルから依存追加

もしかしたら、上記のようにバージョンを調べてファイルを編集するのは NodeJS などに親しんだユーザーの場合手間に感じるかもしれません。

cargo-editというツールを導入するとcargo addなどのコマンドが追加され、これでyarn addのように依存管理ファイルを編集することができます。

例えばserdeを導入したい場合以下のようなコマンドを叩きます。この場合バージョンを調べなくてもこのツールが自動で最新バージョンで追記してくれます。

cargo add serde

# `dev-dependncies` セクションに追加
cargo add --dev serde
# 以下と同じ
# cargoo add -D serde

結果は以下の通りです。

[dependencies]
serde = "1.0.104"
関連

環境変数を取得するには標準ライブラリのstd::env::varを使います。これは単体の値を取得する為のもので、すべてを取得したい場合はstd::env::varsです。

env::varは引数で渡された名前の環境変数があるかどうかをResultで返します。ある場合はOk<String>、ない場合はErr<VarError>を返します。

例えばこのようなコードがあるとして、

use std::env;

fn main() {
  let var = env::var("VAR").expect("VAR is not defined");

  println!("{}", var);
}

単にcargo runするとエラー(thread 'main' panicked at 'VAR is not defined: NotPresent')になります。($VARが設定されてない前提)
VAR=foo cargo runとちゃんと環境変数を設定して実行するとfooがプリントされます。

dotenv

dotenvというクレートを使うとそのプロジェクトだけの環境変数が使いやすくなります。これはプロジェクトルートに.envというファイルを作り中に環境変数をまとめて書けば、1行実行するだけでそれらを環境変数として扱えるクレートです。

使う前にCargo.tomlの依存に追加が必要です。

[dependencies]
dotenv = "0.15.0"

次に.envを以下のような感じで作ります。

VAR=foo

mainコードを書き換えます。

use std::env;
use dotenv::dotenv;

fn main() {
  dotenv().ok();

  let var = env::var("VAR").expect("VAR is not defined");

  println!("{}", var);
}

dotenv()の行で.envの中身をプログラム中だけ環境変数に追加します。よってこの実行はコンソールにfooと表示します。

ちなみに.envが無くてもdotenv().ok()でエラーは特に起こらないようです。

関連

println! デバッグ

ターミナルに結果を出力して内容が期待するものか確認するデバッグです。これにはprintln!、またはeprintln!マクロを使います。これらはstdoutstderrに出力するかの違いがあります。

println!("foo");
eprintln!("foo");

Trait std::fmt::Display を持っていれば{}を含めることで、変数をその位置に埋め込んで出力できます。

例えばstrDisplayを持っているので以下のように使えます。

println!("hello {}", "world!");
// hello world!

{}が複数ある場合は、後の引数にその数だけDisplayを持つ値を与えてあげる必要があります。

println!(
  "My favorite fruits: {}, {}, {}",
  "apple", "tangerines", "peaches"
);

Displayからfmtを実装すれば適用な Struct インスタンスを{}に渡して表示できるようになります。

struct Foo;

impl std::fmt::Display for Foo {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "Foo")
  }
}

fn main() {
  println!("{}", Foo);
  // Foo
}

Displayに似たトレイトに Trait std::fmt::Debug があります。これは開発者向けの結果を出力する為に使います。

この出力には{}の代わりに{:?}を使います。例えばstrDebugも持っているのでこう書いても大丈夫です。

println!("hello {:?}", "world!");
// hello world!

DebugDisplayのようにfmtを実装すれば、好きなように出力結果を調整できますが、こちらはderive属性からすぐに最低限の形の実装を持たせることができます。

#[derive(Debug)]
struct Foo {
  value: String
}

fn main() {
  println!(
    "foo is {:?}",
    Foo {
      value: "value".into()
    }
  );
}
// foo is Foo { value: "value" }

Struct のプロパティが多いと一行では見づらくなってきますが、そんな時用にプリティプリントが用意されてます。これは変わりに{:#?}と置きます。

println!(
  "foo is {:#?}",
  Foo {
    value: "value".into()
  }
);
// foo is Foo {
//   value: "value",
// }

順番の調整

順番を調整したい時は、{0}または{1:?}のように「0番目の値はここ、1番目の値はここ…」のように指定できます。

println!(
  "{1} {0:?}",
  Foo {
    value: "value".into()
  },
  "###"
)
// ### Foo { value: "value" }

また数値だけではなく名前を付けて「fooはここ、barはここ」ということもできます。それには、{foo:?}{bar}のように中にその名前を置き、引数はfoo = Foo { ... }のような形で渡します。

println!(
  "{foo:?} {bar}",
  foo = Foo {
    value: "value".into()
  },
  bar = "bar"
)
// Foo { value: "value" } bar
関連

テストとなるコードには#[test]属性を付けます。テスト関数の中ではassert!assert_eq!マクロを使って期待する値が得られたかどうかを確認します。

assert!は結果にtrueを、assert_eq!は与えた2つの値が同じ値であることを期待します。

#[test]
fn foo() {
  assert!(true);
  
  // 値が違うので、このテストは失敗する
  assert_eq!(2, 1);
  
}

もし、失敗することが正なのであれば一緒に#[should_panic(expected = 'panic message')]属性を付けます。expectedに指定する値は Panic 時のメッセージを渡しいます。
assert_eq!の Panic はassertion failedと言うメッセージ含んでいる為以下のテストは成功します。

#[test]
#[should_panic(expected = 'assertion failed')
fn foo() {
  assert!(true);
  assert_eq!(2, 1);
}

テストはcargo testまたはcargo test foo::fooで走らせられます。違いは後者はそれだけを走らせられます。

またいつもは実装したくないようなテストの為に#[ignore]という属性も用意されてます。このテストはcargo test -- --ignoredとした時のみ走るので CI などではこのように実行させるといいと思います。

上記のテストの書き方はコンパイルに含まれてしまうので、#[cfg(test)]属性を付けたモジュールに閉じ込めると、テストの時だけコンパイルされるようになります。モジュール名はtestsが良く使われますが何でも大丈夫です。

#[cfg(test)]
mod tests {
  #[test]
  fn do() { ... }
}

このような形でcargo testすると、

running 1 test
test tests::do ... ok

のような形で表示されます。

結合テスト

結合テストはtests/以下に置くのが推奨のようです。テストの書き方はユニットテストと同じです。

関連

コメントには普通のコメントとドキュメントコメントの2つがあります。

普通のコメント

行単位なら//を行頭に置き、ブロック単位なら/* */で囲みます。

// 行単位コメント

/* ブロックレベルな
   コメント */

ドキュメントコメント

ドキュメントコメントには/////!があります。違いは、前者は後に続く要素に対してのコメントに対して、後者はそれらを含むモジュールレベルのコメントになります。また、それぞれのコメントはマークダウン記法でコメントを記述します。

コードブロックの言語はデフォルトでrustが設定されます。もし、それ以外の言語だと知らせたい時は、```javascriptのように設定できます。

//! plus-one module
//!
//! # Example
//!
//! ```
//! let x = 1;
//!
//! assert_eq!(plus_one::plus_one(x), 2);
//! ```

/// plus_one function
///
/// Add 1 to the given value
///
/// # Example
///
/// ```
/// let x = 1;
///
/// assert_eq!(plus_one::plus_one(x), 2);
/// ```
///
pub fn plus_one(num: i32) -> i32 {
  num + 1
}

この結果はcargo doc --openでブラウザで見られます。

コードブロックに置いた Rust なコードは(ライブラリの場合)テスト対象になります。上記のcargo testと実行した時、let xから始まるコードブロックのassert_eq!trueで無ければテストが失敗してしまうので注意が必要です。ちなみに、ドキュメントテストだけを実行したい用にcargo test --docがあります。

// ...

   Doc-tests plus-one

running 1 test
test src/lib.rs - plus_one (line 9) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

コードブロックのコードの中身を順番に説明したいような場合もテストが通るコードが要求される為すべてを記述する必要があります。ですが、毎回全てを記載するのはやや単調かもしれません。

そんな場合はコードブロックの今はいらない行の頭に#を置くことで、その部分は今はいらないことや、ドキュメントページでその部分を隠すことができます。

例えば以下のようなコードブロックの場合、

/// ```
/// let one = 1;
/// # let two = 2;
/// # let three = 3;
/// ```

ドキュメントページで出力されるのは、

let one = 1;

だけになります。

関連

Cargo のworkspaceセクションを使います。このセクションにmembersというキーを追加し、その値にはパッケージ名を文字列にした配列を置きます。例えばそれは以下のようになります。

[workspace]
members = [
  "plus-one",
  "there-is-plus-one-as-dependency"
]

このファイルはルートとなるディレクトリでcargo initを実行し生成されたCargo.tomlを編集するといいと思います。

plus-oneはモジュールで、there-is-plus-one-as-dependencyはそれを使うメイン処理を書く場所という想定です。この想定で例を見てみます。

plus-one モジュール

モジュールも Cargo でひな形が作れます。それはcargo init --lib <workspace-name>で作れます。つまり今回の場合、cargo init --lib plus-oneになります。

実行後ディレクトリにplus-one/と、その中にsrc/lib.rsファイルができてます。中身を書き換えます。

pub fn plus_one(num: i32) -> i32 {
  num + 1
}

#[cfg(test)]
mod tests {
  #[test]
  fn plus_one_works() {
    assert_eq!(34, crate::plus_one(33));
  }
}

plus_one関数を定義しました。これは数値を与えると+1した値を返してくれる関数です。

一応テストも書いています。ワークスペースなプロジェクトではルートディレクトリでcargo testするだけで各ワークスペースでテストを走らせてくれます。

cargo test
# ...
# running 1 test
# test tests::plus_one_works ... ok
# ...

次に上の関数を使うワークスペース作ります。プロジェクトルートディレクトリで、今度はcargo initつまり--lib無しで作ります。

cargo init there-is-plus-one-as-dependency

また同じようにthere-is-plus-one-as-dependency/ができsrc/main.rsが作られたのでこのファイルでplus_one関数を使ってみます。

fn main() {
  println!("{}", plus_one::plus_one(33));
}

上記の通り単にモジュール名から関数を辿って使うだけです。plus_oneから辿っているのは-_に変換される為です。

メイン処理もプロジェクトルートディレクトリからcargo runで使えます。34と出てくれば完璧です。

➜ cargo run
   Compiling there-is-plus-one-as-dependency v0.1.0 (/private/tmp/monorepo/there-is-plus-one-as-dependency)
    Finished dev [unoptimized + debuginfo] target(s) in 0.56s
     Running `target/debug/there-is-plus-one-as-dependency`
34

Cargo のcargo runはビルドと実行を同時に行うためのコマンドです。単にcargo newで作成したプロジェクトでmain.rs(バイナリクレート)の実行はcargo runで行えます。しかし、cargo runだけで実行できるのは、プロジェクトにバイナリクレートが1つの場合だけに限ります。

複数のバイナリクレート

もし、1つのプロジェクトに複数のバイナリクレートを持ちたい時はsrc/bin/以下へ置きます。例えばsrc/bin/foo.rsというファイルを作ったとします。ここからはもうcargo runだけでは動きません。cargo run --bin <binary-crate-name>と名前を指定する必要があります。これは例えばcargo run fooのようになります。
ただし、src/main.rsに関してはcargo run mainではなく、cargo run --bin <package-name>とそのパッケージ名を指定して実行します。

複数の例

例を記述したファイルはexamples/に置きます。(src/examplesではない。)例えばexamples/foo.rsというファイルを作ったとします。このファイルは--example <example-name>で実行できます。

cargo run --example foo

JavaScript で飯食べたい歴約 9 年、 純( nju33 ) によるノートサイトです。

このサイトではドリンク代や奨学金返済の為、広告などを貼らせて頂いてますがご了承ください。

Change Log