RustでProject Euler (2)

引数

当然1000は固定でなく、引数で指定したいですよね。そのようにコードを変更してみました。

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let n: i64 = args[1].parse().unwrap();
    let mut s = 0;
    for i in 1..n {
        if i % 3 == 0 || i % 5 == 0 {
            s += i;
        }
    }
    println!("{}", s);
}
use std::env;

モジュールをインポートしているようです。std::envは引数を取るのに必要です。

    let args: Vec<String> = env::args().collect();

引数を配列に格納しています。

env:argsは、引数のiteratorを返すんですね。それをcollectで配列にすると。
ここで、Vecは可変長配列で、その中身の方がStringというわけです。

エラー処理

    let n: i64 = args[1].parse().unwrap();

parseで文字列を整数にするわけですが、もし整数にならなかった場合、Rustでは例外を投げるのではなく、Errを返します。
より正確には、unwrapはここではResult<i64,E>を返します。Result<T>は、Err<E>か、Ok<T>のどちらか取ります。Haskellのようにマッチングが使えて、

    let n: i64;
    match args[1].parse() {
        Ok(val) => n = val,
        Err(err) => panic!("error : {}", err),
    }

ここで、整数にならない文字列を引数にすると、

$ ./e001a 1000.
thread 'main' panicked at 'error : invalid digit found in string', e001a.rs:8:15
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

と、Err(err)にマッチしてそれ以降を処理するわけです。
ただ、たかがProject Eulerの問題を解くためにこんなエラー処理をしているわけにいかないので、Ok(val)であると仮定して、unwrapでひっぺ返して、valにするわけです。最初のコードだと、

$ ./e001a 1000.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', src/libcore/result.rs:1165:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

よく分からないところでエラーになってしまいます。

速度

さて、ここでC++と速度を比較してみましょう。C++で同様に書いて、

#include <iostream>

using namespace std;

int main(int argc, char **argv) {
    const int   n = atoi(argv[1]);
    long long   s = 0;
    for(int i = 1; i < n; ++i) {
        if(i%3 == 0 || i%5 == 0) {
            s += i;
        }
    }
    cout << s << endl;
}
$ g++ -O2 e001.cpp
$ time ./a.out 1000000000
233333333166666668

real    0m1.133s
user    0m1.125s
sys     0m0.000s

Rustも最適化オプションをつけて、

$ rustc -O e001a.rs
$ time ./e001a 1000000000
233333333166666668

real    0m1.221s
user    0m1.203s
sys     0m0.000s

差は確かにあるようですね。