Rustプログラミング言語の10章練習問題をやってみた

技術,プログラミング言語,練習問題

TL;DR

プログラミング言語Rustの10章練習問題をやってみました。Rust初心者なので回答内容は保証しませんが、参考まで。

ついでにトレイトがトレイトではないという話(伝聞ですが)も書いてみました。

第10章

10.2.トレイトでは練習問題とはうたっていません。しかし、「これらの代替策をご自身で実装してみましょう」とあるので、実装してみました。

代替案1.CopyトレイトではなくCloneトレイトを使う

CopyトレイトがダメならCloneトレイトにすればよいじゃない、というわけで、実際にCloneトレイトに変更してみます。

fn largest<T: PartialOrd + Clone>(list: &[T]) -> T {
    let mut largest = list[0].clone();
    for item in list {
        if *item > largest {
            largest = item.clone();
        }
    }
    largest.clone()
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!("The largest number is {}", result); // -> 100

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result); // -> y

    let string_list = vec![String::from("abc"), String::from("xyz"), String::from("ghi")];
    let result = largest(&string_list);
    println!("The largest string is {}", result);
}

まずは1行目のlargest関数のトレイトをCopyからCloneへ。2行目と5行目のlistの要素をlargestに代入するところは、clone()でコピーするように変更しました。

4行目のif文は元々は参照同士で比較していましたが、largestが参照ではなくてT型の値になったので、itemは参照外ししたもので比較に変更。元のリストが昇順に並んでいたりすると、各要素でコピーが発生する羽目に。その都度、ヒープ領域の確保と値のコピーが発生するので、コストが大きくなります。逆に降順に並んでいた場合は一度のヒープ領域の確保とコピーだけで済みます。

代替案2.返り値型をTではなくて&Tにする

続いて参照を返すバージョン。

fn largest<T: PartialOrd + Clone>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list.iter() {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!("The largest number is {}", result); // -> 100

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result); // -> y

    let string_list = vec![String::from("abc"), String::from("xyz"), String::from("ghi")];
    let result = largest(&string_list);
    println!("The largest string is {}", result);
}

元のプログラムとほとんど同じですが、参照を返すようになっています。

混乱してしまうのは、型が何かという部分。2行目はlistがT型のスライスで、その0番目の要素はT型。そのままlargestに代入するとムーブになるので"&"を付けて参照にしています。

3行目のfor文でlistの各要素の参照がitemに入るので、4行目で同じくT型の参照であるlargestとはそのまま比較。最終的にlargestをそのまま返しています。ヒープの確保が不要なので、コストもCloneトレイト版に比べて小さく抑えられることになります。

どちらもサラッと書いてますが、実際にはこの形にするまでに型や参照の扱いで混乱して、あーでもないこーでもないと紆余曲折を経ています。慣れればスッと出てくるのかと思いますが、まだまだRustの考え方に慣れていないようです。

トレイト

10章ではジェネリック、トレイト、ライフタイムを扱ってますが、演習問題があるのはトレイト。補足で「他の言語でインターフェイスと呼ばれる機能に類似」とあります。トレイト(trait)とは特性とか特徴と訳すようです。日本語訳では型の振る舞いと説明しています。

類似していると言いつつ、「ここは似ているがここは違う」や、インターフェイスに比べてトレイトはこういうところが便利、みたいな説明がないのは平常運転。そういうところがRustはとっつきにくいと言われる所以のような。まあ、特定の言語と比較して優劣を語るとそれはそれで炎上しかねないかもしれませんけど。

調べてみるとトレイトという考え方自体はRust特有ではないそう。と言うより、いわゆるトレイトとインターフェイスの中間というか、「トレイトのようなもの」という扱いみたいで、厳密にはオブジェクト指向一般でいうところのトレイトではない、らしいです。Rustのトレイトはオブジェクト指向でいうトレイトでもインターフェイスでもミックスインでもない(らしい。詳しい説明はできませんが)。

そんなわけで、Rustのトレイトは一般的なトレイトとは違うにも関わらず、その説明はしていないというJAROに訴えられそうな状況のよう。ちなみに、クロージャもRustのそれと一般的なクロージャでは違う、らしい。

そもそも論ではオブジェクト指向言語なのかも議論を読んでいる、らしい。らしいばかりですみません。まあ、何を実装すればオブジェクト指向言語なのかとか言い始めるとキリがないですが、オブジェクト指向の考え方(の一部)を取り入れた言語というのが正しいのかもしれません。

memo:Rustはオブジェクト指向言語ではない

Rustの公式サイトを見てみても、Rustがオブジェクト指向言語である、という記述はざっと流したところでは見つかりませんでした。The Rust Programming Language(TRPL or THE BOOK)でも17章で「OOPを含めた多くのプログラミングパラダイムに影響を受けている」とは記していますが、Rustがオブジェクト指向であるともそうでないとも明言はしていません。ただ、OOP的アプローチは可能ということは書いてあります。

一方、WIkipediaのRustの項でも「マルチパラダイムのプログラミング言語」であると説明されています。また、「手続き型プログラミング、オブジェクト指向プログラミング、関数型プログラミングなどの実装手法をサポートしている」とは書いてありますが、オブジェクト指向プログラミング言語であるとは書いてありません。
英語版Wikipediaに至っては、そもそもどこにも「object-oriented」という単語は入っていません。
ただ、Wikipedia日本語版のRustの項ではオブジェクト指向プログラミング言語だと説明している、と主張される方が少なくないようです。本当のところはその文章を書いた人に聞かないと分かりませんが、個人的には「考え方を取り入れている」程度の意味合いで、Rustがオブジェクト指向プログラミング言語であるとは書いてないと解釈しています。文章を見れば分かる通り、手続き型と関数型も併記されており、それらの特徴を取り入れているという意味合いと解釈するのが妥当だと自分は思います。