変数

変数は主にlet 変数名 = 値;というような文で定義します。

let foo = "foo";
let thirty_three = 33;
let truthy = true;

型は Rust がその場の記注などから自動で付与してくれる為省略できる場合があります。とはいえ型を付けても問題ありません。

let foo: &str = "foo";
let thirty_three: i32 = 33;
let truthy: bool = true;

記注することで型それに合わせてくれる場合があります。以下ではthirty_threeu32型になります。

let thirty_three: u32 = 33;

また例えばStringVec<u8>Box<str>を実装済みの為以下のような事もできます。

let ref_str: Vec<u8> = "foo".to_string().into();
// ref_str は `std::vec::Vec<u8>`
let ref_str: Box<str> = "foo".to_string().into();
// ref_str は `std::boxed::Box<str>`

Rust では結果の型に合わせて::intoのように処理が変わる事が良くあります。

タプルでまとめて定義

タプルは( )で囲みます。関数などから複数の値を同時に返すなどもできます。

let (nju, thirty_three): (&str, i32) = ("nju", 33);

変更可能な変数

letのあとにmutと続けると変数に再代入したり、

let mut value = "foo";
value = "baz";

assert_eq!(value, "baz");

編集できるようになります。

let mut num = 33;
num += 1;

assert_eq!(num, 34);

あとで代入する変数

宣言だけした場合、mutを付けなくてもあとで値を代入できます。

let value;

value = 33;

assert_eq!(value, 33);

使わない変数

下記の状態の時、Rust はfooに対してwarning: unused variable: fooのような警告を発行します。

let foo = "foo";
// 以後 `foo` を使っていない

使わないけど定義したい変数には_を頭に付けるか、_という名前で定義すると許されます。

let _foo = "foo";

関数の引数などにも使えます。

fn do_something(_: &str) {
  // snap
}

借用ルール

所有を借用する時には守らなければならない2つのルールがあります。

  1. 不変な借用であればいくらでも借用できる
  2. 可変な借用がある場合、それ以上借用できない

1つ目は以下のコードのように参照をいくつもの変数に持つ事ができます。

let mut num = 33;
let ref1 = &num;
let ref2 = &num;
let ref3 = &num;

println!("{}, {}, {}", ref1, ref2, ref3);
// 33, 33, 33

2つ目は以下のように&mut Tが既にあるスコープにさらに&Tがあるとエラーになります。

let mut num = 33;
let ref1 = &mut num;
let ref2 = &mut num;

println!("{}, {}", ref1, ref2);
// error[E0499]: cannot borrow `num` as mutable more than once at a time

定数

これにはletの代わりにconstを使います。プログラム全体の中で何度か使われる値をその度定義して使うのは良くないです。

const FOO: &str = "foo";

fn main() {
  println!("{}", FOO);
}

文字列

文字列はダブルクォートで書こんで定義します。これで定義した値の型はプリミティブなstrになります。これは定義時に省略できます。

let str = "foo";
// let str: &'static str = "foo";
// と同等

println!("{}", str); // "foo"

Rust の文字列はそのままで改行を含ませられます。

let str = "aiueo
kakikukeko";

もし文字列にエスケープ文字が含まれ、それをそのまま表示したい場合r" "で囲みます。

let str = r"¯\_(ツ)_/¯";

さらにダブルクォートを含ませたい場合は、r#" "#で囲みます。

let str = r#"<input type"text">"#;

この(UTF-8)文字列のサイズは固定ですが、可変にしたい時用に Struct std::string::String も提供されています。この型へはstrから変換することができます。

let string = str.to_string();
let string2 = String::from(str);

println!("{}, {}", string, string2);

文字列結合

文字列を結合するには最初をString型にします。

let s = "foo".to_string() + "bar" + "baz";

assert_eq!(s, "foobarbaz");

既にStringの場合、後に来るものを参照で使います。

let foo = "foo".to_string();
let bar = "bar".to_string();
let baz = "baz".to_string();
let s = foo + &bar + &baz;

assert_eq!(s, "foobarbaz");

またはformat!マクロを使って好きな位置に適当な変数を埋め込む方法もあります。

let s = format!("{1}{0}{baz}", "bar", "foo", baz = "baz");
assert_eq!(s, "foobarbaz");

元の変数を書き換えるならpush_strpushが使えます。

let mut s = String::new();

s.push_str("foo");
s.push('b');
s.push('a');
s.push('r');

assert_eq!(s, "foobar");

文字数の取得

StringlenはUTF-8でのバイト単位のサイズを返します。

"foo".len(); // 3
"あいうえお".to_string().len(); // 15

"foo"のような1バイト文字だけの場合は文字数と一致したサイズが取得できますが、"あいうえお"のような1文字あたり3バイトで表示されるものの場合は3 x 5 = 15となってしまいます。

2,3,4バイトの文字が含まれている場合でも正確な文字数を取得したい場合は、chars().count()を使います。これはstrStringどちらでも使えます。

"foo".chars().count(); // 3
"あいうえお".to_string().chars().count(); // 5

空文字列かどうかはis_emptyで調べることができます。

"".is_empty(); // true

文字

ダブルクォオート"ではなくシングルクォート'で囲むと Primitive Type char を作れます。

let a = 'a';
let b: char = 'b';

文字が何バイトか調べる

::len_utf8を使います。

assert_eq!('純'.len_utf8(), 3);

文字が大文字か小文字か調べる

それぞれ::is_uppercase::is_lowercaseで調べられます。

assert!('A'.is_uppercase());
assert!('a'.is_lowercase());

数値

数値のプリミティブ型は以下のものがあります。

f32f64以外はmin_valuemax_valueメソッドにより取得・確認できます。

(i8::min_value(), i8::max_value())
// (-128, 127) (-2^7, 2^7 - 1)
(i16::min_value(), i16::max_value())
// (-32768, 32767) (-2^15, 2^15 - 1)
(i32::min_value(), i32::max_value())
// (-2147483648, 2147483647) 
(i64::min_value(), i64::max_value()) 
// (-9223372036854775808, 9223372036854775807)
(i128::min_value(), i128::max_value())
// (-170141183460469231731687303715884105728, 170141183460469231731687303715884105727)
(u8::min_value(), u8::max_value())
// (0, 255) (0, 2^8 - 1)
(u16::min_value(), u16::max_value())
// (0, 65535) (0, 2^16 - 1)
(u32::min_value(), u32::max_value())
// (0, 4294967295) 
(u64::min_value(), u64::max_value())
// (0, 18446744073709551615) 
(u128::min_value(), u128::max_value())
// (0, 340282366920938463463374607431768211455)
(isize::min_value(), isize::max_value())
// (-9223372036854775808, 9223372036854775807)
(usize::min_value(), usize::max_value())
// (0, 18446744073709551615)

f32f64はそれぞれi32i64の範囲内で小数点有りの数値が持てます。

宣言

変数宣言時に希望する型を指定するか、型名を数値の後ろに付けることで希望する型の値が得られます。デフォルトは整数の場合i32、少数以下が有る場合f32になります。

let a: i8 = 1; // i8
let b = 12i16; // i16
let c = 123; // i32

値を足す

Trait std::ops::Addが上記の型すべてに実装されているので、それぞれで+が使えます。

10 + 20 // 30i32
4.5 + 5.5 // 10.0f32

値を順番に足していきたいような場合、Trait std::ops::AddAssignによる+=が使えます。

let mut num = 1;
num += 2;
assert_eq!(num, 3);

値を引く

Trait std::ops::Subが上記の型すべてに実装されているので、それぞれで-が使えます。

30 - 10 // 20i32
10.0 - 4.5 // 5.5f32

値を順番に引きていきたいような場合Trait std::ops::SubAssignによる-=が使えます。

let mut num = 3;
num -= 2;
assert_eq!(num, 1);

値を掛ける

これまでのようにTrait std::ops::Mulにより*、またTrait std::ops::MulAssignによる*=が実装されています。

2 * 3 // 6i32
2.0 * 3.0 // 6.0f32
let mut num = 2;
num *= 3;
assert_eq!(num, 6);

値を割る

これまでのようにTrait std::ops::Divにより/、またTrait std::ops::DivAssignによる/=が実装されています。

値が割り切れなかった場合、f32f64以外では小数点以下は切り捨てされます。

5 / 4 // 1i32
5.0 / 4.0 // 1.25.0f32
let mut num = 6.0;
num /= 4.0;
assert_eq!(num, 1.5f32);

真偽値

他の言語と同じようにtruefalseが用意されています。これはif構文により条件によって処理を分岐できます。

if (true) { 1 } else { 2 } // 1
if (false) { 1 } else { 2 } // 2

値の反転

boolean型の前に!を付けると反対の意味になります。

!true // false
!false // true

Option

Option<T>は値があるないかを表現する為の型です。ある場合はSome<T>、ない場合はNoneになります。

let option_thirty_three = Some(33);
let none = None as Option<i32>;

Optionな値は基本中身を取り出してから使います。そのⅠつが::unwrapです。これは値がSome<T>の時Tを取り出してくれます。

assert_eq!(Some(33).unwrap(), 33);

ただし、これは値が入ってるので大丈夫ですがNoneに対して呼んでしまうと Panic を起こしてしまいます。

assert_eq!((None as Option<i32>).unwrap(), 33);
// thread 'main' panicked at 'called `Option::unwrap()`
// on a `None` value'

Noneの時はデフォルト値を返したいなどの場合は、::unwrap_orを使います。

assert_eq!((None as Option<i32>).unwrap_or(66), 66);

もしくは Panic のままエラー文章だけ分かりやすくしたい場合は、::expectを使います。

assert_eq!((None as Option<i32>).expect("a value isn't equal 33"), 33);
// thread 'main' panicked at 'a value isn't equal 33'

値の加工

Optionのまま値を更新できるのが::mapです。

assert_eq!(
  Some(33).map(|mut thirty_three| {
    thirty_three += 1;
    thirty_three
  }),
  Some(34)
);

Result

Result<T, E>は処理が無事完了したかを表現する為の型です。正常に終了した場合Ok<T>、何かしらエラーが起きた場合Err<E>を返します。

let ok: Result<i32, &str> = Ok(33);
let err: Result<i32, &str> = Err("fatal error");

Result<T, E>はモジュールなどに合わせてエイリアス化されている事が多いです。エイリアスにはtypeを使います。

enum FooError {
  AError,
  BError,
}

type FooResult<T> = Result<T, FooError>;

? 演算子

Resultを返す関数呼び出しの後ろに?を置くと、Ok<T>の時は::unwrapのようにTを返しますが、Err<E>が返却された時は即時returnになります。

fn return_ok<'a>() -> Result<&'a str, &'a str> {
  Ok("foo")
}

fn scope<'a>() -> Result<&'a str, &'a str> {
  let foo = return_ok()?;

  println!("{}", foo);

  Ok(foo)
}

fn main() {
  #[allow(unused_must_use)]
  scope();
}

return_okResult型を返す為だけの関数で、scopeは単にそれを実行する為の関数です。scopeを用意した理由は、?は戻り値がResultな関数の中でしか使えません。

また?Err<E>な時、単にそれを返すのではなくFrom::fromEを渡して実行した結果を返します。なので以下のようにエラー列挙型にまとめるといった事ができます。

struct FooError;
struct BarError;

#[derive(Debug, PartialEq, Eq)]
enum AError<'a> {
  FooError(&'a str),
  BarError(&'a str),
}

impl<'a> From<FooError> for AError<'a> {
  fn from(_error: FooError) -> Self {
    AError::FooError("FooError")
  }
}

impl<'a> From<BarError> for AError<'a> {
  fn from(_error: BarError) -> Self {
    AError::BarError("BarError")
  }
}

fn return_err<'a>() -> Result<&'a str, FooError> {
  Err(FooError)
}

fn scope<'a>() -> Result<&'a str, AError<'a>> {
  let _ = return_err()?;

  unreachable!();
}

fn main() {
  assert_eq!(scope().unwrap_err(), AError::FooError("FooError"));
}

return_errは必ずエラーを返す関数です。impl<'a> From<FooError> for AError<'a>によってFooErrorからAErrorに変換できるようにしてる為、この場合AError::FooErrorEとして返ります。

パターンマッチ

パターンマッチには複数の真マッチ条件を持つmatchと1つの真マッチ条件を持つif letがあります。

複数の条件分岐

match構文を使うと多くのケースに対応できます。

単純にイコールならその行の=>より右のブロックや式が実行されます。

match 1 {
  1 => println!("foo"),
  2 => println!("bar"),
  _ => println!("baz"),
}
// foo

ちなみに、_行はどれにもマッチしなかった場合に実行されます

match 10 {
  1 => println!("foo"),
  2 => println!("bar"),
  _ => println!("baz"),
}
// baz

値も返せます。

let val = match 1 {
  1 => "foo",
  2 => "bar",
  _ => "baz",
};
assert_eq1(val, "foo");

matchでは必ず条件のどれかとマッチしなければなりません。どれともマッチしない可能性が有る場合エラーとなります。

match 10 {
  1 => println!("foo"),
}
// non-exhaustive patterns: `_` not covere

matchでは完全なイコールだけでなく、同じ形ならマッチしてくれる点が強力です。例えばOption<T>は値があればSome<T>、無ければNoneとなる型です。この型な値Some(33)matchに渡した時、パターンにSome(num)と書けばそれにマッチしてくれます。そして、なんとそのnumlet num = 33のように変数束縛されているので、その後の処理で使うことができます!

match Some(33) {
  Some(num) => println!("Some({})", num),
  None => println!("None"),
}

以下はNoneにマッチです。

match None as Option<i32> {
  Some(num) => println!("Some({})", num),
  None => println!("None"),
}

パターンの後にifと条件を書くことで、さらにその条件がtrueの時にだけ走らせられます。

let x: i32 = 33;

match Some(x) {
  Some(num) if num == 33 => println!("the x equals 33"),
  _ => println!("None"),
}
// "the x equals 33"

パターンは|で区切ることで複数指定できます。

let x: i32 = 33;
let value = AB::B { value: x };

match value {
  AB::A(num) | AB::B { value: num } if num == 33 => println!("the x equals 33"),
  _ => println!("None"),
}
// "the x equals 33"

1つのの条件分岐

以下はSome(33)Some(num)とマッチします。if letでもマッチした時だけその後のブロック処理を走らせることができます。ちなみに、このブロックの戻り値は()である必要があります。

if let Some(num) = Some(33) {
  println!("it has walked along truthy with {}", num);
}
// it has walked along truhy with 33

もし、マッチしなかった場合の為にelseブロックも用意されてます。

if let Some(num) = None as Option<()> {
  unreachable!();
} else {
  println!("it has walked along falsy");
}
// it has walked along falsy

配列とスライス

配列arrayは固定サイズで所有権を持つ値で、スライスはその参照です。

配列は[ ]に囲む形で定義しますが、中身の書き方には2つの方法があります。1つ目は,で区切って好きな値を列挙する方法、2つ目はある値をまず置き、その後;で区切った後リピート回数を置く方法です。これは例えば[0; 3]と書くと[0, 0, 0]になることを意味します。
後者の場合、基準となる値(前の例で言う0)はCopyできる必要があります。

{
  let array = [1, 2, 3];
  let _slice = &array;
}

{
  let array = [0; 3];
  let _slice = &array;
}

for で使う

IteratorまたはIntoIteratorを持つ要素にする必要があります。array::iterを持っている為これを使うか、arrayは&'a [T; N]&'a mut [T; N]に対してIntoIteratorを実装しているのでスライス化すればfor構文で回せるようになります。

for i in [1, 2, 3].iter() {
  println!("{}", i);
  // `1..=3`まで表示
}

for i in &[1, 2, 3] {
  println!("{}", i);
  // `1..=3`まで表示
}

ベクター

ベクター(Struct std::vec::Vec)を楽に構築するvec!マクロにより拡張可能なコレクションを定義できます。

[ ]で囲むということは共通ルールですが、これには2つの構文があり1つは,で区切って値を定義する方法です。

let v = vec![1, 2, 3];
// [1, 2, 3]

2つはすべての値が同じ時に便利で共通値; サイズのように記述します。例えば、1を3つ持ったベクターを定義したい場合はこうなります。

let v = vec![1; 3];
// [1, 1, 1]

尾に値を追加

pushメソッドを使えます。

let mut v = vec![1, 2];
v.push(3);

assert_eq!(v, vec![1, 2, 3]);

インデックス値を取得

最後の値を取得したい場合popメソッドが使えます。戻り値はVec<T>であればOption<T>になります。

let mut v = vec![1, 2, 3];
let option_i32 = v.pop();

assert_eq!(option_i32, Some(3i32));

好きなインデックス値を削除したいならremoveメソッドが使えます。このメソッドの戻り値はその位置にあった値です。もし、インデックス値がベクターサイズより大きい値の場合panic!されます。

let mut v = vec![1, 2, 3];
let removed_i32 = v.remove(0);

assert_eq!(removed_i32, 1i32);
assert_eq!(v, vec![2, 3]);

// v.remove(5); は `panic!`

サイズを取得

lenメソッドを使います。

let v = vec![1; 3];
v.len() // 3

空がどうかを確認したいだけならis_empty(&self) -> boolが便利です。

型列挙

型列挙型はenumで定義します。使う時はそのいくつかの型から1つを使いたいような場合に使います。例えばそれはエラー型をまとめたものなどです。

以下にいくつかの Struct があります。

struct Foo;
struct Bar;
struct Baz;

これに対してenumValueを定義します。

enum Value {
  Foo,
  Bar,
  Baz,
}

属する型はValueとして束縛することができるようになります。

let _foo: Value = Value::Foo;
let _bar: Value = Value::Bar;
let _baz: Value = Value::Baz

また、enumも Struct のようにいくつかのトレイトをderiveにより実装することができます。

#[derive(Debug)]
enum Value {
  // snap
}

// snap

println!("{:?}", _foo);

match による現在の値の判定

上記のようにenumで束縛ができる為、関数などに渡した時にそのenumが実際にはどれか分からないという問題があります。それにはmatchによる条件分岐で処理を分けることで解決できます。

fn is_foo(value: &Value) -> bool {
  match value {
    Value::Foo => true,
    _ => false,
  }
}

assert!(is_foo(&_foo));
assert!(!is_foo(&_bar));

ループ

繰り返し処理にはloopwhileforがあります。

loop

loopではブロックに書かれた処理を無限に繰り返すことができます。ただし、breakにより繰り返しを終了できます。

let mut i = 1;
loop {
  println!("{}", i);
  // `1..=5` まで表示される

  // `i` が `5`なら繰り返しを終了する
  if i == 5 {
    break;
  }

  i += 1;
}

while

while 条件は条件がtrueの間処理を繰り返せます。上記のloopのコードはwhileに書き直せます。

let mut i = 1;
while i <= 5 {
  println!("{}", i);
  // `1..=5` まで表示される

  i += 1;
}

for

forはイテレーターの各値と共にブロック内の処理を繰り返します。

for val in 1..=5 {
  println!("{}", val);
  // `1..=5` まで表示される
}

インデックス値もほしい場合は::enumerateを使います。

for (i, val) in (1..=5).enumerate() {
  println!("{}:{}", i, val);
  // 以下が表示
  // 0:1
  // 1:2
  // 2:3
  // 3:4
  // 4:5
}

関数

関数を定義するにはfnキーワードを使います。構文はfn function_name<T>(arg1: T, arg2: T) -> T { ... }のような形です。戻り値の型が()の場合はそれを省略できます。

戻り値には;セミコロンを付けずに式のままにしておきます。もし、途中で早期リターンさせたい場合の為にreturn 式;も用意されてます。

fn plus(left: i32, right: i32) -> i32 {
  left + right
}

assert_eq!(plus(1, 2), 3);

<T>のようなジェネリックを指定することで、例えばTrait std::ops::Addが実装されているすべてでは無い複数の型に制限する事も可能です。

fn plus<T: std::ops::Add>(left: T, right: T) -> T::Output {
  left + right
}

assert_eq!(plus(1, 2), 3);
assert_eq!(plus(1.2f32, 2.8f32), 4.0f32);

もしジェネリック部分が長くなってしまうならwhereの後ろに置くこともできます。

fn foo<T>(v: &T)
where
  T: ?Sized + core::fmt::Debug,
{
  println!("{:?}", v);
}

?Sized

?Sizeはサイズ不定型と呼ばれる、実行時にどれくらいメモリを使うか分からないものの事です。基本的にこれはSized + 参照どちらも渡せる意味になります。

fn foo<T>(v: &T)
where
  T: ?Sized + core::fmt::Debug,
{
  println!("{:?}", v);
}

foo("str");
// "str"

foo(&"str".to_string());
// "str"

foo(&[1, 2, 3]);
// [1, 2, 3]

foo(&vec![0; 10]);
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

foo(&A {
  value: "value".into(),
});
// A { value: "value" }

クロージャー

以下のように変数に直接関数のようなものを代入することができます。ブロック{ }を使わない場合戻り値の型は書けません。

let plus_one = |n: i32| n + 1;
//
// 以下のようなもの
//
// fn plus_one(n: i32) -> i32 {
//  n + 1
// }

2行以上になる場合はブロック{ }で囲みます。こちらは戻り値の型を書けますが省略可能です。

let multiply_three_plus_one = |n: i32| -> i32 {
  let mut num = n * 3;
  num += 1;
  num
};

assert_eq!(multiply_three_plus_one(2), 7);

クロージャーで注意なのが現在のスコープに存在する値を暗黙的に借用か所有権を移動させる事です。

let str = String::from("foo");

let _add_bar = || str + "baz";

println!("{}", str); // error

Struct std::string::Stringは、Copyトレイトを実装していない為その所有権はクロージャーの中のstrに渡されます。それにより元々のstrは使えなくなってしまうので、println!でのstr仕様はエラーとなってしまいます。

Copyトレイトを実装していれば値はコピーされて使われるので上記のようなエラーは起きません。Copyトレイとは例えばi32などが実装しています。

let one = 1;

let _plus_one = || one + 1;

println!("{}", one); // 1

mutなクロージャーは変更可能な参照(&mut)を束縛します。

let mut num = 5;

{
    let mut add_num = |x: i32| num += x;
    add_num(5);
}

println!("{}", num); // 10

必ずコピーした所有権を渡したい場合moveキーワードをクロージャーの前に付けます。


let mut num = 5;

{
    let mut add_num = move |x: i32| {
        num += x;

        println!("{}", num); // 10
    };

    add_num(5);
}

println!("{}", num); // 5

参照の場合も大丈夫です。

let one_two_three = &[1, 2, 3];

let _push_four = || {
    let mut v: Vec<i32> = Vec::new();

    v.extend(one_two_three);
    v.push(4);

    v
};

println!("{:?}", one_two_three);

ライフタイム

その変数が属してるスコープの事です。Rust では使用期限が過ぎた変数を使えない様に厳しく確認されます。

use std::borrow::Borrow;

fn plus<'a, 'b, T>(a: &'a T, b: &'b T) -> i32
where
  T: Borrow<i32>,
{
  a.borrow() + b.borrow()
}

fn main() {
  {
    // 'a
    let thirty_three: i32 = 33;

    {
      // 'b
      let one: i32 = 1;
      assert_eq!(plus(&thirty_three, &one), 34);
    }
  }
}

main処理の最初のブロックスコープには'a、その中の2つ目のブロックスコープには'bという名前が付いているとします。ここで変数thirty_threeのライフタイムは'aoneのライフタイムは'bまはた'aと考える事ができます。

Rust の関数ではジェネリック型を置く場所にライフタイムも置くことができます。関数plunでは'aに属するTの参照と、'bに属するTの参照を引数に期待するということになります。また上記の説明の通りライフタイムは'aだけでも動きます。

fn plus<'a, T>(a: &'a T, b: &'a T) -> i32
where
  T: Borrow<i32>,
{
  a.borrow() + b.borrow()
}

実はライフタイムが1つしかなかったり、引数の値ぞれぞれが別のライフタイムを持つのでいい場合ライフライムは省略できます。

fn plus<T>(a: &T, b: &T) -> i32
where
  T: Borrow<i32>,
{
  a.borrow() + b.borrow()
}

これはfn plus<'a, 'b, T>(a: &'a T, b: &'b T) -> i32と展開されます。

先程'bで定義した変数は'b'aどちらにも属すると考えれると書きましたが、ライフタイムは'bで定義した変数の参照を'aなライフタイムの変数に入れることはできません。以下はエラーになります。

{
  // 'a
  let a = Foo { value: 33 };
  let b;

  {
    // 'b
    let c = Foo { value: 1 };

    b = &c;
    // error: `c` does not live long enough
  }
}

これは'bを抜けた後にbが終わった変数cの参照を持つことになってしまう為です。関数ではさらに子ブロックスコープに囲まれて生存期間がハッキリする為エラーにはなりません。

トレイト

トレイトはコードを(自分やチーム的に)分かりやすい単位で再利用する為の仕組みです。例えばあるトレイトIsはこうです。

trait Is {
  fn is_true(&self) -> bool;

  fn is_false(&self) -> bool {
    self.is_true()
  };
}

これは::is_true::is_falseメソッドに関する情報を保ってます。特に::is_false::is_trueと真逆を期待するメソッドになので、この段階で中身を実装できます。

今、以下のような Struct ABがあるとします。

struct A(i32);

struct B {
  value: i32,
}

もし、この2つがIsの機能がほしい時は以下のように実装します。まずA

impl Is for A {
  fn is_true(&self) -> bool {
    self.0 > 0
  }
}

次にB

impl Is for B {
  fn is_true(&self) -> bool {
    self.value > 0
  }
}

どちらも::is_falseメソッドを実装してませんが、トレイト定義時に実装済のものは省略できるルールがある為です。以下を実行しちゃんと実装されてる事が確認できます。

let a = A(33);

assert_eq!(a.is_true(), true);
assert_eq!(a.is_false(), true);

let b = B { value: 33 };

assert_eq!(b.is_true(), true);
assert_eq!(b.is_false(), true);

このようにIsを実装した Struct には、その中の各メソッドが使える状態なことが分かりました。

ジェネリック

例えば以下の関数があるとします。これはAの参照を受け、その::is_trueの結果を返すだけの関数です。

fn is_true_for_a(s: &A) -> bool {
  s.is_true()
}

assert_eq!(is_true_for_a(&a), true);

見てきたように::is_trueを実装してるのはAだけではありません。Bにも欲しくなりました。

fn is_true_for_b(s: &B) -> bool {
  s.is_true()
}

assert_eq!(is_true_for_b(&b), true);

もし、同じくIsCがあるならまた繰り返し…でもいいですが、ジェネリックという仕組みを使うと1回で済ませられます。

fn is_true<T: Is>(s: &T) -> bool {
  s.is_true()
}

assert_eq!(is_true(&a), true);
assert_eq!(is_true(&b), true);

関数is_trueが期待するsIsを実装済みのものに限るという意味になる為です。

ちなみにこれはコンパイル時に以下のような形になるようです。

fn is_true_for_a(s: &A) -> bool {
  s.is_true()
}

fn is_true_for_b(s: &B) -> bool {
  s.is_true()
}

ジェネリック前に戻りました!

is_true_for_aのような書き換えられた関数を Specialized Function (特殊化された関数)、関数が複数になる事を code bloat (コードの膨張)、そして実行時に全ての扱う値が分かってる状態で行われるこの仕組みを Static Dispatch (静的ディスパッチ)と呼ぶようです。

トレイトオブジェクト

オブジェクト安全なトレイトです。これは

  • &selfを受け取る
  • 戻り値をSelfにしない

トレイトです。戻り値をSelfにしないのはSelfが本来何なのかが分からない為です。

例えば以下のAがトレイトオブジェエクトになります。

struct Foo;

trait A {
  fn get_num(&self) -> i32 {
    33
  }
}

impl A for Foo {}

let foo: Box<dyn A> = Box::new(Foo);
foo.get_num();

impl Trait 型

型としてimpl Traitを使うと「そのトレイトを持っている値ならなんでも」入れる事ができます。以下はstri32はどちらもDisplayを持っているのでimpl Displayに入れられます。

use std::fmt::Display;

let nju: impl Display = "nju";
let thirty_three: impl Display = 33;

assert_eq!(format!("{}{}", nju, thirty_three), "nju33")

ファットポインタ

トレイトオブジェクトはファットポインタです。これは参照な型の時に普通の方に比べて使用するメモリが2倍になっています。実際に2倍かどうかはstd::mem::size_ofで確認できます。

use std::mem::size_of;

println!("{}", size_of::<&Foo>()); // 8
println!("{}", size_of::<&A>());   // 16

&Aの場合data&selfに当たるもの)とvtableと呼ばれるトレイトが持つ関数をまとめたものへのポインタの2つの参照を保持してる為です。

Box

Boxはヒープに割り当てられるポインタの所有権を持った物です。Box<T>として実装され、::new(x: T) -> Box<T>に渡されたxの型がTに入ります。

let box_str = Box::new("str");

"str"の型は&strなので、box_strの型は``std::boxed::Box<&str>`になります。

また、impl<T: ?Sized> Deref for Box<T>が実装されている為、Tが参照の場合デリファレンスでTを触れます。

let string: String = (*box_str).to_string();

*box_strがデリファレンスです。ここでstd::boxed::Box<&str>から&strに変換される為、strが持つ::to_string()を実行できます。

加えてBoximpl<T: ?Sized> Drop for Box<T>も実装されてます。これでスコープを抜ける時に自動でヒープ上のメモリを開放してくれる::dropを実装してます。

// snap
{
  let a_box = Box::new("a");
  // drop `a_box`
}
// snap

トレイトオブジェクトのラップ

Box<dyn Trait>とすることでトレイとオブジェクトな型の値を作成できます。

struct A(i32);

impl Is for A {
  fn is_true(&self) -> bool {
    self.0 > 0
  }
}

fn main() {
  let a: Box<dyn Is> = Box::new(A(33));
  println!("{}", a.is_true());
}

AsRef と Borrow

例えばAsRef<T>トレイトを実装済のUがある時、それから&Tを得られます。

このトレイトを実装している 1つにStruct std::path::Path::newメソッドがあります。定義は以下の通り。

impl Path {
  pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path {
    // snap
  }
}

ちなみに OsStringOsStr はプラットフォーム毎の固有の文字を安全に扱う為の型のようです。

戻って、::newAsRef<OsStr>を持ったサイズ不定型sを受け取ります。

ここでTrait std::convert::AsRef を見ると以下のような箇所があります。

impl AsRef<OsStr> for str
impl AsRef<OsStr> for String

よって&str&StringなどはAsRef<OsStr> + ?Sizedを満たすのでこのような書き方で動きます。

use std::path::Path;

let str = "foo";

Path::new(str);
Path::new(&str.to_string());

似たものにBorrowがあります。AsRefのようにUから&Tを取得できるトレイトですが、得たTに関してのHashEqOrdの結果が変換前と同じ結果になることが要求されます。

(この項目は違いがあまり違いが理解できてません🙇Borrowはブランケット実装によってすべてのTで自分を借りれる::borrowを持っているので、「1番渡してほしいのはTだけどUでもいいよ、だけどUは上記に対してTと同じ結果を返すものにしてね」という場合はBorrow、「手間だからTになれるUなら何でもいいよ」な場合はAsRefかな的な🤔)

From と Into

FromIntoは関連性のある型です。

From

From<T>は、struct Uに対して実装するとUTへ変換できるようになります。例えばStringが持つ::fromによってStringな値を作るのは親しみのある例です。これはimpl<'_> From<&'_ str> for Stringのように定義されています。

String::from("str");

上記は、結果どうなるかさえ分かれば以下のようにも書き換えられます。

let _s: String = From::from("str");

Into

From<T>が実装される時、自動でInto<U>が実装されます。

impl<T, U> Into<U> for T
where
  U: From<T>, 

これは少し分かりづらいのでT&'_ strに置き換えてみます。

impl<'_> Into<From<&'_ str>> for &'_ str  

これは「&'_ strからFrom<&'_ str>を実装してる型に変換できるよ」という意味になります。

&strに対して::intoを呼んでみます。

let _s: String = "str".into();

その時メソッドの中はこのような形で実行されます。

let _s: String = From::<&str>::from("str");

上記で見たようにimpl<'_> From<&'_ str> for Stringが実装されてるのでStringへ変換されます。

PartialEq と Eq

これらは同値関係を定義付けします。

PartialEq<Rhs>は、演算子==::eq)と!=::ne)を実装してます。数値(i32)などで==によって同じ値か確認できるのはこのトレイトをi32が持っているからです。

assert!(33_i32 == 33_i32);

このトレイトは以下が成立することを保証します。

  • 対称関係: a == b, b == a
  • 推移関係: a == b, b == c, a == c

Eqは、上記に加えて、

  • 反射関係: a == a

を保証します。

EqPartialEqをスーパートレイトとして持ちますが、中身は空で単にその要素が反射関係を満たすかどうかを定義付ける意味で対象に持たせます。

これらはderiveにより簡単に持たせることができますが、Eqを持たせるには必ずPartialEqも持っている必要があります。

例えばPartialEqは有るが、Eqは無いものの1つにf32があります。これはf32にはstd::f32::NANが含まれますが、これは同じ物同士の比較でもtrueになりません。

let nan: f32 = std::f32::NAN;
assert!(nan == nan); // panic!

PartialOrd と Ord

どちらお大小を表現する為の型です。

std::cmp::PartialOrd

  • >=::ge
  • >::gt
  • <::lt
  • <=::le

でそれぞれの挙動を実装します。対象の Struct はstd::cmp::PartialEqをスーパートレイトに持つ為、その内容も持っている必要があります。

#[derive(PartialEq, PartialOrd)]
struct Foo;

PartialOrdは多分比較できる(比較できない場合がある)場合に使います。例えばf32NAN同士はうまく比較できません。

std::f32::NAN >= std::f32::NAN
// false

std::cmp::Ord

こちらは必ず比較できる場合に使います。i32にはそういった特殊な値はない為、これを持つことができます。

このトレイトはPartialOrdをスーパートレイトとして持ちEqも要求される為、もし実装する場合は以下のようになります。

#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Foo;

Struct

構造体を定義する為に使います。例えば長方形Rectangleは以下のように定義できます。

struct Rectangle {
  width: u32,
  height: u32,
}

このインスタンスを作る時はこのようにします。

let rect = Rectangle {
  width: 100,
  height: 50,
}

デバッグ

デフォルトで自分で作成した Struct はprintln!などで表示させる仕組みが実装されていません。もしそういう仕組みがほしい場合は#[derive(Debug)]を Struct 定義の上の行に書くことで最低現の Debug 機能を取り込んでくれます。

Rectangleの定義箇所を以下に変更します。

#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}

これでprintln!の中で{:?}を置くことにより表示できるようになりました。

println!(
  "{:?}",
  Rectangle {
    width: 100,
    height: 50
  }
)
// Rectangle { width: 100, height: 50 }

ちなみに#[derive]していないとerror[E0277]: \main::Rectangle` doesn't implement std::fmt::Debugというエラーになります。

デフォルト値を設定

Trait std::default::Default を実装することで::defaultによりデフォルト値でインスタンス化できます。Struct の各プロパティがDefaultを持っているなら単に#[derive(Default)]とするだけで実装できます。

#[derive(Default, Debug)]
struct SomeOptions {
  string: String,
  i32: i32,
  f32: f32,
  vec_i32: Vec<i32>,
  cell: std::cell::Cell<i32>,
}

println!("{:?}", SomeOptions::default());
// SomeOptions { string: "", i32: 0, f32: 0.0, vec_i32: [], cell: Cell { value: 0 } 

自分で実装する場合はimpl Defaultから::defaultを実装してあげます。

#[derive(Debug, PartialEq)]
struct MyNumber(i32);

impl Default for MyNumber {
  fn default() -> Self {
    MyNumber(33)
  }
}

assert_eq!(MyNumber::default(), MyNumber(33));

Deref

Derefは Rust が提供する暗黙的な型変換を行う特殊なトレイトの1つです。このトレイトは::derefの実装を要求し、これにより参照外し演算子*を使った時に適当な戻り値になるように上書きできます。

参照外し演算子による型変換

Box<T>Derefにより*を使った場合Tに型変換されるようになってます。

let box_str: Box<&str> = Box::new("str");

assert_eq!(*box_str.to_string(), "str".to_string());

参照による型変換

Deref<Target=T>を実装するUがある時、&Uが自動的に&Tに型強制されます。

これもBox<T>Deref<Target=T>を実装済なので、参照化するだけでTに型強制されます。

let box_str: Box<&str> = Box::new("str");

assert_eq!((&box_str).to_string(), "str".to_string());

メソッド呼び出しによる型変換

先程から&str型に変換してから::to_stringとしていますが、メソッド呼び出し時に自動で::derefによる型変換されるというルールがあるので、Box<str>型のまま::to_stringなどstr型のメソッドを呼び出せます。

let box_str: Box<&str> = Box::new("str");

assert_eq!(box_str.to_string(), "str".to_string());

再帰的な型変換

Derefは要件を満たせるまで再帰的::derefで型を遡ります。例えばメソッド呼び出しの場合、そのメソッドを持つ型になるまで再帰的に型変換されます。

確認の為に、以下のような定義済の Struct や Trait があるスコープがあるとします。

まず、DerefParent<T>child: Tを持ち、::derefにより単にそれを返します。次にDerefChild<T>value: Tを持ち、こちらも::derefによりそれを返します。

use std::ops::Deref;

struct DerefParent<T> {
  child: T,
}

struct DerefChild<T> {
  value: T,
}

impl<T> Deref for DerefParent<T> {
  type Target = T;

  fn deref(&self) -> &T {
    &self.child
  }
}

impl<T> Deref for DerefChild<T> {
  type Target = T;

  fn deref(&self) -> &T {
    &self.value
  }
}

以下のように使ってみて変数に割り当てます。

let deref_parent: DerefParent<DerefChild<String>> = DerefParent {
  child: DerefChild {
    value: "aiueo".to_string(),
  },
};

この時deref_parentstr::repeatを呼び出せます。

assert_eq!(deref_parent.replace("u", "*"), "ai*eo");

これは以下のように型変換される為です。

  1. DerefParent<DerefChild<String>からDerefChild<String>
  2. DerefChild<String>からString
  3. Stringからstr

DerefMut

::deref後の値を更新できるかはこのトレイトも持っているかどうかで決まりいます。このトレイトはDerefをスーパートレイトを持ちます。

例えばBoxDerefMutを持つので、ミュータブルな変数束縛からも問題無く::derefできます。

let mut value: Box<i32> = Box::new(33);

*value += 1;

assert_eq!(*value, 34);

Rc

名前は、Reference Counted (参照カウント)の略。

Rc<T>はシングルスレッド用のカウンタを持つポインターでヒープ上で管理されます。T値に対する複数の所有権を取得できます。

::cloneで所有権の共有と参照カウンタの加算、::strong_countで今の参照数を取得できます。

let thirty_three: Rc<i32> = Rc::new(33);

{
  let cloned = thirty_three.clone();

  // `thirty_three` と `cloned` は同じ
  assert!(thirty_three.eq(&cloned));

  assert_eq!(Rc::strong_count(&thirty_three), 2);

  // スコープを抜けるので参照カウンタを -1 する
}

assert_eq!(Rc::strong_count(&thirty_three), 1);

Tに変換できるDerefを実装してる為、Boxと同じようにTの持つメソッドはそのまま実行できます。

assert_eq!(thirty_three.wrapping_add(1), 34);

Arc

名前は、Atomically Reference Counted (原子参照カウント)の略。

Arc<T>Rc<T>のマルチスレッドでも使える版です。型Atomicを操作する為R<T>よりもオーバーヘッドが高いです。またTTrait std::marker::SendTrait std::marker::Sync を持っている必要があります。

use std::sync::Arc;
use std::thread;

let thirty_three: Arc<Vec<i32>> = Arc::new(vec![1, 2, 3]);

for _ in 0..3 {
  let ref_thirty_three = thirty_three.clone();
  let handler = thread::spawn(move || {
    let repeated = ref_thirty_three.repeat(2);

    assert_eq!(repeated, vec![1, 2, 3, 1, 2, 3]);
  });

  handler.join().unwrap();
}

Cell

Cell<T>は型はミュータブルでもイミュータブルでもT値を変更可能な形で使うことができます。シングルスレッドでのみ使えます。

Cellで値の取得と更新はそれぞれ::get::setを使います。以下ではcell_i32はイミュータブルでの定義ですが、それらメソッドを使って値の更新ができています。

use std::cell::Cell;

let cell_i32: Cell<i32> = Cell::new(33);
let ref_cell_i32 = &cell_i32;

cell_i32.set(cell_i32.get() + 1);
ref_cell_i32.set(ref_cell_i32.get() + 1);
{
  let ref_cell_i32_in_scope = &cell_i32;
  ref_cell_i32_in_scope.set(ref_cell_i32_in_scope.get() + 1);
}

assert_eq!(cell_i32.get(), 36);

しかし、ここでCell<String>で同じようなコードを書いてみます。

use std::cell::Cell;

let cell_string: Cell<String> = Cell::new("foo".into());

assert_eq!(cell_string.get(), "foo".to_string());

これは動きません。エラー文を見ると「::getは有るけど、Tstd::marker::Copy実装してないよ」のように書いてあります。

the method get exists but the following trait bounds were not satisfied: std::string::String : std::marker::Copy

このことからCell<T>Tstd::marker::Copyを実装済の型を渡さなければ意味がありません。

もしstd::marker::Copyな値を同じように扱いたいならRefCell<T>があります。

RefCell

std::marker::Copy未実装な型でも扱えるCell<T>(内側のTが可変)です。

RefCellでは::borrowで借用、::borrow_mutでミュータブル借用が行えます。つまりCell::setにあたるものは無く、ミュータブル借用で取得した値を直接更新します。

::borrowの戻り値はRef<T>となりますが、これはDerefが実装されている為Tに変換可能です。

以下はコンパイルできる適当なコードです。

use std::cell::RefCell;

let ref_cell_string: RefCell<String> = RefCell::new("foo".into());
let ref_ref_cell_string = &ref_cell_string;

*ref_ref_cell_string.borrow_mut() += "bar";

{
  let ref_ref_cell_string_in_scope = &ref_cell_string;
  *ref_ref_cell_string_in_scope.borrow_mut() += "baz";
}

assert_eq!(*ref_cell_string.borrow(), "foobarbaz".to_string());

以下はコンパイルできない Panic を起こすコードです。違いは::borrow_mutを一旦変数に置いている点だけです。

use std::cell::RefCell;

let ref_cell_string: RefCell<String> = RefCell::new("foo".into());
let ref_ref_cell_string = &ref_cell_string;

let mut _borrowed = ref_ref_cell_string.borrow_mut();
*_borrowed += "bar";

{
  // snap
}

assert_eq!(*ref_cell_string.borrow(), "foobarbaz".to_string());

これはRefCell::borrow::borrow_mutによる借用にも Rust の基本的な借用ルールが適用される為です。それは、

  1. イミュータブル参照であればいくらでも大丈夫
  2. ミュータブル参照は1つだけにしてね

というようなものでした。

上記の場合_borrowedがミュータブル参照としてスコープにある状態で、ref_cell_string.borrow()によるイミュータブル参照を取ろうとした為 Panic してしまいました。

直すには借用ルールと同じようにミュータブル参照を扱う部分を小さなスコープ化に閉じ込めてしまえば大丈夫です。

use std::cell::RefCell;

let ref_cell_string: RefCell<String> = RefCell::new("foo".into());
let ref_ref_cell_string = &ref_cell_string;

{
  let mut _borrowed = ref_ref_cell_string.borrow_mut();
  *_borrowed += "bar";
}

{
  let ref_ref_cell_string_in_scope = &ref_cell_string;
  let mut _borrowed = ref_ref_cell_string_in_scope.borrow_mut();
  *_borrowed += "baz";
}

assert_eq!(*ref_cell_string.borrow(), "foobarbaz".to_string());

先程 Panic が起きてしまったと言ったようにRefCellはコンパイル時にルールが守られているか確認していません。全ては実行時に初めて確認されています。

スレッド

Module std::thread::spawnを使うことで新しいスレッドを作ることができます。::spanには関数を渡しその関数の戻り値TをラップしたJoinHandle<T>を返します。

以下は例です。スレッドはJoinHandle::joinを使わなければスレッドの完了を待ってくれないのでhello from a threadと表示されずに終了してしまいます。

use std::thread;

let handler = thread::spawn(|| {
  println!("hello from a thread");
  // `::join`で待ってるのでちゃんとターミナルなどに表示される
});

let thirty_three = handler.join().unwrap();

関数の戻り値はjoin().unwrap()で受け取れます。

use std::thread;

let handler = thread::spawn(|| {
  33
});

let thirty_three = handler.join().unwrap();

assert_eq!(thirty_three, 33);

Function std::sync::mpsc::channel を使った方法でも値の送受信ができます。

上記の方法のようにJooinHandle::joinだけでなく以下のreceiiver.recvを使う方法でもスレッド処理を待つことができます。

use std::sync::mpsc::channel;
use std::thread;

let (sender, receiver) = channel();

thread::spawn(move || {
  sender.send(33).unwrap();
});

println!("{}", receiver.recv().unwrap())

送信した値は、FIFO で受信できます。また、注意として送信する値はSendを持ってる必要があります。

以下は、

  • i=0の時は2秒待つ
  • i=1の時は1秒待つ
  • i=2の時は待たない

で合計3回送信する例です。

スレッドでメインスレッドよりもVecの各u64が長生きしてしまう可能性や、senderは所有権を渡す必要からいくらか::cloneしてます。

これは1秒毎に210と表示されプログラムが終了します。

use std::sync::mpsc::channel;
use std::sync::Arc;
use std::thread;
use std::time::Duration;

let (sender, receiver) = channel();

let secs = vec![Arc::new(2_u64), Arc::new(1_u64), Arc::new(0_u64)];

{
  for (i, sec) in secs.iter().enumerate() {
    let sender = sender.clone();
    let sec = sec.clone();

    thread::spawn(move || {
      thread::sleep(Duration::from_secs(*sec));
      sender.send(i).unwrap();
    });
  }

  for _ in 0..secs.len() {
    println!("{:?}", receiver.recv().unwrap());
  }
}

受信でもfor構文を使っているのは、FIFO なので受信側も送信側と同じ回数繰り返し実行しなければすべて取り出せないからです。例えばこの部分を、

// for _ in 0..secs.len() {
  println!("{:?}", receiver.recv().unwrap());
// }

だけにすると、2だけが表示されプログラムが終了します。

Mutex

Mutex<T>は、共通リソースが複数のスレッド間で競合などが起きないようにしてくれます。::lockを実行することでMutexGuard<T>を取得できます。Tは安全に触りたい値のことで、MutextGuardDerefを持ってることから楽に触る事ができます。

すべてのスレッドの処理が終わった後、再度Tを取得したい場合はメインスレッドで再度::lockし、MutexGuardに変換してから取得できます。

use std::sync::{Arc, Mutex};
use std::thread;

let values: Arc<Mutex<Vec<i32>>> = Arc::new(Mutex::new(Vec::new()));

for i in 0..10 {
  let values = values.clone();

  thread::spawn(move || {
    let mut lock = values.lock().unwrap();
    lock.push(i);
  })
  .join()
  .unwrap();
}

let values = values.lock().unwrap();

assert_eq!(*values, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

あるスレッドのMutexGuardがスコープを抜けるまで他スレッドでは値を取得できない状態が続きます。なので以下のように書き換えると2つめの"foo"が出るまでに1秒の遅れが生まれます。

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

let values: Arc<Mutex<Vec<i32>>> = Arc::new(Mutex::new(Vec::new()));

for i in 0..10 {
  let values = values.clone();

  thread::spawn(move || {
    let mut lock = values.lock().unwrap();

    println!("foo");

    lock.push(i);

    if i == 0 {
      thread::sleep(Duration::from_secs(1));
    }
  })
  .join()
  .unwrap();
}

let values = values.lock().unwrap();

assert_eq!(*values, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

Duration

Struct std::time::Duration は、期間を扱う型です。Function std::thread::sleep など時間を扱う関数などがこの型を期待します。

::newでは秒とミリ秒を同時に設定できます。以下ではthread::sleep(Duration::new(1, 10))で1010ミリ秒を取得してます。

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

let handler = thread::spawn(|| {
  thread::sleep(Duration::new(1, 10));
  println!("called");
});

handler.join().unwrap();

上記は以下のように書き換えることもできます。

  • ::from_secsで秒
  • ::from_millisでミリ秒

をそれぞれ取得してます。また、DurationAddSubなどのトレイトを実装している為+-などで計算することができます。

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

let handler = thread::spawn(|| {
  thread::sleep(Duration::from_secs(1) + Duration::from_millis(10));
  println!("called");
});

handler.join().unwrap();

HashMap

HashMap<K, V, S = RandomState>は、キーを使って値にアクセスするようなデータ型です。

::newでミュータブルなハッシュマップを作り、::insertで値を登録します。データへアクセスする場合はインデックスと同じく[K]を変数の後ろに置きます。

use std::collections::HashMap;

let mut hash_map: HashMap<&str, i32> = HashMap::new();

hash_map.insert("nju", 33);

assert_eq!(hash_map["nju"], 33);

キーとなる値にはHashEqの両方を持っている型でなければなりません。

キーを持ってるか

::contains_keyを使います。

assert!(hash_map.contains_key("nju"));

キーと値を削除

::removeで削除できます。

hash_map.remove("nju");
assert!(!hash_map.contains_key("nju"));

すべてのキーを回して処理する

参照はIntoIteratorを持つのでこれを使います。それぞれの処理の前にkeyvalueのタプルを受け取ります。

for (key, value) in &hash_map {
  println!("{key}: {value}", key = key, value = value);
}

Range

範囲(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

Serde

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::<User>(json).unwrap());
// User { name: "nju33" 

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

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

println!(
  "{:?}",
  serde_json::from_str::<User>(&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

マクロ

マクロはコードを書くためのコード(メタプログラミング)です。コード(code)を生成する(generate)ので*_codegenという名前でクレートが提供されてたりします。マクロを使うと何度も記述しなければ行けないような部分を1箇所にまとめて効率よくコードを書くことができます。

(難しいのでまだ理解と中身が不十分です🙇)

マクロはmacro_rules! <name>で宣言します。例えばmacro_rules! foo {}で作ったマクロはfoo!で呼び出せます。

例えば以下のマクロは、実行時にprintln!(...)というコードに置き換わるマクロです。

macro_rules! printlnprintln {
  ( $($x:expr),* ) => {
    println!($($x),*);
  };
}

// printlnprintln!("{} {}", "I'm", "nju33");
// → println!("{} {}", "I'm", "nju33");

TBW

Rust のアップデート

Rust インストール時に rustup コマンドが使えるようになっているなら以下をするだけです。

rustup update
//
// ...ログ...
//
//   stable-x86_64-apple-darwin updated - rustc 1.29.1 (b801ae664 2018-09-20)
//  nightly-x86_64-apple-darwin updated - rustc 1.31.0-nightly (96cafc53c 2018-10-09)