Rustを放棄した理由







C ++のようなパフォーマンスで、ガベージコレクターのない新しいシステムレベルのプログラミング言語が登場したことがわかり、すぐに興味を持ちました。 C#やJavaScriptなどのガベージコレクターを使用して言語を使用することで問題を解決したいのですが、C ++の生で強引な力の考えに常に悩まされていました。 しかし、C ++では、自分自身を撃ち殺す方法や他のよく知られている問題がたくさんあるので、通常はあえてしませんでした。







だから私は錆になった。 そして、いまいましい、深く登った。







さびはまだ非常に若いため、その生態系はまだ初期段階にあります。 場合によっては、たとえば、 Webソケットまたはシリアル化の場合、適切で一般的なソリューションがあります。 他の地域では、Rustはそれほどうまくいっていません。 そのような領域の1つは、 CEGUInanoguiなどのOpenGL GUI です 。 私はコミュニティと言語を助けたかったので、C / C ++バインディングなしで、純粋なRustコードでnanoguiをRustに移植し始めました。 このプロジェクトはここにあります







通常、Rustとの知り合いは、ボローチェッカーのアイデアとの闘いから始まります。 他のプログラマーと同じように、この問題やその問題を解決する方法が分からない時期もありました。 幸い、 #rust-beginnersにはクールなコミュニティがあります。 その住民は私を助け、私の愚かな質問に答えました。 Rustで多少なりとも快適に感じるまでに数週間かかりました。







しかし、問題に直面したとき、解決策を見つけることはジャングルのオリエンテーションに似ているとは思わなかった。 多くの場合、問題の解決策のように見えますが 、詳細が小さいため適合しないいくつかの回答があります。







以下に例を示します。ベースWidgetクラスがあり、ウィジェット自体(Label、Button、Checkbox)に共通の簡単にアクセスできる機能を持たせたいとします。 C ++やC#などの言語では、これは簡単です。 言語に応じて抽象クラスまたは基本クラスを作成し、そこからクラスを継承する必要があります。







public abstract class Widget { private Theme _theme { get; set; } private int _fontSize { get; set; } public int GetFontSize() { return (_fontSize < 0) ? _theme.GetStandardFontSize() : _fontSize; } }
      
      





Rustでは、このために特性を使用する必要があります。 ただし、特性は内部実装について何も知りません。 トレイトは抽象関数を定義できますが、内部フィールドにはアクセスできません。







 trait Widget { fn font_size(&self) -> i32 { if self.font_size < 0 { //compiler error return self.theme.get_standard_font_size(); //compiler error } else { return self.font_size; //compiler error } } }
      
      





» インタラクティブなサンドボックスで実行する







考えてみてください。 私の最初の反応は「あれ、何?!」でした。 もちろん、OOPにはかなりの批判がありますが、そのような決定はばかげています。







幸いなことに、変更要求の助けを借りて言語が変化し改善されていることがわかりました。このプロセスは十分に確立されています。 そのような実装は言語を大きく制限すると信じているのは私だけではありません。そして今、この愚かさを改善するために設計されたオープンなRFCがあります 。 しかし、プロセスは2016年3月から続いています。特性の概念は、長年にわたって多くの言語で存在していました。 今は2016年9月です。なぜ言語のこのような重要かつ必要な部分が依然として嘆かわしい状態にあるのですか?







場合によっては、型ではなくオブジェクト自体に実装されている型に関数を追加し、それを使用して実際の関数にアクセスすることで、この制限を回避できます。







 trait Widget { fn get_theme(&self) -> Theme; fn get_internal_font_size(&self) -> i32; fn get_actual_font_size(&self) -> i32 { if self.get_internal_font_size() < 0 { return self.get_theme().get_standard_font_size(); } else { return self.get_internal_font_size(); } } }
      
      





» インタラクティブなサンドボックスで実行する







しかし、今ではパブリック関数があり(型関数はインターフェイスのように動作し、型関数をmod専用としてマークする方法はありません)、すべての特定の型で実装する必要があります。 したがって、抽象関数を使用せずに大量のコードを複製するか、上記のアプローチを使用して複製を少し減らしますが、コードが多すぎて穴のあいたAPIを取得します。 両方の結果は受け入れられません。 そして、これはC ++、C#などの確立された言語のいずれにも見られず、Goでさえ、通常の解決策があります。







別の例。 nanogui(CEGUIでは、この概念も使用されます)では、各ウィジェットには親へのポインターとその子孫へのポインターのベクトルがあります。 これはRustでどのように実装されていますか? いくつかの答えがあります:







  1. Vec<T>



    実装を使用する
  2. Vec<*mut T>



  3. Vec<Rc<RefCell<T>>>



  4. Cバインディングを使用する


方法1、2、および3を試してみましたが、それぞれに使用が受け入れられない短所がありました。 現在、オプション4を検討しています。これが最後のチャンスです。 すべてのオプションを見てみましょう。







オプション1



Rustの初心者はこのオプションを選択します。 私はそうしましたが、すぐに借用チェッカーで問題に遭遇しました。 この実施形態では、ウィジェットは、その子孫および親の所有者でなければならない。 親と子は互いの所有権の循環リンクを持っているため、これは不可能です。







オプション2



これが私の2番目の選択肢でした。 そのプラスは、nanoguiで使用されるC ++スタイルに似ていることです。 いくつかの欠点があります。たとえば、ライブラリ内外のあらゆる場所で安全でないブロックを使用することです。 また、ボローチェッカーはポインターの有効性をチェックしません。 しかし、主なマイナス点は、カウンターオブジェクトを作成できないことです。 C ++のスマートポインター、またはRustのRcタイプに相当するものではありません。 つまり、オブジェクトが何回指摘されたかをカウントし、カウンターがゼロに達するとそれ自体を削除するオブジェクトを意味します。 以下は 、nanogui実装のC ++のです。







このことを機能させるには、オブジェクト内からのみ自分自身を削除できることをコンパイラーに伝える必要があります。 例を見てみましょう:







 struct WidgetObj { pub parent: Option<*mut WidgetObj>, pub font_size: i32 } impl WidgetObj { fn new(font_size: i32) -> WidgetObj { WidgetObj { parent: None, font_size: font_size } } } impl Drop for WidgetObj { fn drop(&mut self) { println!("widget font_size {} dropped", self.font_size); } } fn main() { let mut w1 = WidgetObj::new(1); { let mut w2 = WidgetObj::new(2); w1.parent = Some(&mut w2); } unsafe { println!("parent font_size: {}", (*w1.parent.unwrap()).font_size) }; }
      
      





» インタラクティブなサンドボックスで実行する







出力は次のようになります。







 widget font_size 2 dropped parent font_size: 2 widget font_size 1 dropped
      
      





これは、メモリは削除後にリセットされないため、use after freeエラーが表示されないようにするために必要です。







そのため、このようなカウンターを正しく実装するには、メモリをグローバルに予約する必要があります。 変数がスコープ外になったときに自動的に変数を削除しないようコンパイラーに指示する簡単な方法はありません。







いいですね ご存じのとおり、Rust。 Rustで円形の有向グラフを実装する慣用的な方法は何ですか?







オプション3



最終的に、私はrust-forestと呼ばれる木を作成するための良いライブラリを見つけました。 ノードを作成し、スマートポインターでノードをポイントし、ノードを挿入および削除することができます。 ただし、実装では、異なるタイプTのノードを単一のグラフに追加することはできません。これは、nanoguiなどのライブラリの重要な要件です。







このインタラクティブな例をご覧ください。 少し長いので、記事に完全なリストを直接追加しませんでした。 この関数の問題は次のとおりです。







 // Widget is a trait // focused_widgets is a Vec<Rc<RefCell<Widget>>> fn update_focus(&self, w: &Widget) { self.focused_widgets.clear(); self.focused_widgets.push_child(w); // This will never work, we don't have the reference counted version of the widget here. }
      
      





» インタラクティブなサンドボックスで実行する







ちなみに、この奇妙なことは回避できますが、なぜこれが問題なのかはまだわかりません。







 let refObj = Rc::new(RefCell::new(WidgetObj::new(1))); &refObj as &Rc<RefCell<Widget>>; // non-scalar cast
      
      





» インタラクティブなサンドボックスで実行する







おわりに



方法1、2、および3を実装するときに遭遇した問題により、Csの束を持つ4番目のオプションが私のタスクに適した唯一の方法であると思います。 そして今、私は考えます-あなたがCですべてを書くことができるのに、なぜCで束を作るのですか? またはC ++?







Rustプログラミング言語には、いくつかの優れた機能があります。 マッチの仕組みが大好きです。 Goのインターフェイスだけでなく、トレイトの一般的なアイデアも気に入っています。 貨物パッケージマネージャーが好きです。 しかし、特性の詳細、参照カウント、およびコンパイラの動作をオーバーライドできないことの実装に関しては、ノーと言わざるを得ません。 私には似合わない。







人々がRustを改善し続けることを心から願っています。 しかし、私はゲームを書きたいです。 コンパイラを無効にするか、RFCを作成して、言語を自分のタスクにより適したものにしようとする代わりに。







翻訳者のメモ







著者が「 このようなカウンタを正しく実装するには、グローバルにメモリを予約する必要がある 」と言ったとき、著者が何を意味するのか理解できませんでした。 また、関数の完了後に変数を保存する場合は、動的メモリに変数を配置する必要がありますか?







さらに、「 変数がスコープ外になったときに自動的に変数を削除しないようにコンパイラに指示する簡単な方法はありません 」-これは、間違ったステートメントのようです。 関数std :: mem :: forgetは 、このために特別作成されたためです(redditの説明から)。







良い記事の議論:










All Articles