男!(D => Rust).basics

この記事をあまり真剣に受け取らないでください.Dmitry aka vintageによって作成された一連の翻訳を読んだ直後に、DからRustに切り替えることをお勧めしません。私は特にRustのコードの例を書き直したいと思っていました。特に作者がこの言語を投票に追加したからです。 私の主な作業ツールはC ++です。ただし、最近積極的にRustに興味を持っています。 私はDに数回挑戦しようとしましたが、そのたびに何かが私をはじきました。 これが悪い言語だとは決して言いたくありません。「プラスの殺人者」にとって「過激すぎる」場所にあるだけです。例えば、GC(切断されていますが)があり、他の場所では、C ++に近すぎます。明白でないニュアンス。



おもしろいことに、Rustを研究した後、Dに対する態度が少し変わりました。簡潔さと表現力の観点から、Dが大いに勝ちます。 ただし、それどころか、Rustコミュニティは「マニフェスト」を利点と見なしています。 私の気持ちによると、Rustでは「学問的正しさ」によって導かれることが多く、Dではより実用的なアプローチです。 どちらが良いかは難しい質問です。個人的には、私自身が常に決めることはできません。



ただし、これはすべて非常に主観的であるため、一緒にコードを見てみましょう。 Goのコードは公開しません。必要に応じて、 元の記事で確認できます。



ハローワールド



D

module main; import std.stdio; void main() { // stdout.writeln( "Hello, 世界" ); writeln( "Hello, 世界" ); }
      
      





さび

 fn main() { println!("Hello, 世界") }
      
      





Rustは、最も一般的に使用されるものを暗黙的にインポートします (無効にできますが)ので、他に何もインポートする必要はありません。



この例でもセミコロンを省略できます。これは式をステートメントに変換するために使用され、mainおよびprintln! 何も返さない(実際には、特別な空の型()



返される)場合、違いはありません。



ネストされたモジュールを宣言したくない場合は、ファイルまたはディレクトリの名前に依存するため、モジュールの名前を明示的に示す必要はありません。 モジュールの詳細( 翻訳 )。





パッケージ



D

 module main; import std.stdio; import std.random; void main() { writeln( "My favorite number is ", uniform( 0 , 10 ) ); }
      
      





さび

 extern crate rand; use rand::distributions::{IndependentSample, Range}; fn main() { let between = Range::new(0, 10); let mut rng = rand::thread_rng(); println!("My favorite number is {}", between.ind_sample(&mut rng)); }
      
      





Rustは標準ライブラリの拡張に慎重にアプローチしているため、この例は完全に同等ではないことが判明しました。その結果、多くの「単純な」ことのために、サードパーティライブラリを使用する必要があります。 決定はあいまいですが、利点があります。 いずれにしても、便利なパッケージマネージャーCargoのおかげで、ライブラリの接続は非常に簡単です。



さて、ランダムな値での作業はより冗長で、C ++でどのように行われたかを思い出させますが、これはライブラリの主張です。



残念ながら、play.rust-langは外部パッケージをサポートしていないため、この例を試すことはできません。





輸入品



D

 module main; import std.stdio, std.math; void main() { import std.conv; // ... }
      
      





さび

 use std::{path, env}; fn main() { use std::convert; // ... }
      
      





Rustを使用すると、インポートを共通ルートでグループ化できます。 残念ながら、このようなインポートでは相対パスを使用できません。

 use std::{path, env::args}; // Error
      
      





インポートはファイルの先頭だけでなく、ブロックの先頭に配置する必要があります。





エクスポートされた名前





Dでは、デフォルトではすべてがパブリックであると見なされます(モジュール自体のインポートを除く)。必要に応じて、privateを指定できます。 Rustは、この点でも明示性を好みます。pubキーワードでマークされたエンティティのみがエクスポートされます。

 mod test { pub struct PublicStruct { pub a: i32, } pub struct NoSoPublicStruct { pub a: i32, b: i32, } struct PrivateStruct { a: i32, } pub struct PublicTupleStruct(pub i32, pub i32); pub struct TupleStruct(pub i32, i32); struct PrivateTupleStruct(i32, i32, i32); pub fn create() -> NoSoPublicStruct { NoSoPublicStruct { a: 10, b: 20 } } fn create_private() -> PublicTupleStruct { PublicTupleStruct(1, 2) } } use test::{PublicStruct, NoSoPublicStruct, PublicTupleStruct, create}; // :    /. // use test::{PrivateStruct, create_private}; // Error. fn main() { let _a = PublicStruct { a: 10 }; // :       . // let _b = NoSoPublicStruct { a: 10, b: 20 }; // Error. let _c = create(); // :    . // _c.b; let _d = PublicTupleStruct(1, 2); }
      
      





機能



D

 module main; import std.stdio; int add( int x , int y ) { return x + y; } void main() { // writeln( add( 42 , 13 ) ); writeln( 42.add( 13 ) ); }
      
      





さび

 fn add(x: i32, y: i32) -> i32 { x + y } fn main() { println!("{}", add(42, 13)); }
      
      





Rustでは、関数をメソッドとして使用することはできませんが、逆も可能です。 さらに、メソッドは外部で実装されるため、メソッド(および特性実装)を既存の型に追加できます。 利用可能な汎用プログラミング:



D

 module main; import std.stdio; auto add( X , Y )( X x , Y y ) { return x + y; // Error: incompatible types for ((x) + (y)): 'int' and 'string' } void main() { // writeln( 42.add!( int , float )( 13.3 ) ); writeln( 42.add( 13.3 ) ); // 55.3 writeln( 42.add( "WTF?" ) ); // Error: template instance main.add!(int, string) error instantiating }
      
      





さび

 use std::ops::Add; fn add<T1, T2, Result>(x: T1, y: T2) -> Result where T1: Add<T2, Output = Result> { x + y } fn main() { println!("{}", add(42, 13)); //println!("{}", add(42, "eee")); // trait Add is not implemented for the type }
      
      





ここで、文字通り言う:add関数は2つのパラメーターT1とT2を取り、Result型を返します。T1型の場合、T2型の加算が実装され、Resultを返します。 この例では、アプローチの違いが最もよく見られます:簡潔さと、「明示性」とより便利なエラーメッセージのための柔軟性を犠牲にします。



複数の結果



D

 module main; import std.stdio; import std.meta; import std.typecons; auto swap( Item )( Item[2] arg... ) { return tuple( arg[1] , arg[0] ); } void main() { string a , b; AliasSeq!( a , b ) = swap( "hello" , "world" ); writeln( a , b ); // worldhello }
      
      





さび

 fn swap(a: i32, b: i32) -> (i32, i32) { (b, a) } fn main() { let (a, b) = swap(1, 2); println!("a is {} and b is {}", a, b); }
      
      





let



は完全なパターンマッチングであるため、アンパックはアナウンスのように見えます。





名前付き戻り値



Dと同様、Rustには名前付き戻り値はありません。 名前付き引数を持つタプルもありません。 しかし、後者は奇妙なことに思えます-なぜ、構造体を使用しないのですか?..



ところで、両方の言語には、名前付き関数パラメーターはありません。 DRustの両方に表示されるのは面白いです。





変数



D

 module main; import std.stdio; void main() { bool c; bool python; bool java; int i; }
      
      





さび

 fn main() { let c: bool; let python: bool; let java: bool; let i: i32; }
      
      





Rustでは、コンパイラは初期化されていない変数へのアクセスを禁止しています。





短い変数宣言



D

 module main; import std.stdio; void main() { int i = 1 , j = 2; auto k = 3; auto c = true , python = false , java = "no!"; writeln( i , j , k , c , python , java ); // 123truefalseno! }
      
      





さび

 fn main() { let (i, j) = (1, 2); let k = 3; let (c, python, java) = (true, false, "no!"); println!("{}, {}, {}, {}, {}, {}", i, j, k, c, python, java); // 1, 2, 3, true, false, no! }
      
      





どちらの言語も型を推測できますが、Rustでは、宣言からだけでなくuseからも型を推測できます

 fn take_i8(_: i8) {} fn take_i32(_: i32) {} fn main() { let a = 10; let b = 20; take_i8(a); //take_i32(a); // error: mismatched types: expected `i32`, found `i8` take_i32(b); //take_i8(b); // error: mismatched types: expected `i8`, found `i32` }
      
      





そのような例はやや手に負えないように見えますが、不完全に指定された型を関数に渡す(または返す)場合に便利です。





基本タイプ



タイプマッチングテーブル:

 Go D Rust --------------------------------- void () bool bool bool string string String &str int int i32 byte byte i8 int8 byte i8 int16 short i16 int32 int i32 int64 long i64 uint unint u32 uint8 ubyte u8 uint16 ushort u16 uint32 uint u32 uint64 ulong u64 uintptr size_t usize ptrdiff_t isize float32 float f32 float64 double f64 real ifloat idouble ireal complex64 cfloat complex128 cdouble creal char wchar rune dchar char
      
      





基本的なタイプのRustでは、最もエキゾチックなものを作るために最も必要な最低限のものを、ライブラリに移動する必要があります。 Rustの型関連のプロパティは、定数として定義されています( 例としてf64を使用 )。

比較には「よりcな」タイプは含まれません。それらを見ることができる面白いテーブルがあります 恐ろしい





ゼロ値



D

 module main; import std.stdio; void main() { writefln( "%s %s %s \"%s\"" , int.init , double.init , bool.init , string.init ); // 0 nan false "" }
      
      





さび

 fn main() { println!("{} {} {} '{}'", i32::default(), f64::default(), bool::default(), String::default()); // 0 0 false '' }
      
      





Rustでは、タイプに意味のあるデフォルト値がある場合、 デフォルトの特性を実装できます。 既に述べたように、コンパイラは初期化されていない変数へのアクセスを監視するため、変数を自動的に初期化することはほとんど意味がありません。





型変換



Rustには暗黙的な型変換はありません。 安全に実行できるものであっても、精度を損なうことなく実行できます。 最近、友人との論争がありましたが、彼は整数型を浮動小数点数にキャストすることは明示的であり、プラットフォーム固有の型(size_tなど)にキャストする必要があると主張しました。 私は後者に完全に同意します-他のコンパイル設定でのみ警告が出たとき、それはあまり便利ではありません。 その結果、混乱を招くルールを作成するよりも、常に明示的な型の指示を要求する方が良いです-決定は言語哲学の精神に基づいています。

 let a: i32 = 10; let b: i64 = a as i64;
      
      





数値定数



D

 enum Big = 1L << 100; // Error: shift by 100 is outside the range 0..63
      
      





さび

 let a = 1 << 100; // error: bitshift exceeds the type's number of bits, #[deny(exceeding_bitshifts)] on by default
      
      





ところで、デバッグアセンブリのRustは、算術演算中のオーバーフローを監視します。 このリリースでは、パフォーマンスのために、チェックは無効になっていますが、アセンブリのタイプに関係なく、チェックを明示的に有効/無効にする方法があります。



もちろん、Dの場合のように、このような単純な例を別の言語から書き換える場合、利点/機能を完全に明らかにすることは常に可能とは限りません。 舞台裏で、代数的にデータ型を残し、サンプルおよびマクロと比較するとしましょう。




All Articles