Rustプロジェクトの主な目標の1つは、より良い並行性のストーリーです。そのため、プロジェクトがその目標を達成することを信頼していれば、改善が期待されます。完全な免責事項:私はRustについて高い意見を持ち、それに投資しています。要求に応じて、価値判断を避け、(IMHO)改善ではなく違いを説明しようとします。
安全な錆と安全でない錆
「Rust」は2つの言語で構成されています。1つは、システムプログラミングの危険からあなたを隔離しようとする言語、もう1つはそのような願望のないより強力な言語です。
Unsafe Rustは、C ++によく似た厄介で野bruな言語です。任意に危険なことをしたり、ハードウェアに話しかけたり、手動でメモリを(誤って)管理したり、足で自分自身を撃ったりすることができます。そしてそれに関与する他のすべてのプログラマーの手。キーワードを使用してこの言語を選択するunsafe
と、CおよびC ++の場合と同様に、1つの場所で1つのミスをするとプロジェクト全体がクラッシュする可能性があります。
Safe Rustは「デフォルト」であり、大部分のRustコードは安全でありunsafe
、コード内でキーワードに言及しない限り、安全な言語を離れることはありません。投稿の残りの部分は、ほとんどの場合、その言語に関係しunsafe
ます。なぜなら、コードは、安全なRustが非常に懸命にあなたに与えるというすべての保証を破ることができるからです。反対に、unsafe
コードは悪ではなく、コミュニティによってそのように扱われません(ただし、必要でない場合は強く推奨されません)。
危険ですが、安全なコードが使用する抽象化を構築できるため、重要です。優れた安全でないコードは、型システムを使用して他の人がそれを誤用するのを防ぎます。したがって、Rustプログラムに安全でないコードが存在しても、安全なコードを妨害する必要はありません。Rustの型システムにはC ++にはないツールがあり、同時実行抽象化を実装する安全でないコードはこれらのツールを効果的に使用するため、以下の違いがすべて存在します。
差異なし:共有/可変メモリ
Rustはメッセージの受け渡しをより重視し、共有メモリを厳密に制御しますが、共有メモリの同時実行性を排除せず、共通の抽象化(ロック、アトミック操作、条件変数、同時コレクション)を明示的にサポートします。
さらに、C ++と同様に、関数型言語とは異なり、Rustは従来の命令型データ構造が本当に好きです。標準ライブラリには永続的/不変のリンクリストはありません。ありますが、C ++ std::collections::LinkedList
のようなものstd::list
で、同じ理由で推奨されませんstd::list
(キャッシュの不適切な使用)。
ただし、このセクションのタイトル(「共有/変更可能なメモリ」)を参照すると、RustにはC ++との違いが1つあります。時間。いわば「自分のスレッドのプライバシー」で好きなようにメモリを変更します。これを、共有可変メモリがデフォルトオプションであり、広く使用されているC ++と比較してください。
shared-xor-mutableパラダイムは以下の違いにとって非常に重要ですが、慣れるまでに時間がかかり、大きな制限を課すまったく異なるプログラミングパラダイムでもあります。たとえば、アトミック型(AtomicUsize
共有可変メモリの本質)など、このパラダイムをオプトアウトする必要がある場合があります。ロックは、同時読み取りおよび書き込みを除外するため、共有xor-mutableルールにも従うことに注意してください(1つのスレッドが書き込みを行っている間、他のスレッドは読み取りまたは書き込みを行えません)。
差異なし:データの競合は未定義の動作(UB)
Rustコードでデータレースをトリガーすると、C ++と同様にゲームオーバーになります。すべての賭けはオフになっており、コンパイラは好きなようにできます。
ただし、安全なRustコードにデータの競合(またはその点でのUB)がないことは強固な保証です。これは、コア言語と標準ライブラリの両方に拡張されます。unsafe
UBをトリガーする(サードパーティライブラリを含むが標準ライブラリを除く)Rustプログラムを作成できる場合、それはバグと見なされ、修正されます(これは既に何度か発生しています)。もちろん、これは、プログラムをUBで書くのは簡単なC ++とはまったく対照的です。
違い:厳格なロック規則
C ++とは異なり、錆(内ロックstd::sync::Mutex
、std::sync::RwLock
など)を所有している、それは保護のデータを。ロックを取得し、ドキュメントでのみロックに関連付けられている共有メモリを操作する代わりに、ロックを保持していない間は共有データにアクセスできません。RAIIガードはロックを保持し、同時にロックされたデータへのアクセスを許可します(これはC ++で実装できますが、std::
ロックではありません)。ライフタイムシステムにより、ロックを解除した後(RAIIガードを削除した後)データにアクセスし続けることができなくなります。
もちろん、有用なデータを含まないロック(Mutex<()>
)を持つことができ、そのロックに明示的に関連付けずにメモリを共有することができます。ただし、潜在的に非同期の共有メモリを使用するにはが必要unsafe
です。
違い:偶発的な共有の防止
共有メモリを持つことはできますが、明示的に要求した場合にのみ共有します。たとえば、メッセージの受け渡し(からのチャネルなどstd::sync
)を使用する場合、ライフタイムシステムは、データを別のスレッドに送信した後、データへの参照を保持しないようにします。ロックの背後でデータを共有するには、ロックを明示的に構築し、別のスレッドに渡します。非同期メモリを共有するにunsafe
は、を使用する必要がありますunsafe
。
これは次のポイントに結びついています。
違い:スレッドセーフトラッキング
Rustの型システムは、スレッドセーフの概念を追跡します。具体的には、Sync
特性は、データ競合のリスクなしに複数のスレッドで共有できるタイプを示し、一方Send
、あるスレッドから別のスレッドに移動できるタイプをマークします。これは、プログラム全体でコンパイラーによって強制されるため、ライブラリー設計者は、これらの静的チェックがなければ愚かなほど危険な最適化を敢行します。たとえばstd::shared_ptr
、参照カウントを操作するために常にアトミック操作を使用するC ++ は、shared_ptr
たまたま複数のスレッドで使用される場合にUBを回避します。錆はありRc
かつArc
、そののみが異なるたRc
非アトミック参照カウント操作の使用とスレッドセーフではありません(つまり、実装していないSync
か、Send
一方で)Arc
非常に似ていますshared_ptr
(および両方の特性を実装します)。
型がunsafe
同期を手動で実装するために使用しない場合、特性の有無は正しく推測されることに注意してください。
違い:非常に厳しいルール
一部のコードにデータ競合やその他のUBがないことをコンパイラーが完全に確認できない場合、期間はコンパイルされません。前述のルールや他のツールを使用すればかなり遠くまで行くことができますが、遅かれ早かれ、正しいことをしたいと思うでしょうが、コンパイラーの注意を逃れる微妙な理由があります。ロックフリーのトリッキーなデータ構造である可能性がありますが、「共有配列のランダムな場所に書き込みますが、インデックスはすべての場所が1つのスレッドのみによって書き込まれるように計算される」という平凡なものにもなります。
その時点で、弾丸を噛んで不要な同期を少し追加するか、コンパイラーがその正確性を確認できるようにコードを言い換えることができます(多くの場合、実行可能、時には非常に困難、時には不可能)unsafe
。それでも、それは余分なメンタルオーバーヘッドであり、Rustはunsafe
コードの正確性については保証しません。
違い:少ないツール
前述の違いのため、Rustでは、データの競合(または解放後の使用、二重解放、または...)が発生する可能性のあるコードを記述することは非常にまれです。これは素晴らしいことですが、そのようなエラーを追跡するためのエコシステムは、若さとコミュニティの規模が小さいことを考えると、予想よりもさらに開発が遅れているという不幸な副作用があります。
valgrindやLLVMのスレッドサニタイザーなどのツールは原則としてRustコードに適用できますが、これが実際に機能するかどうかはツールによって異なります(特に機能するものは、 -それを行う方法に関する日付のリソース)。Rustが現在、実際の仕様、特に正式なメモリモデルに欠けていることは、実際には役に立ちません。
要するに、unsafe
Rustコードを正しく記述することは、C ++コードを正しく記述することよりも困難です。ただし、両方の言語は機能とリスクの点でほぼ同等です。もちろん、これは、典型的なRustプログラムに含まれるunsafe
コードの割合が比較的小さいのに対し、C ++プログラムは完全にC ++ であるという事実に対して重み付けする必要があります。