ラベル プログラミング の投稿を表示しています。 すべての投稿を表示
ラベル プログラミング の投稿を表示しています。 すべての投稿を表示

2017年7月23日日曜日

The Rust Programming Language 2nd 16日目 テスト2

https://doc.rust-lang.org/book/second-edition/
Apache License Version 2.0

Testing

Rustはコードの中にテストを書き込むことを許している.

Controlling How Tests are Run

cargo testにオプションを与えてデフォルトとは異なる動作をさせることが出来る.オプションはcargo test --aaa --bbb というふうに書く.cargo testそのものに与えるオプションはaaaに,生成されるバイナリに与えるオプションはbbbに列挙する.

Running Tests in Parallel or Consecutively

デフォルトでは,それぞれのテストはいちいち結果を出力せずに平行実行され,最後に全ての結果を出力する.これは高速だが,以前行ったテストの結果や途中の計算を利用したテストを行えないという問題が有る.たとえばtest-output.txtというファイルを作成し,その中に何かしらのデータを書き込み,そのファイルがテストごとに異なる特定の値を含んでいるという一連のテストを考える.平行実行によるテストでは同じ名前のファイルの作成によって上書きが生じてしまい,正しいコードでもテストに通らなくなるおそれがある.この問題を解決するため,テストを一つずつ実行するという手段が有る.これを実現するには実行ファイルに--test-threadフラグを与える.
$cargo test -- --test-threads=1とすれば,テストを一つずつ実行するようになる.

Showing Function Output

あるテスト関数の中での標準出力は,そのテストが成功するとtest libraryに回収されて,出力されない.例えばprintln!を中に含むテストが成功すると,シェルからそのprintln!が出力した値を見ることはできない.一方テストが失敗すれば全ての標準出力とエラーメッセージがシェルから見れる.
例えばlisting 11-10は成功するテストと失敗するテストを持つ.
src/lib.rs listing 11-10

fn prints_and_returns_10(a: i32) -> i32 {
  println!("I got the value {}", a);
  10
}

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

  #[test]
  fn this_test_will_pass() {
    let value = prints_and_returns_10(4);
    assert_eq!(10, value);
  }

  fn this_test_will_fail() {
    let value = prints_and_returns_10(8);
    assert_eq!(5, value);
  }
}

shell

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok


failures:

---- tests::this_test_will_fail stdout ----
        I got the value 8
thread 'tests::this_test_will_fail' panicked at 'assertion failed: `(left == right)` (left: `5`, right: `10`)', src/lib.rs:19
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured

error: test failed, to rerun pass '--lib'

たしかにI got the value 8と,エラー時のみ標準出力が現れている.通過したテストの標準出力を見たい場合,--nocaptureフラグを使う.
$ cargo test -- --nocaptureによって
shell

running 2 tests
I got the value 4
I got the value 8
thread 'tests::this_test_will_fail' panicked at 'assertion failed: `(left == right)` (left: `5`, right: `10`)', src/lib.rs:19

note: Run with `RUST_BACKTRACE=1` for a backtrace.
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED

と,たしかに両方の標準出力がシェルから見れるようになっている.

Running a Subset of Tests by Name

全てのテストを行うと時間がかかりすぎるときは,cargo testの引数としてどのテストを走らせるかを指定することが出来る.
例えば
src/lib.rs listing 11-11

pub fn add_two(a: i32) -> i32 {
  a + 2
}

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

  #[test]
  fn add_two_and_two() {
    assert_eq!(4, add_two(2));
  }

  #[test]
  fn add_three_and_two() {
    assert_eq!(5, add_two(3));
  }

  #[test]
  fn one_hunder() {
    assert_eq!(102, add_two(100));
  }
}

listing 11-11のコードは明らかに全てのテストが通過する.

Running Single Tests

cargo testの引数にテスト関数名を入れることで,そのテスト関数だけを実行できる.

$ cargo test one_hundred
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running target/debug/deps/adder-06a75b4a1f2515e9

running 1 test
test tests::one_hundred ... ok

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

Ignore Some Tests Unless Specifically Requested

非常に時間がかかるテストを,特に指定した場合以外実行したくないときがある.このときは,テスト関数にtest attributeに加えてignore attributeをつける.
src/lib.rs

#[test]
fn it_works() {
  assert!(true);
}

#[test]
#[ignore]
fn expensive test() {
  // code that takes and hour tu run
}

このようにすれば,ただcargo testとしてもexpensive_testは実行されない.ignoreした関数も実行したいときには,cargo test -- --ignoredとオプションをつける.

The Organization

テストには大きく分けてunit(単体)テストとintegration(結合)テストが有る.unit testはそれぞれのモジュールを切り離して,private interfacesについてテストする.integration testはpublic interfaceと既存のモジュールのみを使って,外からコードを利用しているのと同じ方法でテストされる対象にアクセスし,テストする

Unit tests

コードのそれぞれの部分を他と切り離し,その部分が予期したとおり動くかを試す.unit testはsrc directoryに,テストされるコードとともに書く.testsという名前のcfg(test) annotation モジュールを作って中にテスト関数を列挙するのが慣習である.

The Tests Module and #[cfg(test)]

#[cfg(test)] annotationはRustに,cargo testコマンドが実行されたときにのみこのモジュールをコンパイルするように命令する.cargo buildが高速かつ生成される実行ファイルが小さくてすむ.integration testはコードとは別のところに書くので,#[cfg(test)]を書く必要はない.
新しくライブラリプロジェクトを生成すると,src/lib.rs

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

と,予めテストの雛形が自動的に書き込まれている.cfgとはconfigurationの略であって,以下のコードをtestに設定している.testに設定されたコードはcargo test時のみコンパイルされる.

Testing Private Functions

privateな関数をテストする.
src/lib.rs listing 11-12

pub fn add_two(a: i32) -> i32 {             // public
  internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {  // private
  a + b
}

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

  #[test]
  fn internal() {
    assert_eq!(4, internal_adder(2, 2));
  }
}

Privacy Rules

あるアイテムがpublicであるとき,親moduleを通じてそのアイテムにアクセスできる.
あるアイテムがprivateであるとき,そのアイテムがあるmoduleとその子moduleからしかそのアイテムにはアクセスできない.

を思い出すと,testsadd_twointernal_adderが定義されたスコープの子moduleだから,internalinternal_adderにアクセスできて,テストできる.

2017年7月22日土曜日

The Rust Programming Language 2nd 15日目 テスト1

https://doc.rust-lang.org/book/second-edition/
Apache License Version 2.0

Testing

Rustはコードの中にテストを書き込むことを許している.

How to Write Tests

testはRustの関数で,testでないコードが期待した通りに動いているか確かめる.test関数のbodyはふつう, 1.準備, 2.テストしたいコード, 期待する結果の3つを含んでいる.ここではtest attribute(属性)といくつかのマクロ,そしてshould_panic attributeを学ぶ.

The Anatomy of a Test Function

attributeはRustコードのメタデータで,chap.5 ですでにderiveを扱った.test attribute付きの関数がRustのテストコードである.関数をtest関数にするには,#[test]fnの上の行に書く.cargo testによってテストを実行し,test関数のどれが成功してどれが失敗したかを返す.
testの働きを実験を,自動生成されるtemplate testを通して見ていく.そのあと実際のtestを書いてみる.
ライブラリプロジェクトadderを生成すると,src/lib.rsにはすでにテストコードが書いてある.
src/lib.rs listing 11-1

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

it_works() {}は何もしないから,テストを無事通過する.

shell

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.22 secs
     Running target/debug/deps/adder-ce99bcc2479f4607

running 1 test
test tests::it_works ... ok     // testsモジュールのit_worksが正常と言っている

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

   Doc-tests adder  // documentに対するテスト

running 0 tests     // docを書いていないのでテストは行われない

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

失敗するテストを書いてみる.test functionがどこかでpanicするとテストは失敗する.
src/lib.rs listing 11-3

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

    #[test]
    fn anoter() {
        panic!("Make this test fail");
    }
}

shell listing 11-4

running 2 tests
test tests::exploration ... ok
test tests::another ... FAILED

failures:

---- tests::another stdout ----
    thread 'tests::another' panicked at 'Make this test fail', src/lib.rs:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.

failures:
    tests::another

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured

error: test failed

test tests::anotherFAILEDだったと言っている.成功したit_worksには触れられず,anotherの失敗の理由と,失敗したtestの一覧が表示され,最後にtest全体の要約が表示される.testが失敗するのはpanicが生じたときだけではない次節では,panicは起きないが予期した結果と違った計算を行ったときエラーを出すマクロを学ぶ.

Checking Result with the assert! Macro

assert! macroはtest functionがfalseを返したとき場合にpanic!を呼び,testを失敗させる.

rectangle/src/lib.rs listing 11-5

#[cfg(test)]
mod tests {
  use super::*;  // tests modの外に有るstructをスコープに入れる

  #[test]
  fn larger_can_hold_smaller() {
    let larger = Rectangle { length:8, width: 7};
    let smaller = Rectangle { length:5, width: 1};

    assert!(larger.can_hold(&smaller));             // assert!(ture)
  }

  #[test]
  fn smaller_can_not_hold_larger() {
    let larger = Rectangle {length: 8, width: 7};
    let smaller = Rectangle {length: 5, width: 1};

    assert!(!smaller.can_hold(&larger))             // assert!(!false)
  }
}

#[derive(Debug)]
pub struct Rectangle {
  length: u32,
  width: u32,
}
impl Rectangle {
  pub fn can_hold(&self, other: &Rectangle) -> bool {
    self.length > other.length && self.width > other.width
  }
}

listing 11-5では,Rectangleのcan_holdメソッドが真となる場合と偽になる場合の両方を確かめている.計算の結果がfalseであることを確かめたいなら,assert!(!false)によって,確かにfalseである場合のみtestを通過させるようにできる.
shell

running 2 tests
test tests::larger_can_hold_smaller ... ok
test tests::smaller_can_not_hold_larger ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured

また,コードにバグを埋め込んでみる.ここではRectangle.can_holdの不等号演算子の一つを逆にしてみる.
self.length < other.length && self.width > other.width
結果は
shell

running 2 tests
test tests::smaller_can_not_hold_larger ... ok
test tests::larger_can_hold_smaller ... FAILED

failures:

---- tests::larger_can_hold_smaller stdout ----
        thread 'tests::larger_can_hold_smaller' panicked at 'assertion failed: larger.can_hold(&smaller)', src/lib.rs:10
note: Run with `RUST_BACKTRACE=1` for a backtrace.



failures:
    tests::larger_can_hold_smaller

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured

と,やはり失敗したtestの詳細と全体の要約を出力してくる.

Testing Equality with the assert_eq! and assert_ne! Macros

ある関数が適当なfuncが数値や文字列xxxを返すときにのみ通過するテストは,assert(func()== xxx)などとすれば書けるのだが,手間を省くためにassert_eq!(xxx, func())として同じ意味になるマクロassert_eq!が定義されている.また,assert_ne!(xxx, funct())func()の返り値がxxxでない場合のみ通過する.どちらのマクロも,テストを通過しなかったときには問題となっている関数の返り値と想定された値を出力する.例えば
src/lib.rs listing 11-7

pub fn add_two(a: i32) -> i32 {
    a + 2
}

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

    #[test]
    fn it_adds_two() {
        assert_eq!(4, add_two(2));
    }
}

は無事通過し,ここでadd_twoのbodyをa+3に書き換えてテストを再度実行すると
shell

test tests::it_adds_two ... FAILED

failures:

---- tests::it_adds_two stdout ----
        thread 'tests::it_adds_two' panicked at 'assertion failed: `(left == right)` (left: `4`, right: `5`)', src/lib.rs:11
note: Run with `RUST_BACKTRACE=1` for a backtrace.

と,左辺,すなわち予期した値は4であるのに,返り値が5であったとしてエラーを返してくる.
ここで我々はassert_eq!(xxx, func())と,左辺に左に予期した値,右に関数を書いたが,この順序が逆でも構わないし,両方が関数でも構わない.例えば
src/lib.rs listing 11-7-0

pub fn add_two(a: i32) -> i32 {
    a + 2
}

pub fn mut_3(a: i32) -> i32 {
    a * 3
}

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

    #[test]
    fn mul_and_add() {
        assert_eq!(mut_3(4), add_two(10));
    }
}

はテストを通過する.

assert_eq!assert_ne!は内部で==!=演算子をそれぞれ使っており,また失敗時にはマクロの引数をdebug formattingによって出力する.ゆえに,比較される値はPartialE1Debug traitを実装していなければならない.全ての基本型と殆どの標準ライブラリ型はこれらのtraitを実装しているが,プログラマが実装したstructやenumにassert_eq!assert_ne!を適用するには,以上のtraitを実装しなければならない.しかしこれらのtraitはderivableだから,chap.5で見たように,#[derive(PartialEq, Debug)]定義時に注釈することで,簡単に実装できる.derivable traitについてはappendix Cに詳しい.

Custom Failure Messages

テストが失敗したときに好きなメッセージを出力させることが出来る.assert!は1つ,assert_eq!, assert_ne!は2つの引数を必ず取るが,さらに引数を与えると,それらはformat!マクロによって加工されるので,format stringと適当な変数を引数に渡すと,適当にパースして出力してくれる.例えば,人名を引数としてその人を歓迎する関数を作ってテストするときには以下のようなコードが考えられる.

src/lib.rs listing

pub fn greeting(name: &str) -> String {
  format!("Hello {}!", name)
}

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

  #[test]
  fn greeting_contains_name() {
    let result = greeting("Carol");
    assert!(result.contains("Carol"));
  }
}

これはテストを通過する.greetingのbodyをString::from("Hello!")としてバグを入れると,
shell

test tests::greeting_contains_name ... FAILED

failures:

---- tests::greeting_contains_name stdout ----
        thread 'tests::greeting_contains_name' panicked at 'assertion failed: result.contains("Carol")', src/lib.rs:12
note: Run with `RUST_BACKTRACE=1` for a backtrace.

とエラーを生じる.assertionが失敗したことを言っているが,よりエラーを見やすくするために,greeting関数の返り値を表示するようにする.
src/lib.rs

#[test]
fn greeting_contains_name() {
    let result = greeting("Carol");
    assert!(
        result.contains("Carol"),
        "Greeting did not contain name, value was `{}`", result
    );    // 第二引数はプレースホルダー{}を持てる文字列で,
          // 第三引数以降がそのプレースホルダーに入る.
}

ここでまたテストを行うと
shell

test tests::greeting_contains_name ... FAILED

failures:

---- tests::greeting_contains_name stdout ----
        thread 'tests::greeting_contains_name' panicked at 'Greeting did not contain name, value was 'Hello'', src/lib.rs:12
note: Run with `RUST_BACKTRACE=1` for a backtrace.

と,確かにエラーメッセージが想定したとおりになる.

Checking for Panics with should_panic

予期した通りの値を返すかを確かめるのと同じくらいに,発生したエラーを予期したとおりに対処するか確かめるのは重要である.たとえばChap. 9, listing9-8で定義したGuess型で,そのinstanceは必ず1から100の値を取ることを約束したので,Guessのinstanceでその範囲から外れたものを作ろうとしたときには確かにpanicを起こすことを確かめたい.
これをshould_panic attributeを関数につけて実現する.shold_panicは,それがつけられた関数がpanicを起こすときにのみテストを通過するようにする.
src/lib.rs listing 11-8

struct Guess {
  value: u32,
}

impl Guess {
  pub fn new(value: u32) -> Guess {
    if value < 1 || value > 100 {
      panic!("Guess value must be between 1 and 100, got {}", value);
    }

    Guess {
      value
    }
  }
}

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

  #[test]
  #[should_panic]
  fn greater_than_100() {
    Guess::new(200);
  }
}

これは確かにテストを通過する.
shell

running 1 test
test tests::greater_than_100 ... ok

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

ここでnew()における条件を外すと,
shell

running 1 test
test tests::greater_than_100 ... FAILED

failures:

failures:
    tests::greater_than_100

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

と,正常にGuessの新しいinstanceが作られてしまうので,エラーを生じる.should_panicは,予期した形のpanicでなくともpanicを拾うとテストに通してしまうので,should_panicexpectedというパラメータを渡して,より厳密なテストを行うことが出来る.expectedには文字列が入って,panic時のメッセージにその文字列が現れるときのみテストを通すようにする.例えば
src/lib.rs listing 11-9

struct Guess {
  value: u32,
}

impl Guess {
  pub fn new(value: u32) -> Guess {
    if value < 1 {
      panic!("Guess value must be greater than or equal to 1, got {}.", value);
    }
    else if value > 100 {
      panic!("Guess value must be less than or equal to 100, got {}", value);
    }

    Guess {
      value
    }
  }
}

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

  #[test]
  #[should_panic(expected = "Guess value must be less than or equal to 100")]
  fn greater_than_100() {
    Guess::new(200);
  }
}

をテストにかけると,確かにpanicが生じ,しかも値が100を上回るときのメッセージが与えられるから,テストを通過する.
また,if value < 1else if value > 100において数値と不等号を交換すると,

shell

test tests::greater_than_100 ... FAILED

failures:

---- tests::greater_than_100 stdout ----
        thread 'tests::greater_than_100' panicked at 'Guess value must be greater than or equal to 1, got 200.', src/lib.rs:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.
note: Panic did not include expected string 'Guess value must be less than or equal to 100'

と,予期したメッセージと返されたメッセージが異なるため,panicが生じてもshould_panicはテストを通過させない.

以上でテストの書き方を学んだので,つぎはテストを行っているとき内部で何が起きているかとか,cargo testの様々なオプションを見ていくことにする.

2017年7月19日水曜日

The Rust Programming Language 2nd 13日目 GenericとTraits

https://doc.rust-lang.org/book/second-edition/
Apache License Version 2.0

Generic Types, Traits, and Lifetimes

同じロジックでも,扱う変数の型が違えばこれまで学んだ関数の定義方法では,型ごとに似たような関数をいくつも書かなければならない.このようなロジックの重複を解消するのがgenericで,変数の型に関係なく関数やstructを定義できる.
genericはすでにChap 6でOption<T>を, Chap. 8でVec<T>HashMap<K, V>を,Chap. 9でResult<T, E>を使った.これを一般化した用法をこの章では学ぶ.
まず,単純に同じロジックだが引数や返り値の型が違う二つの関数を見て,それをgenericによって一本化する.次に単純に一本化できない場合の解決法を学び,最後にlifetimeという,reference同士の関係性を記述するgenericの一種を導入し,リ方法を学ぶ.

Generic Data Types

Using Generic Data Types in Function Definitions

src/main.rs listing 10-4a

fn largest(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

という関数を考える.これはi32のリストのreferenceを取ってその最大値(i32)を返す関数で,['y', 'm', 'a', 'q']と言うようなリストを引数に取ることはできないので,型だけを(list: &[char]) -> charとしたような関数を更に定義しなければならない.これは明らかに無駄なので,genericによって一本化することを考える.
引数や返り値をgeneric型にするには,そのgeneric型の変数の型名と変数自体の名を関数宣言の際に定義し,関数の内部でその変数名を使ってロジックを書く.変数の型名にはほとんど必ずTを使う.具体的な記法は
fn largest<T>(list: &[T]) -> T {
のようになる.改めてlargestを書き直したのがlisting 10-5aである.しかしこれはコンパイルできない.

src/main.rs listing 10-5a

fn largest<T>(list: &[T]) -> T {
  let mut largest = list[0];

  for &item in list.iter() {
    if item > largest {
      largest = item;
    }
  }
  largest
}

shell

error[E0369]: binary operation `>` cannot be applied to type `T`
  |
5 |         if item > largest {
  |            ^^^^
  |
note: an implementation of `std::cmp::PartialOrd` might be missing for `T`

とエラーが出る.これは任意の型Tに不等号の演算が定義されていないことが原因で,Tの取りうる値をプログラマが制限しなければならない.そのため,標準ライブラリのもつtrait std::cmp::PartialOrdを使うようにする(後述).

Using Generic Data Types in Struct Definitions

structの定義にもgenericを使える.
src/main.rs listing 10-6a

struct Point<T> {
  x: T,
  y: T,
}

fn main() {
  let integer = Point {x: 5, y: 10};
  let float = Point {x: 1.0, y: .40};
}

listing 10-6では,generic型の型名をTしか決めておらず,x, yは1つのinstanceでは必ず同じTの型しか持てない.x, yがそれぞれ異なる型の値を持てるようにするには,generic型の名前を予め二つ用意する.
src/main.rs listing 10-8

struct Point<T, U> {
  x: T,
  y: U,
}

fn main() {
  let both_integer = Point {x: 5, y: 10};
  let both_float = Point {x: 1.0, y: 4.0};
  let integer_and_float = Point{x: 5, y: 4.0};
}

は正常にコンパイルできる.generic型の型名はいくつあってもいいが,コードを読んで把握しづらくなるほど多いようならロジック自体を考え直すべき.

Using Generic Data Types in Enum Definitions

structと同様に,enumもgeneric型をそのvariantsに持てる.Option<T>がこれを行っているのはすでに見た.Rustは変数を取らないときにはgeneric型をvariantに入れないことを許しているから

enum Option<T> {
  Some(T),
  None,
}

というふうにOption<T>を定義できる.また,あるvariantに入る型と別のvariantに入る型が異なっている場合にも

enum Result<T, E> {
  Ok(T),
  Err(E),
}

と記述できる.

Using Generic Data Types in Method Definitions

Chap.5 でやったように,struct やenumにmethodを定義できるが,これにもgenericが使える.例えば
src/main.rs listing 10-9

struct Point<T> {
  x: T,
  y: T,
}

impl<T> Point<T> {
  fn x(&self) -> &T {
    &self.x
  }
}

fn main() {
  let p = Point {x: 5, y: 10};
  println!("p.x = {}", p.x());
}

また例えば
src/main.rs listing10-10

struct Point<T, U> {
  x: T,
  y: U,
}

impl<T, U> Point<T, U> {
  fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
    Point {
      x: self.x,
      y: self.y,
    }
  }
}

fn main () {
  let p1 = Point {x: 5, y: 10.4} ;
  let p2 = Point {x: "Hello", y: 'C'};  
  let p3 = p1.pixup(p2);
}

は正常に実行できる.

Performance of Code using Generics

Rustはコンパイル時にgenericを具体的な型のコードたちに変換するので,genericが実行時のオーバーヘッドになることはない.Rustのこの働きをmonomorphizationという.
たとえば

let integer = Some(5);
let float = Some(5.0);

をコンパイルするとき

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

のように内部で変換する.

Traits: Defining Shared Behavior

traitは型の振る舞いを抽象化する.つまり複数の型に同時にメソッドを定義できる.また関数の引数などにgeneric型を使っているときtraitによってその引数が取れる型の範囲を制限して,Using Generic Data Types in Function Definitionsでみたエラーに対処することができる.

Defining a Trait

型のふるまいは型に実装されているメソッドたちによって決まる.異なった型たちが同じ名前のメソッドを持っているとき,その型たちは振る舞いを共有していると考えることが出来る.traitによって複数の型に同じ名前のメソッドを同時に定義できる.例えばNewsArticle型とTweet型を考える.両者ともに,そのインスタンスの要約を返すメソッドsummaryを,summarizable traitによって持たせる.traitはmoduleのように定義するが,body blockにはsignatureだけ書く.
src/lib.rs listing 10-11

pub trait Summarizable {
  fn summary(&self) -> String;  // method signatureのみ書く
  fn author(&self) -> String;   // 複数のメソッドも書ける.
  fn content(&self) -> String;  // 1行に一つのmethod signatureを書き,セミコロンを打つ.
}

Implementing a Trait on a Type

Summarizable traitを定義したところで,型にtraitを実装する.通常のメソッド定義は
impl NewsArticle { fn summary...}と書くが,traitを実装するときは
impl Summarizable for Newsarticle { fn summary signature { }} と書く.fn summary signature { }の中に実際のロジックをコーディングする.

具体的な例:
lib.rs listing 10-12

pub struct NewsArticle {
  pub headline: String,
  pub location: String,
  pub author: String,
  pub content: String,
}

impl Summarizable for NewsArticle {
  fn summary(&self) -> String {
    format!("{}, by {} ({})", self.headline, self.author, self.location)
  }
}

pub struct Tweet {
  pub username: String,
  pub content: String,
  pub reply: bool,
  pub retweet: bool,
}

impl Summarizable for Tweet {
  fn summary(&self) -> String {
    format!("{}: {}", self.username, self.content)
  }
}

こうしてSummarizableNewsArticleTweetに実装できた.それぞれの型のinstanceにドット記法でSummarizableの中のメソッドを呼べる.

let tweet = Tweet {
    username: String::from("horse_ebooks"),
    content: String::from("of course, as you probably already know, people"),
    reply: false,
    retweet: false,
};

println!("1 new tweet: {}", tweet.summary());

1 new tweet: horse_ebooks: of course, as you probably already know, people.を出力する.

これまではすべてをlib.rsに書いてきた.これらをaggregatorというcrateにして,他の場所にあるWeatherForecast structにSummarizable traitを実装したい場合,Summarizableをまずインポートする.
lib.rs listing 10-13 例

extern crate aggregator;

use aggregatro::Summarizable;

struct WeatherForecast {
  high_temp: f64,
  low_temp: f64,
  chance_of_precipitation: f64
}

impl Summarizable for WeatherForecast {
  fn summary(&self) -> String {
    format!("The high will be {}, and the low will be {}. The chance of precipitation is{}%", self. high_temp, self.low_temp, self.chance_of_precipitation)
  }
}

traitとtypeがともにexternalであるとき,そのtypeにtraitを新たに実装することはできない.例えば,Vecはexternal traitでDisplayはexternal traitだから,VecDisplayを実装することはできない.こうしたルールをOrphan ruleという.

Default Implementations

traitを定義するとき,予めロジックを決めておいて,改めて型にtraitのメソッドを実装しない限りそのデフォルトのロジックをその型のメソッドとすることが出来る.Default implementationという.そのためには,listing 10-11ではセミコロンで止めていおいたメソッドのsignatureを,実際のロジックまで書くようにし,
lib.rs listing 10-14

pub trait Summarizable {
  fn summary(&self) -> String {
    String::from("(Read more...)");
  }
}

さらに型への実装で{ }を空白にする. impl Summarizable for NewsArticle {}仮にここでtraitの定義とは別のロジックを書いたら,新しいロジックが優先される.

default implementationはそのtraitの他のメソッドを,デフォルトが定義されていなくても,呼ぶことが出来る.例えば

pub trait Summarizable {
  fn author_summary(&self) -> String;

  fn summary(&self) -> String {
    format!("(Read more from {}...)", self.author_summary())
  }
}

このSummarizableを使うときは,author_summaryを型に実装する.

impl Summarizable for Tweet {
    fn author_summary(&self) -> String {
        format!("@{}", self.username)
    }
}

Trait Bounds

traitをgeneric type parameterと使うことも出来る.generic typeは野放図に使うとUsing Generic Data Types in Function Definitionsのようなエラーを生じることがあるので,そのgeneric typeを取れる型が特定のtraitを実装されている型であると制限して,その制限下のどの型でも動くとコンパイラが判断すれば,コンパイルしてくれる.こうしてgeneric typeの型を制限することを,”generic typeにtrait boundsを指定する”という

例えばlisting 10-12でsummarizableNewsArticleTweetに実装したので,NewsArticleTweetを引数に取るnotifyという関数をgenericを使って定義する.generic type parameter TSummarizable traitが実装されている型に制限するには,定義時に<T: Summarizable>とすれば良い.例えば

pub fn notify<T: Summarizable>(item: T) {
  println!("Breaking news! {}", item.summary())
}

また,SummarizableDisplayを同時に実装している型にgeneric typeを制限したいときには<T: Summarizable + Displayとする.
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
というふうに複数の引数にそれぞれのtrait boundsを設定することが可能だが,読みづらいのでwhereキーワードを使って

fn some_function<T, U>(t: T, u: U) -> i32
  where T: Display + Clone,
        U: Clone + Debug
{

というふうに定義することも可能である.

Fixing the largest Function with Trait Bounds

Using Generic Data Types in Function Definitionsで見たエラーを実際に修正しよう.

error[E0369]: binary operation `>` cannot be applied to type `T`
  |
5 |         if item > largest {
  |            ^^^^
  |
note: an implementation of `std::cmp::PartialOrd` might be missing for `T`

不等号演算子が定義されている型がTに来るかもしれないというのでエラーメッセージが出たので,不等号を定義する標準ライブラリのtrait, std::cmp::PartialOrdをtrait boundとしてみる.
fn largest<T: PartialOrd>(list: &[T]) -> T {
しかし,これでもエラーが出る.

error[E0508]: cannot move out of type `[T]`, a non-copy array
 --> src/main.rs:4:23
  |
4 |     let mut largest = list[0];
  |         -----------   ^^^^^^^ cannot move out of here
  |         |
  |         hint: to prevent move, use `ref largest` or `ref mut largest`

error[E0507]: cannot move out of borrowed content
 --> src/main.rs:6:9
  |
6 |     for &item in list.iter() {
  |         ^----
  |         ||
  |         |hint: to prevent move, use `ref item` or `ref mut item`
  |         cannot move out of borrowed content

cannot move out of type [T], a non-copy array.に着目する.TCopy traitを実装していないため,largest = list[0]が実行できなかったことを示している.よってtrait boundにCopyを加えることで,コンパイルが可能になる.

src/main.rs listing 10-15

use std::cmp::PartialOrd;

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];

    let result = largest(&numbers);
    println!("The largest number is {}", result);

    let chars = vec!['y', 'm', 'a', 'q'];

    let result = largest(&chars);
    println!("The largest char is {}", result);
}

Copyをtrait boundに加えたくない場合,かわりにCloneをtrait boundsに加えても良いが,Cloneはheap構造を使うので,性能が落ちる可能性がある.

2017年7月13日木曜日

The Rust Programming Language 2nd 14日目

https://doc.rust-lang.org/book/second-edition/
Apache License Version 2.0

Generic Types, Traits, and Lifetimes

Validating References with Lifetimes

referenceにはかならずlifetime(寿命)という,referenceが有効であるスコープをもっているが,大方の場合それは明示されずコンパイラに推測される.lifetimeを特に指定する場合には,generic lifetime parametersを使う.lifetimeはRust独特の機能であって,時に非常に重要なので,この章でその基本的な概念を述べた後,19章で応用的なlifetimeの扱い方を学ぶ.

Lifetime Prevent Dangling References

dangling(宙ぶらりん) referenceは,すでに意味を失った変数へのreferenceで,これを放置すると,あるデータにアクセスしようとして他のデータにアクセスしてしまうことがある.lifetimeの目的はdanglin referenceが出来るのを防ぐことである.
例えばlist 10-16では内側の{ }で定義されたxへのreferenceをrに代入しているが,xは内側の{ }が終了すると同時に消えてしまうので,その時点でrも有効ではなくなる.つまりxのlifetimeは内側の{ }の中で,rのlifetimeは外側の{ }の中だから,その外でのreferenceは無効になる.
list 10-16

{
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}

The Borrow Checker

list 10-16にlifetimeの注釈を加えてみる.
list 10-17

{
    let r;         // -------+-- 'a
                   //        |
    {              //        |
        let x = 5; // -+-----+-- 'b
        r = &x;    //  |     |
    }              // -+     |
                   //        |
    println!("r: {}", r); // |
                   //        |
                   // -------+
}

rのlifetimeを'a, xのlifetimeを'bで書いた.コンパイラはそれぞれのlifetimeを比較し,rが“`x““をborrowしているのを見つけて,エラーを出す.
list 10-18はdangling referenceを持たず,正常にコンパイルできる.
list 10-18

{
    let x = 5;            // -----+-- 'b
                          //      |
    let r = &x;           // --+--+-- 'a
                          //   |  |
    println!("r: {}", r); //   |  |
                          // --+  |       rustでは,宣言した順とは逆順に
}                         // -----+       変数が無効化されていくのだった

Generic Lifetimes in Functions

2つのstring sliceを引数に与えて,長い方のstring sliceを返す関数longestを考える.
longestの実装は後回しにして,例えばlongeestはlist 10-19のように利用できる.
src/main.rs list 10-19

fn main() {
  let string1 = String::from("abcd");
  let string2 = "xyz";

  let result = longest(string1.as_str(), string2);
  println!("The longest string is {}", result);
}

は正常に動けば”abcd”を出力するはずだ.
list 10-20は longestの案だが,コンパイルできない.
src/main.rs list 10-19

fn longest(x: &str, y: &str) -> &str {
  if x.len() > y.len() {
    x
  } else {
    y
  }
}

shell

error[E0106]: missing lifetime specifier
   |
1  | fn longest(x: &str, y: &str) -> &str {
   |                                 ^ expected lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but the
   signature does not say whether it is borrowed from `x` or `y`

エラーメッセージは返り値のreferenceがx, yどちらを指せばいいのかわからないと言っている.しかしプログラマもx, yのどちらが長いかは事前にわからないし,与えられる引数のlifetimeがどうであるかもわからない.そこで,generic lifetime parameterによってrefereneたちの間の関係性を記述し,borrow checkerを助けることにする.

Lifetime Annotation Syntax

lifetime annotationは変数のlifetimeそのものを変えることはできないが,複数のreferenceを関連付けることが出来る.構文としては,アポストロフィ`'とそれに続くlifetime parameterの名前(ふつう小文字1文字)を書く.'aが最も使われる書き方である.lifetime annotation自体は,referenceの&の直後に書く.たとえば

&i32          // a reference
&'a i32       // a reference with an explicit lifetime
&'a mut i32   // a mutable reference with an explicit lifetime

などと書く.

2017年7月9日日曜日

The Rust Programming Language 2nd 12日目 Error Handling

https://doc.rust-lang.org/book/second-edition/
Apache License Version 2.0

Error Handling

RustはエラーをRecoverableUnrecoverableに分けている.前者は生じたことをユーザーに知らせてインプットし直させたりして解決しうるエラーであり,後者はarrayの長さよりも大きいindexを指定するような,回復不能なエラーである.
RustではResult<T, E>型によって前者の発生を伝え,後者の場合panic! macro が実行を停止する.この章ではまずpanic!の扱い方を論じてからResult<T, E>を論じる.さらに,エラーから復帰するか停止するかを決めるに当たっての方法論を述べる.

Unrecoverable Errors with panic!

バグが生じて,プログラムがそれをどう処理するかわからないとき,panic! macroはエラーメッセージを出力し,メモリをきれいにしてから実行を停止する.

Unwinding the Stack Versus Aborting on Panic

デフォルトではpanic!によってプログラムはunwinding(解きほぐし?)を始める.unwindingはRustの関数が持っていたデータを削除することである.これには時間がかかるので,ただちにabortしてメモリをそのままにプログラムを停止することもできる.この場合OSがメモリを掃除することになる.プログラムのサイズをできるだけ小さくしたいときはcargo.toml[profile]panic = 'abort'を追加することでabortを指定できる.

試しにpanic!を呼んでみよう.
src/main.rs

fn main() {
  panic!("crash and burn");
}

shell

Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
 Running `target/debug/error_handling`
thread 'main' panicked at 'crash and burn', src/main.rs:2
note: Run with `RUST_BACKTRACE=1` for a backtrace.

エラーメッセージが示すsrc/main.rsの2行目には我々が書いたpanic!があるが,普通のプログラムではエラーメッセージが示している部分を更に我々が書いたコードが呼んでいることが多いので,その場合にはbacktraceによって,我々のコードが孕んでいるバグを見つけることができる.

Using a panic! backtrace

src/main.rs

fn main() {
  let v = vec![1, 2, 3];
  v[100];
}

このコードはvというVectorの割り当てられた範囲外のメモリを参照している.
Cのような言語はこうしたコードを無事コンパイルして,実行時にbufer overreadという危険な状態が生じる.Rustではunrecoverableなエラーを生じ,panic!する.
shell

Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
 Running `target/debug/error_handling`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', /checkout/src/libcollections/vec.rs:1488
note: Run with `RUST_BACKTRACE=1` for a backtrace.

このエラーメッセージにはlibcollections/vec.rsというファイルが含まれる.このファイルでRustはVec<T>型を実装していて,[]vに対して使うときに呼び出される.panic!は実際にはここで起こっているのである.
最終行ではRUST_BACKTRACEを有効にすることでbacktraceを行えることがわかる.実際にやってみよう.
shell

ren@ren-ThinkCentre-Edge72:~/Projects/error_handling$ RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/error_handling`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', /checkout/src/libcollections/vec.rs:1488
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
             at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::_print
             at /checkout/src/libstd/sys_common/backtrace.rs:71
   2: std::panicking::default_hook::{{closure}}
             at /checkout/src/libstd/sys_common/backtrace.rs:60
             at /checkout/src/libstd/panicking.rs:355

   3: std::panicking::default_hook
             at /checkout/src/libstd/panicking.rs:371
   4: std::panicking::rust_panic_with_hook
             at /checkout/src/libstd/panicking.rs:549
   5: std::panicking::begin_panic
             at /checkout/src/libstd/panicking.rs:511
   6: std::panicking::begin_panic_fmt
             at /checkout/src/libstd/panicking.rs:495
   7: rust_begin_unwind
             at /checkout/src/libstd/panicking.rs:471
   8: core::panicking::panic_fmt
             at /checkout/src/libcore/panicking.rs:69
   9: core::panicking::panic_bounds_check
             at /checkout/src/libcore/panicking.rs:56
  10: <collections::vec::Vec<T> as core::ops::Index<usize>>::index
             at /checkout/src/libcollections/vec.rs:1488
  11: error_handling::main
             at ./src/main.rs:3
  12: __rust_maybe_catch_panic
             at /checkout/src/libpanic_unwind/lib.rs:98
  13: std::rt::lang_start
             at /checkout/src/libstd/panicking.rs:433
             at /checkout/src/libstd/panic.rs:361
             at /checkout/src/libstd/rt.rs:57
  14: main
  15: __libc_start_main
  16: _start

11行目で,src/main.rsの3行目がエラーに関係していることを教えてくれる.

Recoverable Errors with Result

殆どのエラーはプログラムをただちに終了させるほどのものではなく,例えば開くファイルをユーザーが指定するときに存在しないパスを入力してしまったときのような,もう一度正しいインプットをするように促すだけですむものもある.Chap.2 でみたように,Result型を使ってこのような状況を扱うことが出来る.ResultOkErrの値を取るEnumであって,以下のように定義されている.

enum Result<T, E> {
  Ok(T),
  Err(E),
}

TEはgeneric type parameterといって,Chap.10で詳しく述べる.今は,Tの場合にOkとともに返す値の型が,Eの場合にErrとともに返す値の型を表すと考えれば良い.

Result型を返す関数を使ってみよう.
src/main.rs

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");
    // File::openはResult型を返す.
    // 正常に読み出すとResult<T>:std::fs::File
    // 読み出しでエラーが生じるとReulst<E>std::io::Error型が入る
}

読み出しに成功するとfOkのインスタンスで,ファイルへのhandleを持つことになり,失敗するとErrのインスタンスでエラーの詳細を持つことになる.
Resultによって挙動を変えるときには以下のようにする.

src/main.rs

use std::fs::File;

fn main() {
  let f = File::open("hello.txt");

  let f = match f{
    Ok(file) => file,
    Err(error) => {
      panic!("There was a problem opening the file \n : {:?}", error)
    },
  };
}

match構文によってfの型で場合分けし,正常な場合はfにハンドラを改めて代入し,異常な場合はpanic!する.このとき,以下のようなエラーが出力される.
shell

thread 'main' panicked at 'There was a problem opening the file
: Error { repr: Os { code: 2, message: "No such file or directory" } }', src/main.rs:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Matching on Different Errors

先程はOkErrかのみで分岐したが,Errの内容によってさらに分岐することも出来る.例えば,File::openが,開くファイルが存在しないためにエラーを出した場合,新しくそのファイルを作れば復帰できる一方で,開くファイルへのパーミッションを持っていないときにはpanic!したいとする.このときは,以下のように書く.
src/main.rs

use std::fs::File;
use std::io::ErrorKind;
fn main() {
  let f = File::open("hello.txt");

  let f = match f {
    Ok(file) => file,
    Err(ref error) if error.kind() == ErrorKind::NotFound => {
      match File::Create("hello.txt") {
        Ok(fc) => fc,
        Err(e) => {
          panic!{
            "tried to create file but there was a problem: {:?}",
            e
          }
        },
      }
    },
  };
}

Propagating Errors

関数が別の何かを呼んで,その何かがエラーを生じたときに,もとの関数がそのエラーの内容によって分岐するように出来る.これをpropagatingといい,もとの関数がエラーをどう処理するかを関数の他の内部状態で決めたいときに使われる.
以下のコードでread_username_from_fileを呼んだ関数はResultを返される.
src/main.rs, list9-5

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
  let f = File::open("hello.txt");

  let mut f = match f{
    Ok(file) => file,                 // fにファイルへのハンドラを代入
    Err(e) => return Err(e),          // Err(io::Error)
  };

  let mut s = String::new();

  match f.read_to_string(&mut s) {    // sにfの中身を(あれば)代入
    Ok(_) => Ok(s),                   // Ok(String)
    Err(e) => Err(e),                 // Err(io::Error)
  }
  // 返される値はResult型であり,呼んだ関数がエラー処理を行うことになる.
}

fn main() {
    let result = read_username_from_file();
    println!("The return result is \n : {:?}", result);
}

shell

Running `target/debug/error_handling`
The return result is
: Err(Error { repr: Os { code: 2, message: "No such file or directory" } })

簡潔に書く方法に?キーワードを使う方法が有る.

A Shortcut for Propagating Error: ?

先ほどと同じ機能を持つ関数を?を使って書く.
src/main.rs, list9-6

use std::io;
use std::io::Read;
use std::io::File;

fn read_username_from_file() -> Result<String, io::Error> {
  let mut f = File::open("hello.txt")?;
              // Okならfは中身(ハンドラ)をfに代入し継続
              // Errなら関数を終了してResultを呼んだもとに返す

  let mut s = String::new();
  f.read_to_string(&mut s)?;
              // Ok ならstatementはread_to_stringのOkの中身
              // Errなら関数を終了してResultを呼んだもとに返す
  Ok(s)
}

Result型の後に?がつくと,Okの場合はその中身を返し,関数を継続する.Errの場合はそれを呼んだもとに返してただちに関数を終了する.
list9-6はさらに簡潔に書ける.
src/main.rs, list9-7

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
  let mut s = String::new();

  File::open("hello.txt")?.read_to_string(&mut s)?;
  Ok(s)
}

? Can Only Be Used in Functions That Return Result

Err?をつけると,Err自体がその関数の返す値になるので,?キーワードを使う関数は最初から返り値の型がResultでなければならない.

To panic! or Not To panic!

多くの場合panic!でプログラムを落とすより,条件分岐を使って通常の状態に復帰することを考えるべきだが,panic!を使うべき場面もいくつか存在する.

Examples, Prototype code, and Test: Perfectly Fine to Panic

サンプルコードや青写真を書くときには,高度なエラーの取扱はロジックをわかりにくくする恐れが有るし,unwrapexpectのほうが明瞭にどこでエラーが起きたかわかりやすい場合が有る.テストを行う場合も同様である.

Cases When You Have More Infromation Than The Compiler

必ずResultOkとなるような仕組みが有るときは,unwrapexpectを使ってErrの場合の処理を書くのを省略できる.例えば

use std::net::IpAddr;
let home = "127.0.0.1".parse::<IpAddr>().unwrap();

とする.”127.0.0.1”は有効なIPアドレスだからparseは成功するはずだが,parseは本質的にResultを返すmethodだから,Errが返されたときの処理も書かないとコンパイラに怒られる.これを回避して簡潔に書くため,単にunwrapを使える.
仮にIPアドレスをユーザーが入力するプログラムなら,無効なIPアドレスを入力される場合が考えられるので,Resultによってエラー処理を書くほうが良い.

2017年7月5日水曜日

The Rust Programming Language 2nd 11日目

![](https://doc.rust-lang.org/book/second-edition/]
Apache License Version 2.0

8. Common Collections

Strings

What is String?

Rustが実装している文字列型はstrのみで,ふつう&strで利用される.StringはRustの標準ライブラリに実装されていて,ともにUTF-8でエンコードされている.

Creating a New String

Vecと同じく,newによって新しいStringを定義できる.
let s = String::new();
sという空の文字列を新たに宣言する.また,変数に入れたい文字列が決まっているときは,

let data = "initial contents";

let s = data.to_string();
// これらは結局同じこと
let s = "initial contents".to_string();

このように,to_string methodによって,String型の変数を作れる.to_stringDisplayを実装している型なら持っている.また,String::fromで文字列リテラルを受け取るとStringにできることはすでに見た.

let s = String::from("initial contents");

Updating a String

Vecと同じくStringは伸び縮みできるし,その中身自体を変更することもできる.特に,+演算子によってString同士をつなげることができる.

Appending to a String with Push

Stringpush_str methodによって末尾にさらに文字列を追加できる.

fn main() {
  let mut s = String::from("foo");
  s.push_str("bar");

  println!("s is {}", s);
}

shell

s is foobar

さらに,string literalはStringのreferenceだから,(Chap. 4)

fn main() {
  let mut s1 = String::from("foo");
  let s2 = String::from("bar");
  s1.push_str(&s2);                   // referenceでないとエラー

  println!("s1 is {}", s1);
}

としても同じことである.ただし,push_strはreferenceのみを引数に取る.
また,pushは引数に1文字だけをとる.

fn main() {
  let mut s1 = String::from("lo");
  s1.push('l');                       // 'lo'などとするとエラー

  println!("s is {}", s1);
}

Concatenation with the + Operator or the format! Macro

2つのStringを結合させたいことがある.前節とは別の方法に,+によるconcatenationがある.

let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2; // s1はmoveしてこの時点で消滅する.s2は必ずreference

このようにしてs3の中身はHello, World!となる.+演算子は内部的には

fn add(self, s: &str) -> String{ // 本当はもっと高度なことをしているらしい(Chap. 10)

のような,Stringadd methodを使っているので,ownershipのmoveによってs1は消えてしまう.ところで,&s2の型は&Stringで,&strではない.addの引数として&Stringを与えると内部でderef coercionという機能が働いて&strに変換(原文coerce)する.(&s2 => &s2[..] にする.strとはStringのsliceであった.)
また,addは引数のselfのownershipをとっていて,s1は消える.
2つ以上のStringを結合したいときは,let s = s1 + "-" + &s2 + "-" + &s3;などとする.またより読みやすい記法としてlet s = format!("{}-{}-{}", s1, s2, s3);としても同じことである.

Indexing into Strings

rustはStringのindexを指定して文字を取り出す機能を実装していない.

Internal Representation

StringVec<u8>のラッパである.つまり,UTF-8のコードの数値をVectorに保持し,それを文字列として扱うための糖衣構文である.

let len = String::from("Hola").len();

とすると,lenは4である.これはUTF-8でラテン文字は1Byteで表現されるためで,例えば

let len = String::from("Здравствуйте").len();

とすると,キリル文字は2Bytesで表現されるのでlen24となる.ここで

let hello = "Здравствуйте";
let answer = &hello[0];       // 実際はコンパイルできない

とすると,ЗのUTF-8表現は208なので,answer208となって,これは大方のプログラマの予期しない結果と思わるので,Rustはこうした行為ができないようにしている.

Bytes and Scalar Values and Grapheme Clusters! Oh my!

Grapheme Clustersとは,我々が単にletters(文字列?)と呼ぶものに最も近いRust内の表現である.
“नमस्ते”というヒンズー語の文字列は,Rust内部では究極的に
“`rust
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]

という```Vec<u8>```型である.
これは18bytesあって,ヒンズー語の文字としてそれぞれを分けてみると,
```rust
['न', 'म', 'स', '्', 'त', 'े']




<div class="se-preview-section-delimiter"></div>

左から4,6番目はletterでなく,diacritic(発音記号)であって,結局grapheme clustersは

["न", "म", "स्", "ते"]




<div class="se-preview-section-delimiter"></div>

のようになる.このように,プログラマの直感とはかなり異なった方法でRustは文字列を保持していて,Stringのインデクシングを直感的に行えるようにするには,計算量が(O(1))では効かなくなる.インデクシングは常に定数計算量(O(1))であるべきと開発者が考えたため,Stringはインデクシングを実装していない.

Slicing Strings

Stringをbyteごとにスライスすることはできる.

let hello = "Здравствуйте"
let s = &hello[0..4];

sは最初から4番目までのByteへのrefereceで,&str型である.キリル文字は2bytes文字だから,sをプリントするとЗдとなる.
&hello[0..1]とすると,最初の1byteのみ保持するようになるはずだが,それぞれの文字の境界に両端がないスライシングをしようとすると実行時にPanicを起こす.

Methods for iterating Over Strings

Stringのそれぞれの文字にアクセスできる方法は他にもある.
Stringに含まれるUTF-8のscalar value(つまり文字)に何かを実行したいときは,chars methodを使うのが一番良い.“नमस्ते”にcharsを適用すると,もとの文字列を分解して,それぞれの文字をchar型にしてくれる.

for c in “नमस्ते”.chars() {
  println!("{}", c);
}

न
म
स
्
त
े

を返す.(発音記号ごとイテレーションしているがいいのか・・・・)
一方でbytes methodはそれぞれのbyteを返す.

for b in "नमस्ते".bytes() {
    println!("{}", b);
}

shell

224
164
// etc

Strings are Not so Simple

このように,Rustでの文字列の扱いはとても複雑なことがわかっただろう.

2017年6月30日金曜日

MIT OCW 6.0002 02日目

Eric Grimson, John Guttag, and Ana Bell. 6.0002 Introduction to Computational Thinking and Data Science. Fall 2016. Massachusetts Institute of Technology: MIT OpenCourseWare, https://ocw.mit.edu. License: Creative Commons BY-NC-SA.

Lecture 3. Graph-theoretic Models

ある現実に存在するものや,抽象的な概念の集合(nodes)があって,それらの関係性(edges)を記述する方法にGraphがある.あるnodeから出るedgeを辿って他のnodeにたどり着けるとき,渡ってきたedgeたちをからへのpathという.edgeはnode間の関係性を表すが,必ずしも両方向でない関係性(好き嫌い,因果関係,所属)のような関係を記述するgraphをdirected graph(有向グラフ)といい,必ず両方向である関係(血縁,路線図)を記述するgraphをundirected graph(無向グラフ)という.undirected graphはdirected graphの特別な場合である.また,edge間に重みがつけられているgraphもある。.

特に重要なグラフの一種にtreeがある.treeでは任意の2つのedgeが一意のpathで結ばれている.


The network graph formed by Wikipedia editors (edges) contributing to different Wikipedia language versions (vertices) during one month in summer 2013
The network graph formed by Wikipedia editors (edges) contributing to different Wikipedia language versions (vertices) during one month in summer 2013

早速graphを実装する.

class Node(object):
  def __init__(self, name):
    self.name = name
  def getName(self):
    return self.name
  def __str__(self):
    return self.name

class Edge(object):
  def __init__(self, src, dest):
    """ Assumes src and dest are nodes"""
    self.src = src
    self.dest = dest
  def getSource(self):
    return self.src
  def getDestination(self):
    return self.dest
  def __str__(self):
    return self.src.getName() + '->' \
            + self.dest.geName()

class Digraph(object):         # directed graphのクラス
  """
  edge is a dict mapping each node
  to a list of its children.
  """

  def __init__(self):
    self.edges = {}
  def addNone(self, node):
    if node in self.edges:
      raise ValueError('Duplicate node')
    else:
      self.edges[node] = []

  def addEdge(self, edge):
    src = edge.getSource()
    dest = edge.getDestination()
    if not (src in self.edges and dest in self.edges):
      raise ValueError('Node not in graph')
    self.edges[src].append(dest)

  def childrenOf(self, node):
    return self.edges[node]

  def hasNode(self, node):
    return node in self.edges

  def getNode(self, name):
    for n in self.edges:
      if n.getName() == name:
        return n
      raise NameError(name)

  def __str__(self):
    result = ''
    for src in self.edges:
      for dest in self.edges[src]:
        result = result + src.getName() + '->'\
                + dest.getName() + '\n'

    return result[:-1] # omit final newline

  class Graph(Digraph):
    """
    transform a directed graph to a undirected graph
    """

    def addEdge(self, edge):
      Digraph.addEdge(self, edge)
      rev = Edge(edge.getDestination(), edge.getSource())
      Digraph.addEdge(self, rev)

古典的なグラフ最適化問題に,最短経路問題がある.node1, node2を結ぶ最短のpathを見つける問題である.ループ構造がある可能性があるので,無限ループを避ける工夫が必要.depth-first search(深さ優先探索)breadth-first search(幅優先探索)がある.

初期nodeから辿れるpathを目的nodeに到達するか,到達しないと判明するまで(ループ構造は,辿ってきたpathを保存しておいて照合することで回避する)辿る.

def DFS(graph, start, end, path, shortest, toPrint=False):
  path = path + [start]
  if toPrint:
    print('Current DFS path:', printPath(path))
  if start == end:
    return path
  for node in graph.childrenOf(start):
    if node not in path: #avoid cycles
      if shortest == None or len(path) < len(shortest):
        newPath = DFS(graph, node, end, path, shortest, toPrint)
        if newPath != Node:
          shortest = newPath
        elif toPrint:
          print('Already visited', node)
  return shortest


def  shortestPath(graph, start, end, toPrint=False):
  """ a wrapper function of DFS"""
  return DFS(graph, start, end, [], None, toPrint)

Breadth-first Search(BFS)

初期nodeから長さnのpathのnodeに目的nodeがあるかすべて探索してから,長さn+1のpathのnodeたちを調べる.depth-first searchよりもふつう低速である.

def BFS(graph, start, end, toPrint=False):
  initPath =  [start]
  pathQueue = [initPath]
  while len(PathQueue) != 0:
    tmpPath = pathQueue.pop(0)
    if toPrint:
      print('Current BFS path:', printPath(tmpPath))
      lastNode = tmpPath[-1]
      if lastNode == end:
        return tmpPath
      for nextNode in graph.childrenOf(lastNode):
        if nextNode not in tmpPath:
          newPath = tmpPath + [nextNode]
          PathQueue.append(newPath)
  return None