Rustを使用する10の明らかな利点

Rustは、若くて野心的なシステムプログラミング言語です。 ガベージコレクターやその他のランタイムオーバーヘッドなしで自動メモリ管理を実装します。 さらに、デフォルト言語はRust言語で使用され、可変データにアクセスするための前例のないルールがあり、リンクの有効期間も考慮されます。 これにより、データの競合がないため、メモリのセキュリティを保証し、マルチスレッドプログラミングを容易にすることができます。













これはすべて、少なくとも最近のプログラミングテクノロジの開発に追随するすべての人によく知られています。 しかし、システムプログラマーではなく、プロジェクトにマルチスレッドコードがあまりない場合でも、Rustのパフォーマンスに魅了されます。 アプリケーションで使用することで追加の利点が得られますか? それとも、彼があなたに与えるすべては、コンパイラとの厳しい戦いであり、それはあなたが常に借用と所有権に関する言語の規則に従うようにプログラムを書くことを強制しますか?







この記事では、Rustを使用することで得られる非自明で特に宣伝されていない多くの利点を収集しました。これは、プロジェクトでこの言語を選択する際に役立つことを願っています。







1.言語の普遍性



Rustはシステムプログラミングの言語として位置付けられているという事実にもかかわらず、高度な応用問題の解決にも適しています。 タスクに必要な場合を除き、生のポインターを使用する必要はありません。 標準言語ライブラリには、アプリケーション開発に必要なほとんどのタイプと機能がすでに実装されています。 外部ライブラリを簡単に接続して使用することもできます。 Rustの型システムと汎用プログラミングでは、かなり高いレベルの抽象化を使用できますが、この言語ではOOPを直接サポートしていません。







Rustの簡単な使用例を見てみましょう。







2つのイテレータを要素のペアで1つのイテレータに結合する例:







let zipper: Vec<_> = (1..).zip("foo".chars()).collect(); assert_eq!((1, 'f'), zipper[0]); assert_eq!((2, 'o'), zipper[1]); assert_eq!((3, 'o'), zipper[2]);
      
      





走る







注: name!(...)



形式の呼び出しは、機能マクロの呼び出しです。 Rustのこのようなマクロの名前は、常にシンボルで終わります!



関数名や他の識別子と区別できるように。 マクロを使用する利点については、以下で説明します。

regex



を操作するために外部regex



ライブラリを使用する例:







 extern crate regex; use regex::Regex; let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); assert!(re.is_match("2018-12-06"));
      
      





走る







追加演算子をオーバーロードするためのネイティブPoint



構造のAdd



実装例:







 use std::ops::Add; struct Point { x: i32, y: i32, } impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } } } let p1 = Point { x: 1, y: 0 }; let p2 = Point { x: 2, y: 3 }; let p3 = p1 + p2;
      
      





走る







構造体でジェネリック型を使用する例:







 struct Point<T> { x: T, y: T, } let int_origin = Point { x: 0, y: 0 }; let float_origin = Point { x: 0.0, y: 0.0 };
      
      





走る







Rustでは、効率的なシステムユーティリティ、大規模なデスクトップアプリケーション、マイクロサービス、Webアプリケーション(RustはWasmでコンパイルできるため、クライアント部分を含む)、モバイルアプリケーション(言語エコシステムはまだこの方向で不十分に開発されていますが)を記述できます。 このような汎用性は、多くの異なるプロジェクトで同じアプローチと同じモジュールを使用できるため、マルチプロジェクトチームにとって利点となります。 各ツールが独自の狭いアプリケーション分野向けに設計されているという事実に慣れている場合は、同じ信頼性と利便性を備えたツールボックスとしてRustを検討してください。 おそらく、これはまさにあなたが見逃していたものです。







2.便利なビルドおよび依存関係管理ツール



これは明らかに宣伝されていませんが、多くの人は、Rustが現在利用可能な最高のビルドおよび依存関係管理システムの1つであることを認識しています。 CまたはC ++でプログラミングし、外部ライブラリを簡単に使用できるという問題が非常に深刻な場合、RustをビルドツールとCargo依存関係マネージャーとともに使用することは、新しいプロジェクトに適した選択肢になります。







Cargoが依存関係をダウンロードし、バージョンを管理し、アプリケーションをビルドおよび実行し、テストを実行し、ドキュメントを生成するという事実に加えて、他の便利な機能のプラグインで拡張することもできます。 たとえば、Cargoがプロジェクトの廃止された依存関係を判断したり、ソースコードの静的分析を実行したり、Webアプリケーションのクライアント部分をビルドおよび再デプロイしたりできる拡張機能があります。







Cargo構成ファイルは、フレンドリーで最小限のtomlマークアップ言語を使用してプロジェクト設定を記述します。 典型的なCargo.toml



構成Cargo.toml



例を次に示しCargo.toml









 [package] name = "some_app" version = "0.1.0" authors = ["Your Name <you@example.com>"] [dependencies] regex = "1.0" chrono = "0.4" [dev-dependencies] rand = "*"
      
      





以下は、Cargoを使用するための3つの典型的なコマンドです。







 $ cargo check $ cargo test $ cargo run
      
      





彼らの助けを借りて、ソースコードのコンパイルエラー、プロジェクトのアセンブリとテストの起動、実行のためのプログラムのアセンブリと起動をそれぞれチェックします。







3.組み込みテスト



Rustでの単体テストの記述は非常に簡単で単純なので、何度も繰り返してやりたいと思います。 :)別の方法で機能をテストするよりも、単体テストを書く方が簡単な場合がよくあります。 次に、関数とそれらのテストの例を示します。







 pub fn is_false(a: bool) -> bool { !a } pub fn add_two(a: i32) -> i32 { a + 2 } #[cfg(test)] mod test { use super::*; #[test] fn is_false_works() { assert!(is_false(false)); assert!(!is_false(true)); } #[test] fn add_two_works() { assert_eq!(1, add_two(-1)); assert_eq!(2, add_two(0)); assert_eq!(4, add_two(2)); } }
      
      





走る







#[test]



属性でマークされたtest



モジュールの関数は、単体テストです。 これらは、 cargo test



コマンドが呼び出されると並行して実行されます。 モジュール全体をテストでマークする条件付きコンパイル属性#[cfg(test)]



は、テストの実行時にのみモジュールがコンパイルされ、通常のアセンブリには入らないという事実につながります。







test



サブモジュールを追加するだけで、テスト対象の機能モジュールと同じモジュールにテストを配置することは非常に便利です。 統合テストが必要な場合は、プロジェクトのルートにあるtests



ディレクトリにテストを配置し、アプリケーションを外部パッケージとして使用します。 この場合、個別のtest



モジュールと条件付きコンパイルディレクティブを追加する必要はありません。







テストとして実行されるドキュメントの特別な例は特別な注意に値しますが、これについては以下で説明します。







組み込みのパフォーマンステスト(ベンチマーク)も使用できますが、まだ安定していないため、コンパイラナイトアセンブリでのみ使用できます。 安定したRustでは、このタイプのテストには外部ライブラリを使用する必要があります。







4.適切なドキュメントと関連する例



標準のRustライブラリは非常によく文書化されています。 HTMLドキュメントは、ドックのコメントにマークダウンの説明が含まれたソースコードから自動的に生成されます。 さらに、Rustコードのドキュメントコメントには、テストの実行時に実行されるサンプルコードが含まれています。 これにより、例の関連性が保証されます。







 /// Returns a byte slice of this `String`'s contents. /// /// The inverse of this method is [`from_utf8`]. /// /// [`from_utf8`]: #method.from_utf8 /// /// # Examples /// /// Basic usage: /// /// ``` /// let s = String::from("hello"); /// /// assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes()); /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] pub fn as_bytes(&self) -> &[u8] { &self.vec }
      
      





ドキュメント







String



型のas_bytes



メソッドの使用例を次に示します







 let s = String::from("hello"); assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes());
      
      





テストの起動時にテストとして実行されます。







さらに、プロジェクトのルートにあるexamples



ディレクトリにある小さな独立したプログラムの形で使用例を作成する習慣は、Rustライブラリでは一般的です。 これらの例もドキュメントの重要な部分であり、テスト実行中にコンパイルおよび実行されますが、テストとは無関係に実行できます。







5.型のスマートな自動演duction



Rustプログラムでは、コンパイラが使用状況に基づいて自動的に式を出力できる場合、式のタイプを明示的に指定することはできません。 そして、これは変数が宣言されている場所だけに当てはまりません。 例を見てみましょう:







 let mut vec = Vec::new(); let text = "Message"; vec.push(text);
      
      





走る







型注釈を配置すると、この例は次のようになります。







 let mut vec: Vec<&str> = Vec::new(); let text: &str = "Message"; vec.push(text);
      
      





つまり、文字列スライスのベクトルと文字列スライス型の変数があります。 ただし、この場合、コンパイラーは( Hindley-Milnerアルゴリズムの拡張バージョンを使用して)単独で型を出力できるため、型の指定は完全に冗長です。 vec



がベクトルであるという事実は、 Vec::new()



からの戻り値の型によって既に明確になっていますが、その要素の型がどうなるかはまだ明確ではありません。 タイプtext



が文字列スライスであるという事実は、このタイプのリテラルが割り当てられているという事実によって理解できます。 したがって、 vec.push(text)



後、ベクトルの要素のタイプが明らかになります。 vec



変数のタイプは、初期化段階ではなく、実行スレッドでの使用によって完全に決定されることに注意してください。







このような自動型推論システムは、コードからノイズを除去し、動的に型指定されたプログラミング言語のコードと同じくらい簡潔にします。 そして、これは厳密な静的型付けを維持しながら!







もちろん、静的に型付けされた言語での型付けを完全になくすことはできません。 プログラムには、他の場所でこれらのタイプを表示できるように、オブジェクトのタイプが既知であることが保証されているポイントが必要です。 Rustのこのようなポイントは、ユーザー定義のデータ型と関数シグネチャの宣言です。そこでは、使用する型を指定するしかありません。 ただし、一般化プログラミングを使用して、「タイプのメタ変数」を入力できます。







6.変数宣言ポイントでのパターンマッチング



let









 let p = Point::new();
      
      





本当に新しい変数を宣言するだけにとどまりません。 彼女が実際に行うのは、等号の右側の表現を左側のパターンと一致させることです。 また、サンプルの一部として新しい変数を導入できます(その場合のみ)。 次の例を見てください、そしてそれはあなたに明らかになります:







 let Point { x, y } = Point::new();
      
      





走る







このような比較では、変数x



y



が導入され、 Point::new()



呼び出して返されるPoint



構造体のオブジェクトのフィールドx



y



値で初期化されます。 この場合、右側の式のタイプは左側のタイプPoint



パターンに対応するため、比較は正しいです。 同様の方法で、たとえば配列の最初の2つの要素を取得できます。







 let [a, b, _] = [1, 2, 3];
      
      





そして、さらに多くのことを行います。 最も注目すべきことは、そのような比較は、Rustで新しい変数名を入力できるすべての場所で実行されることです。つまり、 match



let



if let



while let



if let



for



ループのヘッダー、関数とクロージャーの引数です。 for



ループでパターンマッチングをエレガントに使用する例を次に示します。







 for (i, ch) in "foo".chars().enumerate() { println!("Index: {}, char: {}", i, ch); }
      
      





走る







イテレータで呼び出されるenumerate



メソッドは、新しいイテレータを作成します。イテレータは、初期値ではなく、タプル、「順序インデックス、初期値」のペアを反復処理します。 サイクルの反復中のこれらの各タプルは、指定されたパターン(i, ch)



にマッピングされます。その結果、変数i



はタプルから最初の値(インデックス)を受け取り、変数ch



は2番目(文字列の文字)を受け取ります。 さらにループの本体では、これらの変数を使用できます。







for



ループでパターンを使用する別の一般的な例:







 for _ in 0..5 { //   5  }
      
      





ここでは、 _



パターンを使用してイテレータ値を無視します。 ループの本体では反復番号を使用しないためです。 たとえば、関数の引数を使用しても同じことができます。







 fn foo(a: i32, _: bool) { //      }
      
      





または、一致ステートメントで一致する場合:







 match p { Point { x: 1, .. } => println!("Point with x == 1 detected"), Point { y: 2, .. } => println!("Point with x != 1 and y == 2 detected"), _ => (), //        }
      
      





走る







パターンマッチングにより、コードは非常にコンパクトで表現力豊かになります。また、 match



ステートメントでは、通常、置き換えられません。 match



演算子は完全な変動分析の演算子です。そのため、分析された式の可能な一致のいくつかを誤ってチェックすることを忘れることはありません。







7.構文拡張とカスタムDSL



主に言語で使用される型システムの複雑さのために、錆の構文は制限されています。 たとえば、Rustには名前付き関数の引数や、可変数の引数を持つ関数はありません。 しかし、マクロを使用してこれらの制限やその他の制限を回避できます。 Rustには、宣言型と手続き型の2種類のマクロがあります。 宣言型マクロを使用すると、Cのマクロと同じ問題が発生することはありません。マクロは衛生的で、テキスト置換のレベルでは機能せず、抽象的な構文ツリーの置換のレベルで機能するためです。 マクロを使用すると、言語構文レベルで抽象化を作成できます。 例:







 println!("Hello, {name}! Do you know about {}?", 42, name = "User");
      
      





このマクロは、書式設定された文字列を印刷する「関数」を呼び出す構文機能を拡張するという事実に加えて、その実装では、実行時ではなくコンパイル時に入力引数が指定された書式文字列と一致するかどうかを確認します。 マクロを使用すると、独自の設計ニーズに合わせて簡潔な構文を入力し、DSLを作成して使用できます。 WasmでコンパイルするRustプログラム内でJavaScriptコードを使用する例を次に示します。







 let name = "Bob"; let result = js! { var msg = "Hello from JS, " + @{name} + "!"; console.log(msg); alert(msg); return 2 + 2; }; println!("2 + 2 = {:?}", result);
      
      





マクロjs!



stdweb



パッケージで定義されているため、プログラムに本格的なJavaScriptコードを埋め込み(単一引用符で囲まれた文字列とセミコロンで完了していない演算子を除く)、構文@{expr}



を使用してRustコードのオブジェクトを使用できます。







マクロは、Rustプログラムの構文を特定のサブジェクト領域の特定のタスクに適応させる絶好の機会を提供します。 複雑なアプリケーションを開発する際に時間と注意を節約できます。 ランタイムのオーバーヘッドを増やすのではなく、コンパイル時間を増やします。 :)







8.依存コードの自動生成



Rustの手続き型派生マクロは、特性やその他のコード生成を自動的に実装するために広く使用されています。 以下に例を示します。







 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] struct Point { x: i32, y: i32, }
      
      





標準ライブラリのこれらすべての特性( Copy



Clone



Debug



Default



PartialEq



およびEq



)はi32



構造体のフィールドタイプに対して実装されているため、それらの実装は構造体全体に対して自動的に表示できます。 別の例:







 extern crate serde_derive; extern crate serde_json; use serde_derive::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] struct Point { x: i32, y: i32, } let point = Point { x: 1, y: 2 }; //  Point  JSON . let serialized = serde_json::to_string(&point).unwrap(); assert_eq!("{\"x\":1,\"y\":2}", serialized); //  JSON   Point. let deserialized: Point = serde_json::from_str(&serialized).unwrap();
      
      





走る







ここでは、 Point



構造のserde



ライブラリのserde



Deserialize



Deserialize



して、そのシリアル化と逆シリアル化のメソッドが自動的に生成されます。 次に、この構造のインスタンスをさまざまなシリアル化関数に渡します。たとえば、JSON文字列に変換します。







必要なコードを生成する独自の手続きマクロを作成できます。 または、他の開発者が作成した多くのマクロを使用します。 マクロには、プログラマーが定型的なコードを記述しないようにするだけでなく、コードの異なるセクションを一貫した状態に維持する必要がないという利点もあります。 たとえば、3番目のフィールドz



Point



構造に追加された場合、その正しいシリアル化を行うために派生を使用する場合、他に何もする必要はありません。 Point



のシリアル化に必要な特性を実装する場合、この実装がPoint



の構造の最新の変更と常に一貫していることを確認する必要があります。







9.代数データ型



簡単に言えば、代数データ型は複合データ型であり、構造体の結合です。 より正式には、製品タイプのタイプサムです。 Rustでは、このタイプはenum



キーワードを使用して定義されます:







 enum Message { Quit, ChangeColor(i32, i32, i32), Move { x: i32, y: i32 }, Write(String), }
      
      





タイプMessage



変数の特定の値のタイプは、 Message



リストされている構造タイプのうちの1つのみです。 これは、ユニットのようなフィールドレスQuit



構造、 ChangeColor



またはWritelessタプル構造のいずれか、名前のないフィールド、または通常のMove



構造のいずれかです。 従来の列挙型は、代数データ型の特殊なケースとして表すことができます。







 enum Color { Red, Green, Blue, White, Black, Unknown, }
      
      





パターンマッチングを使用して、特定のケースで実際にどのタイプが値を取得したかを調べることができます。







 let color: Color = get_color(); let text = match color { Color::Red => "Red", Color::Green => "Green", Color::Blue => "Blue", _ => "Other color", }; println!("{}", text); ... fn process_message(msg: Message) { match msg { Message::Quit => quit(), Message::ChangeColor(r, g, b) => change_color(r, g, b), Message::Move { x, y } => move_cursor(x, y), Message::Write(s) => println!("{}", s), }; }
      
      





走る







代数的なデータ型の形式で、RustはOption



Result



などの重要な型を実装します。これらはそれぞれ欠損値と正しい/誤った結果を表すために使用されます。 標準ライブラリでOption



を定義する方法は次のとおりです。







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





Rustには、予期しない呼び出しによる迷惑なエラーのように、null値がありません。 代わりに、欠損値の可能性を示すことが本当に必要な場合、 Option



使用されます。







 fn divide(numerator: f64, denominator: f64) -> Option<f64> { if denominator == 0.0 { None } else { Some(numerator / denominator) } } let result = divide(2.0, 3.0); match result { Some(x) => println!("Result: {}", x), None => println!("Cannot divide by 0"), }
      
      





走る







代数データ型は、型駆動開発への扉を開く強力で表現力豊かなツールです。 このパラダイムで有能に書かれたプログラムは、その作業の正確性のチェックのほとんどを型システムに割り当てます。 したがって、日常の産業用プログラミングでHaskellが少し不足している場合は、Rustを使用できます。 :)







10.簡単なリファクタリング



Rustで開発された厳密な静的型システムと、コンパイル中にできるだけ多くのチェックを実行しようとすると、コードの変更とリファクタリングが非常に簡単で安全になります。 変更後にプログラムがコンパイルされた場合、これは、検証がコンパイラに割り当てられた機能に関連しない論理エラーのみが含まれていることを意味します。 単体テストをテストロジックに簡単に追加できるため、プログラムの信頼性が大幅に保証され、変更後のコードの正しい動作に対するプログラマの信頼性が向上します。










おそらく、この記事で説明したかったのはこれだけです。 もちろん、Rustには他の多くの利点と、いくつかの欠点(言語の多少の湿気、使い慣れたプログラミングイディオムの欠如、および「非文学的な」構文)があります。 それらについて何か伝えたいことがあれば、コメントに書いてください。 一般的に、Rustを実際に試してください。 そして、私の場合に起こったように、あなたにとっての彼の利点は彼のすべての欠点を上回るかもしれません。 そして最後に、長い間必要な一連のツールを正確に入手できます。








All Articles