Neunomizuの日記

俺だけが俺だけじゃない

Rust以外無意味

tags: 情報

この記事はeeic (東京大学工学部電気電子・電子情報工学科) Advent Calendar 2019の11日目の記事として書かれました

Rustとは

公式HPによれば

  • Performance
  • Reliability
  • Productivity

という長所がある言語なようです

下のように

Rust以外は無意味なのです

C++でバリバリにコードを書いたことがないとそのありがたみがわからないよ的な文章も読みました

しかし,そう言われても実際にどういうものかわからないとこれが真実かイキリかどうか見分けがつきません

ということで実際にドキュメントを読んで短いコードを書いてみることにしました

どのドキュメントを読んだのか

公式のレポジトリにあるものを読みました

の3種類があります

Nightly(毎日更新)->Beta(6週間でNightlyから昇格)->Stable(6週間でBetaから昇格)という風になっているようです

私はStableを読みました

日本語版は2018年Editionに対応していないのでまだ読まないほうが良いと思ったので英語の方を読みました

読んだ感想

  • 競プロをするだけなら1~9章と10,12,13,18章を読んで後は他人のコードを見ながらコードを書けばなんとかなりそうな気がします
  • コンパイラがめちゃくちゃ賢い色々なパラダイムを含む言語だと思いました
  • CやC++を書いたことがない人(メモリに対する知識がない人)がいきなりRustを書くのは大変な気がします
    • Rustでコードを書こうとするとownership,borrowing,lifetimesを常に意識する必要がります
    • これらを理解するのにメモリに関して具体的にイメージできる必要があるように思えます
      • そして,そういう勉強をした人はCやC++を学んだ経験があるはずなので初心者がやることがそもそもなさそうに感じます
      • 初心者がやるとコンパイラが優しすぎて,挫折してしまいそうです
    • という理由で文系学部などから来たWeb系に来た人がいきなりこれを書くのも大変そうだなという感想を持ちました
  • バグの温床になりそうな部分を片っ端から潰している言語で使えるようになったら楽しそうです
  • Rustで書いている人が強いという話は本当っぽいなぁと思いました(ただなんちゃってみたいな人は除く)

読んでとりあえずコードを書く

Rustという言語の強みはコンパイラと言いました.これは本当でコンパイラが少しでもバグの原因になりそう!というコードを見つけたら教えてくれます

ということで適当に書いてみます

フィボナッチ数

プログラミング言語の勉強をする時,最初は再帰関数の練習としてフィボナッチ数を書きますよね(?)

ということで次のような感じで0~10のフィボナッチ数は書けます

fn main() {
    println!("!Fibonacci!");
    for i in 0..11 {
        println!("{}th Fibonacci number is {}", i, fibonacci(i));
    }
}

fn fibonacci(n: u32) -> u32 {
    if n == 0 || n == 1 {
        1
    } else {
        fibonacci(n - 1) + fibonacci(n - 2)
    }
}

実行するとこんな感じに出力されます

!Fibonacci!
0th Fibonacci number is 1
1th Fibonacci number is 1
2th Fibonacci number is 2
3th Fibonacci number is 3
4th Fibonacci number is 5
5th Fibonacci number is 8
6th Fibonacci number is 13
7th Fibonacci number is 21
8th Fibonacci number is 34
9th Fibonacci number is 55
10th Fibonacci number is 89

このコードを見ると

「あれ?そんなに難しくじゃん」

と思うかも知れません.しかし,後ほんの少し複雑なコードを書くと困ったことになります

Hashmapを使った問題

LeetCodeで一番解かれている問題を使います

めちゃくちゃ簡単なこの問題ですが,Rustで書くと落とし穴が何個か有ります

問題の概要はある整数列が与えられその値から2つの異なる値を足し合わせてtargetという目的の数になること

解答は

  • map<target - num0, num1>というHashMapを作り,この値があればそれを出力し,なければ新しくkeyとvalueを新しく格納する

というものです

下のように書きました

use std::collections::HashMap;
impl Solution {
    pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {        
        let mut map: HashMap<i32, i32> = HashMap::new();
        let mut res: Vec<i32> = Vec::new();
        
        for (i, num) in nums.iter().enumerate() {
            if (map.contains_key(&(target - num))) {
                res.push(map[&(target - num)]);
                res.push(i as i32);
                return res;
            }
            map.insert(*num, i as i32);
        }
        return res;
    }
}

この短いコードの相田に以下のような点に注意する必要が有ります

  • map.contains_key(&(target - num))に関して
    • ここではHashMapの仕様上&を付けてborrowする必要がある(他はダメ)
  • map.insert(*num, i as i32)に関して
    • numnums.iter().enumerate()から取り出しており,borrowされており,*を付けてdereferenceをしないとinsertできないため(これはHashMapinsertする際にownershipが移ることから)
    • i as i32iのままだとusize(符号なし)というデータ型なので出力形式とマッチしないから

このように実行時のバグをなくすためにコンパイル時に指摘しまくります

自分もあまり理解しておらず理解が浅いうちはコンパイラにお世話になりっぱなしだと思います

結論

Rust以外無意味ではなくてRust(が書けないエンジニアはコンピュータを分かったつもりで書いているので)無意味ということなのかなと思いました

C++やCをわかっていない人が書いても確かに恩恵が分かりにくそうだと感じました.というのも一度メモリに触れておかないとコンパイラに頼りっぱなしでごちゃごちゃやって理解した気になってしまいそうだと感じたからです

基本大事ダネ☆

次回

C言語以外無意味

続く...