@@ -25,14 +25,17 @@ AtCoderは、オンラインで参加できるプログラミングコンテス
25
25
26
26
AtCoderで使える言語は非常にたくさんあります。どの言語を使ってもよいですし、問題によって使い分けても構いません。その中でなぜRustを使うのか、そのメリットとデメリットをまとめてみました。できるだけ一般論で比較するよう心がけますが、競技プログラミングにおけるC++人口がそれなりに多いことと、Rustはその特性上C++と比較されることが多いので、具体的にC++との比較になっている部分も多くあります。
27
27
28
+
28
29
### メリット
29
30
31
+
30
32
#### 高速である
31
33
32
34
AtCoder含め、競技プログラミングでは「実行時間制限」とよばれるものがあります。この時間内にプログラムの実行が終わらないと「TLE (Time Limit Exceeded)」という判定が付いて誤答扱いとなります。多くの場合は想定されている解法であれば多少の余裕をもって解けるように設定されていますが、非常にたくさんの言語が使える都合上全ての言語で公平になるようにはできません。遅い言語に合わせて設定すると速い言語では強引な解法でゴリ押しできてしまうことがありますし、速い言語に合わせると遅い言語では想定されている解法でも通せないということになります。いずれにせよ、基本的には速い言語であるほうが計算時間的には有利です。 (もちろん遅い言語と言われるものにも、例えば書き易さであったり、ライブラリが充実していたり、なにかしらのメリットがあるはずです。どちらかが絶対的に有利ということではありません。)
33
35
34
36
Rustは最速と言われるC/C++並みに速いとされていますので、(少なくともAtCoderでは) 速度面で不利になることはないと言えるでしょう。
35
37
38
+
36
39
#### 信頼性が高い
37
40
38
41
信頼性は、ここではRustの[ 公式トップページ] ( https://www.rust-lang.org/ ) に倣いメモリ安全性、スレッド安全性、バグの起こしにくさであるとします。競技プログラミングで特に大事になってくるのはメモリ安全性とバグの起こしにくさです。
@@ -97,6 +100,7 @@ stack backtrace:
97
100
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
98
101
```
99
102
103
+
100
104
#### 多くの問題をコンパイル時に発見できる
101
105
102
106
RustはC/C++並みの速度を確保するため、実行時にやらなければいけないことをなるべく減らす方針の言語です。たとえば先ほど触れたように、多くの言語にあるガベージコレクタがありません。それだけならばC++と変わりありませんが、速度と安全性を両立させるためにRustではできるだけ多くのことをコンパイル時に確認する仕組みになっています。C++が受け入れてしまうような危険なコードもコンパイルエラーにします。
@@ -136,6 +140,7 @@ let x: i32 = s.into_iter().sum();
136
140
137
141
ジェネリクスとトレイトの仕組みも強力です。たとえばジェネリックな関数が型変数`T`をもつとき、この`T`のとりうる型を特定のトレイト (=機能一覧) を実装しているものだけに制限することができます。逆に`T`に対してできることはその特定のトレイトが定める機能のみです。従って、一度コンパイルが通った関数はその制約を満たす限りのどのような`T`を与えても関数の内部でコンパイルエラーとなることはありません。特にライブラリを整備するにあたってはこれはとてもありがたいことです。実際に使ってみなくても、コンパイルさえ通れば、将来的に作られうるどんなユーザー定義型を与えようともその関数が正しく呼び出せることが保証されます。C++のテンプレートなどでは実際に具体的な型を与えて始めて様々な検証をするので、使う段階になってからでないとエラーが発見できません。このことは、後述するコンパイルエラーの分かりやすさにも繋がっています。
138
142
143
+
139
144
#### コンパイルエラーが分かりやすい
140
145
141
146
これは少々主観的な話になるのかもしれませんが、Rustのコンパイルエラーは読みやすく分かりやすいという評判があります。実際にコンパイルエラーが発生したとき、まずエラーが起きた場所はもちろんとして、エラーが関連する他の場所 (例えば以前に借用された場所など) などをアスキーアート的な手法で視覚的に分かりやすく表示してくれます。さらに、なぜそれが間違っているのか/それをどのように修正することができるかのヒントが提示されることもあります。例えば、先ほどのエラー全体は次のようになっていました。
@@ -185,6 +190,7 @@ fn main() {
185
190
(以下略)
186
191
``````
187
192
193
+
188
194
#### 抽象化のための機能を数多く備えている
189
195
190
196
例えば次のようなさまざまな機能があります。Rustは後発の言語ですので、他のプログラミング言語に備わっている優れた機能も参考にして多数の機能が導入されています。
@@ -268,6 +274,7 @@ fn main() {
268
274
269
275
また、変数の個数を抑止するという効果もあります。block expression 等と適宜組み合わせることで変数の数やスコープはさらに小さく保つことができ、多少関数の実装が長くなっても見通しが悪くなりにくいと言えます。競技プログラミングではmain 関数が長くなりがちなので一層嬉しいのではないでしょうか。
270
276
277
+
271
278
#### ゼロコスト抽象化を追求している
272
279
273
280
Rust の言語デザインやライブラリは、一定の使いやすさを実現しつつも、使いやすさのために実行時の高速性を犠牲にはしないという** ゼロコスト抽象化** (_zero - cost abstraction_ )を追求しています。
@@ -276,8 +283,10 @@ Rustの言語デザインやライブラリは、一定の使いやすさを実
276
283
277
284
このことは「簡潔な構文や関数によってその機能が必要とするコストを隠してしまう」ことを避けているとも言えます。つまり、本当にパフォーマンスが必要なときに最適化を検討するべき「コスト」の部分が明確化されているということでもあります。
278
285
286
+
279
287
### デメリット
280
288
289
+
281
290
#### 現れる概念が比較的難しい
282
291
283
292
先に見たように、Rust では、いままで他の言語ではコンパイラが検証していなかったようなことをコンパイル時に検証します。そのためにRust では所有権や借用をはじめとする独特の概念が導入されており、それらの概念の理解そのものが難しいとされることも多いようです。これらの概念が課す多数のルールがなぜ存在するのかを理解することは、仕組みをある程度理解していなければ難しいものです。
@@ -286,6 +295,7 @@ Rustの言語デザインやライブラリは、一定の使いやすさを実
286
295
287
296
Rust が課すルールにも理由がありますので、そういった事情について意識的に考えることは他の言語や競技プログラミング以外の文脈でも活きる有意義なものではあると思います。単にAtCoder である程度の競技プログラミングをするだけであれば、C #やJava といった言語でもほぼ正解できるよう調整されているようなので、どちらを取るかは好みといっていいかもしれません。
288
297
298
+
289
299
#### 素早く書くことにはあまり向かない仕様
290
300
291
301
Rust の安全指向や標準ライブラリの設計方針などは、時間をかけて大規模なプログラムを書くときや堅牢なプログラムを書くときには非常に役に立ちます。一方で競技プログラミングでは、一般のプログラミングと異なり、次のような特徴があります。
@@ -341,12 +351,14 @@ fn main() {
341
351
342
352
Rustで参加する競技プログラマーの中には、こうした煩雑さを改善するためのマクロやヘルパ関数 (もっと便利に標準入力がとれるようにするなど) を定義し、テンプレート (ひな型) として用意している方もいます。インターネット上で公開されている方もいらっしゃいますし、過去のコンテストでの上位Rust参加者の提出などをのぞいてみると、いろいろと参考になるかもしれません。
343
353
354
+
344
355
#### 標準ライブラリが小さい
345
356
346
357
Rustは比較的新しい言語ですので、インターネット接続環境を前提にしたパッケージ管理システムCargoを標準で持ちます。このため、言語の成長とクレートやRustエコシステムの成長を分離することを目的に、Rustは標準ライブラリを最低限の抽象化とインターフェースとして位置付け、できるだけ小さく保ち続けてきました。かつて標準ライブラリの一部だったり本体にバンドルされていたライブラリ (`num`, `rand`, `regex` など) を積極的に分離することさえしています。ユーザーはCargoを使えば、使いたいパッケージを[crates.io](https://crates.io)からいつでも自由にダウンロードできます。
347
358
348
359
(TODO: クレートが導入されたかどうか追記)
349
360
361
+
350
362
#### コンパイル時間が長くなりがち
351
363
352
364
様々な解析をコンパイル時に行う都合上、コンパイル時間が長めにかかる傾向があります。Rustではコンパイル速度を速くすることはあまり重要視されていません。特に手元で提出をテストする際、外部クレートを利用するならその外部クレートのビルドも実行することになります。二回目以降のビルドではビルドキャッシュを利用するためコンパイルする必要はありませんが、初回の実行では利用する外部クレートによっては数分単位の時間をとられる可能性があります。つまり、素早く書き上げたコードを手元で軽く実行してみることにすら時間をとられてしまい、提出時刻が数分遅れてしまうということがあり得ます。結果的に手元でコンパイルが通るかどうかをチェックする時間すら惜しいとなってしまうと本末転倒です。なお、外部ライブラリを含むパッケージをコンテスト開始前に一回ビルドしておいて、競技時はそのフォルダをコピーして編集するというふうにすれば回避できます。
0 commit comments