Rustの最初のステップ

画像







みなさんこんにちは。 最近、新しいプログラミング言語Rustに出会いました。 彼は私が以前に出会った他の人とは違うことに気付きました。 したがって、私はより深く掘り下げることにしました。 結果と私の印象を共有したい:









Javaで約10年間書いていることをすぐに説明するので、鐘楼から推論します。







キラー機能



Rustは、C / C ++などの低レベル言語と高レベルのJava / C#/ Python / Rubyなどの低レベル言語の中間的な位置を占めようとしています... しかし、メモリに完全にアクセスできると、足を撃ちやすくなります。 C / C ++とは対照的に、Python / Javaおよびその他すべてが登場しました。 彼らは記憶を消去することを考える必要はありません。 最悪の事態はNPEであり、リークはそれほど一般的ではありません。 しかし、これが機能するためには、少なくとも、ガベージコレクターが必要です。ガベージコレクターは、ユーザーコードと並行して自分の生活を始め、その予測可能性を低下させます。 仮想マシンはプラットフォームに依存しませんが、どれだけ必要かは重要なポイントなので、今は上げません。







Rustは低レベル言語であり、コンパイラーはバイナリーを出力します。これは、動作するために追加のトリックを必要としません。 不要なオブジェクトを削除するためのすべてのロジックは、コンパイル時にコードに統合されます。 実行時にガベージコレクターもありません。 また、Rustにはnull参照がなく、型は安全であるため、Javaよりも信頼性が高くなります。







メモリ管理の中心にあるのは、オブジェクト参照を所有して借りることです。 各オブジェクトを所有する変数が1つだけの場合、ブロックの最後で有効期限が切れると、その変数が示すすべてのものを再帰的にクリアできます。 リンクは、読み取りまたは書き込み用に借りることもできます。 ここでは、1人の作家と多くの読者の原則が機能します。







この概念は、次のコードで示すことができます。 Test ()は、デストラクタインターフェイスを実装する再帰的なデータ構造MyStructを作成するmain()メソッドから呼び出されます。 ドロップを使用すると、オブジェクトを破棄する前に実行するロジックを設定できます。 これはJavaのファイナライザに多少似ていますが、Javaとは異なり、 drop()メソッド呼び出しの時間は非常に確実です。







fn main() { test(); println!("End of main") } fn test() { let a = MyStruct { v: 1, s: Box::new( Some(MyStruct { v: 2, s: Box::new(None), }) ), }; println!("End of test") } struct MyStruct { v: i32, s: Box<Option<MyStruct>>, } impl Drop for MyStruct { fn drop(&mut self) { println!("Cleaning {}", self.v) } }
      
      





結論は次のとおりです。







 End of test Cleaning 1 Cleaning 2 End of main
      
      





つまり test()を終了する前に、メモリは再帰的にクリアされました。 コンパイラは、必要なコードを挿入することでこれを処理しました。 BoxOptionとは、後ほど説明します。







このようにして、Rustは高レベル言語のセキュリティと低レベルプログラミング言語の予測可能性を利用します。







他に何が面白い



次に、言語の機能を重要度の高い順にリストします。







おっと



ここで、Rustは一般的に他の製品よりも優れています。 ほとんどの言語が多重継承を放棄しなければならないという結論に達した場合、Rustでは継承はまったくありません。 つまり クラスは任意の数のインターフェイスのみを実装できますが、他のクラスから継承することはできません。 Javaの観点では、これはすべてのクラスをfinalにすることを意味します。 一般に、OOPを維持するための構文の多様性はそれほど大きくありません。 おそらくこれは最高です。







データを結合するために、実装を含むことができる構造があります。 インターフェイスはトレイトと呼ばれ、デフォルトの実装も含まれる場合があります。 抽象クラスに到達しないのは、 フィールドを含めることはできません;多くの人がこの制限について文句を言います。 構文は次のとおりです。ここではコメントは不要だと思います。







 fn main() { MyPrinter { value: 10 }.print(); } trait Printer { fn print(&self); } impl Printer { fn print(&self) { println!("hello!") } } struct MyPrinter { value: i32 } impl Printer for MyPrinter { fn print(&self) { println!("{}", self.value) } }
      
      





私が気づいた機能のうち、次の点に注目する価値があります。









さらなるセキュリティ



前述したように、Rustはコードの信頼性に多大な注意を払い、コンパイル段階でほとんどのエラーを防止しようとします。 このため、リンクを空にする機能は除外されました。 Kotlinのnull許容型を思い出しました。 オプションは、空のリンクを作成するために使用されます。 Kotlinの場合と同様に、このような変数にアクセスしようとすると、コンパイラーは手を打ってチェックを挿入します。 チェックせずに値を取り出そうとすると、エラーが発生する可能性があります。 しかし、これはJavaなどのように偶然ではできません。







また、すべての変数とクラスフィールドがデフォルトで不変であるという事実も気に入っています。 こんにちは、コトリン。 値が変更される可能性がある場合は、 mutキーワードを使用して明示的に指定する必要があります。 不変性に対する欲求は、コードの可読性と予測可能性を大きく改善すると思います。 何らかの理由でオプションは変更可能ですが、私はこれを理解していませんでした。ドキュメントのコードを次に示します。







 let mut x = Some(2); let y = x.take(); assert_eq!(x, None); assert_eq!(y, Some(2));
      
      





乗り換え



Rustはenumと呼ばれます。 限られた数の値に加えてのみ、任意のデータとメソッドを含めることができます。 したがって、Javaの列挙型とクラスの間の何かです。 最初の例の標準列挙オプションは、このタイプに属しているだけです。







 pub enum Option<T> { None, Some(T), }
      
      





そのような値を処理するための特別な構造があります。







 fn main() { let a = Some(1); match a { None => println!("empty"), Some(v) => println!("{}", v) } }
      
      





同様に



Rustの教科書を書くつもりはありませんが、単にその機能を強調したいだけです。 このセクションでは、他に何が役立つかを説明しますが、私の意見では、それほどユニークではありません:









軟膏で飛ぶ



このセクションは、図を完成させるために必要です。







キラーの問題



主な欠陥は主な機能に由来します。 すべてを支払う必要があります。 Rustでは、可変グラフのデータ構造を扱うのは非常に不便です。なぜなら、 すべてのオブジェクトに複数のリンクを含めることはできません。 この制限を回避するために、多数の組み込みクラスがあります。









そして、これは不完全なリストです。 最初のRustサンプルでは、​​基本的な方法で単一リンクリストを作成することに無謀に決めました。 最終的に、ノードへのリンクは、次のオプション<Rc <RefCell <ListNode> >>になりました









まあまあのように見えますが、1つのオブジェクトの周りに合計3つのラッパーがあります。 リストの最後にアイテムを追加するだけのコードは非常に面倒であることがわかりました。また、クローンや借用などの明白でないものが含まれています。







 struct ListNode { val: i32, next: Node, } pub struct LinkedList { root: Node, last: Node, } type Node = Option<Rc<RefCell<ListNode>>>; impl LinkedList { pub fn add(mut self, val: i32) -> LinkedList { let n = Rc::new(RefCell::new(ListNode { val: val, next: None })); if (self.root.is_none()){ self.root = Some(n.clone()); } self.last.map(|v| { v.borrow_mut().next = Some(n.clone()) }); self.last = Some(n); self } ...
      
      





Kotlinでは、同じことがはるかに簡単に見えます。







 public fun add(value: Int) { val newNode = ListNode(null, value); root = root ?: newNode; last?.next = newNode last = newNode; }
      
      





後でわかったように、そのような構造はRustの典型ではなく、私のコードは完全に非正統的です。 人々は記事全体を書くことさえあります:









ここで、Rustはセキュリティの可読性を犠牲にします。 さらに、このような演習は、メモリ内にハングアップするループリンクにつながる可能性があります。 ガベージコレクターがそれらを削除することはありません。 Rustで動作するコードを書いていないので、そのような困難がどれほど人生を複雑にしているのかを言うのは難しいです。 練習中のエンジニアからコメントを受け取るのは面白いでしょう。







困難な学習



Rustを学習する長いプロセスは、主に前のセクションから続きます。 何かを書く前に、次のように、メモリ所有権の重要な概念を習得するために時間を費やす必要があります。 すべての行に浸透します。 たとえば、最も単純なリストでは数晩かかりましたが、Kotlinでは同じことが10分で書かれていますが、これは私の使用言語ではありません。 さらに、Rustでアルゴリズムやデータ構造を記述するための多くの馴染みのあるアプローチは異なって見えるか、まったく機能しません。 つまり それに切り替えるとき、思考をより深く再構築する必要があります。構文を習得するだけでは十分ではありません。 これは、すべてを飲み込んで耐えるJavaScriptとはほど遠いものです。 Rustがプログラミング学校で子供たちに教えられる言語になることは決してないと思います。 この意味では、C / C ++でさえチャンスがあります。







最後に



コンパイル段階でメモリを管理するというアイデアは非常に興味深いものでした。 C / C ++では、経験がないため、スマートポインターと比較しません。 構文は一般的に快適であり、余分なものはありません。 グラフデータ構造の実装の複雑さについてRustを批判しましたが、これはすべての非GCプログラミング言語の機能であると思われます。 Kotlinとの比較は完全に正直ではなかったかもしれません。







藤堂



この記事では、マルチスレッドについてはまったく触れませんでした。これは別の大きなトピックだと思います。 それでも、リストよりも複雑な何らかのデータ構造またはアルゴリズムを記述する計画があります。アイデアがあれば、コメントで共有してください。 Rustで一般的に作成されるアプリケーションの種類を知ることは興味深いでしょう。







読む



Rustに興味がある場合は、次のリンクをご覧ください。









UPD:コメントありがとうございます。 私は自分のために多くの有用なことを学びました。 誤りとタイプミスを修正し、リンクを追加しました。 こうした議論は、新しい技術の研究に大きく貢献すると思います。








All Articles