男!(C => D =>錆)

前の記事は思ったよりもよく認識されていたので、実験を続けることにしました。 これは、Dmitry aka vintageによる記事 Cプログラマ向けDのプ​​ログラミングの 翻訳に対する一種の応答です。 アプリケーションの分野では、前の記事で提案したように、Rの方がGoを置き換えるよりも適切であるように思えます。 より興味深いのは、比較することです。 繰り返しますが、特にDのアナログはより簡潔に見えるため、Cのコードは提供しません。









型サイズをバイト単位で取得します



C(およびC ++)には、この目的のための特別なsizeof



演算子があり、これは型と変数の両方に適用できることを思い出してください。 Dでは、プロパティを介してサイズにアクセスできます(変数にも適用できます)。







 int.sizeof (char*).sizeof double.sizeof Foo.sizeof
      
      





Rustは、コンパイラ内部にアクセスする関数( 組み込み関数に対応)を使用します。







 size_of::<i32>() size_of::<*const i8>() size_of::<f64>() size_of::<Foo>()
      
      





同時に、関数のオーバーロードがないため、変数のサイズを取得するために別の関数size_of_val



が使用されます。 おそらく、この分離はやや不便ですが、特別なキーワードを入力する必要はありません-通常の言語メカニズムを使用します:







 let a = 10i32; println!("{}", size_of_val(&a));
      
      





面白いニュアンス:Rustでは、空の構造体(例のFooなど)はそれぞれ0バイトを占有し、そのような構造体の任意のサイズの配列も0バイトを占有します。

[コードで遊ぶ]









型の最大値と最小値を取得します



Dでは、再びタイププロパティが使用されます。







 char.max char.min ulong.max double.min_normal
      
      





RustはCのような定数を使用します:







 i8::MAX i8::MIN u64::MAX f64::MIN
      
      





[実行]









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



 CD Rust ----------------------------------------------------- bool bool bool char char signed char char i8 unsigned char ubyte u8 short short i16 unsigned short ushort u16 wchar_t wchar int int i32 unsigned uint u32 long int i32 unsigned long uint u32 long long long i64 unsigned long long ulong u64 float float f32 double double f64 long double real _Imaginary long double ireal _Complex long double creal
      
      





Cはプラットフォーム依存型を使用し、Dは固定サイズを使用するため、比較は完全に正確ではありません。 Rustの場合、固定サイズのアナログを正確に選択しました。









特別な浮動小数点値



 double.nan double.infinity double.dig double.epsilon double.mant_dig double.max_10_exp double.max_exp double.min_10_exp double.min_exp
      
      





 f64::NAN f64::INFINITY f64::DIGITS f64::EPSILON f64::MANTISSA_DIGITS f64::MAX_10_EXP f64::MAX_EXP f64::MIN_10_EXP f64::MIN_EXP
      
      





ご覧のとおり、Rustは再び定数を使用します。これは、ちなみに大文字で書くのが慣例です。









実数の除算の残り



ここには啓示はありません-Rustには Dのようにa%演算子があります。









NaN値の処理



DとRustの両方で、NaNとの比較はfalse









 let x = 1f64; let y = NAN; println!("{}", x < y); // false println!("{}", y < x); // false println!("{}", x == y); // false
      
      





[実行]









Acerts-エラーを検出するための便利なメカニズム



どちらの言語も「すぐに使える」アサーションを提供しますが、Dでは特別な言語構成体です。







 assert( e == 0 );
      
      





A in Rust-ただのマクロ:







 assert!(condition); assert_eq!(a, b);
      
      





ただし、興味深い違いがあります:Dでは、通常の実行中に達成できないコードを示すために使用される特別なケースであるassert(0)



を除き、リリースビルドのリリースは無効になります。



Rustでは、それらはリリースに残りますが、 debug_assert!



マクロを使用して同様の動作を取得できますdebug_assert!



unreachable!



別のマクロを使用する場合のunreachable!



より明示的な指定についてunreachable!













配列の繰り返し(コレクション)



 int array[17]; foreach( value ; array ) { func( value ); }
      
      





 let array = [0; 17]; for value in &array { println!("{}", value); }
      
      





Rustのfor



ループはCからの相対ループのようには見えませんが、大きな違いはありません。









配列要素の初期化



 int array[17]; array[] = value;
      
      





Dでは、上記のように、単一の値で配列を初期化できます。 作成後、配列は最初に含まれる型のデフォルト値で初期化されることに注意してください。









 let array = [value; 17];
      
      





この場合、Rustには特別な構文があります









可変長配列の作成



Dには、可変長配列の組み込みサポートがあります。







 int[] array; int x; array.length = array.length + 1; array[ array.length - 1 ] = x;
      
      





Rustでは、「明示の哲学」に従って、 resize



メソッドが呼び出されたときに新しい要素を初期化する値を設定する必要があります。 したがって、より正確な例は次のように記述されます。







 let mut array = Vec::new(); array.push(value);
      
      





ベクターに含まれる要素のタイプを指定する必要はありません -それらは自動的に表示されます。









ライン結合



Dには、リストを結合するために設計された特別なオーバーロード演算子〜と〜=があります







 char[] s1; char[] s2; char[] s; s = s1 ~ s2; s ~= "hello";
      
      





公式文書 、個々の演算子の存在を主張しているため、演算子のオーバーロード+



は驚きにつながる可能性があります。







 let s1 = "abc"; let s2 = "eee"; let mut s = s1.to_owned() + s2; s.push_str("world");
      
      





一方、 Rustでは、明示的な型変換が必要なため、これらの問題は不可能です。 一方、文字列の+=



演算子はまだ実装されていません。









フォーマットされた出力



 import std.stdio; writefln( "Calling all cars %s times!" , ntimes );
      
      





 println!("Calling all cars {} times!" , ntimes);
      
      





[実行]



ご覧のように、この点での言語はそれほど違いはありません。 Rustの場合を除き、 フォーマットは Cの「なじみのある」ものとは異なります。









発表前に関数を呼び出す



どちらの言語もモジュールを使用するため、定義の順序は無関係であり、事前の発表は必要ありません。



錆の例:







 fn foo() -> Test { bar() } fn bar() -> Test { Test { a: 10, b: 20 } } struct Test { a: i32, b: i32, }
      
      





引数なしの関数



 void foo() { ... }
      
      





 fn foo() { ... }
      
      





どちらの言語も引数がないことを示すためにvoid



を必要としないため、Cからの分離では比較はいくぶん無意味です。









複数のコードブロックを終了する



 Louter: for( i = 0 ; i < 10 ; i++ ) { for( j = 0 ; j < 10 ; j++ ) { if (j == 3) break Louter; if (j == 4) continue Louter; } }
      
      





 'outer: for i in 0..10 { 'inner: for j in 0..10 { if i == 3 { break 'outer; } if j == 4 { continue 'inner; } } }
      
      





ラベル付きのbreak / continueの構文はほぼ同じです。









構造名前空間



繰り返しますが、どちらの言語にも、構造体用の個別の名前空間はありません。









文字列値の分岐(例:コマンドライン引数の処理)



 void dostring( string s ) { switch( s ) { case "hello": ... case "goodbye": ... case "maybe": ... default: ... } }
      
      





 fn do_string(s: &str) { match s { "hello" => {}, "goodbye" => {}, "maybe" => {}, _ => {}, } }
      
      





この場合、それほど大きな違いはありませんが、Rustでは、 match



コンストラクトはサンプルとの完全な比較であり、より複雑なことを行うことができます







 enum Type { Common, Secret, Unknown, } struct Data { id: i32, data_type: Type, info: Vec<i32>, } fn check_data(data: &Data) { match *data { Data { id: 42, .. } => println!("The Ultimate Question..."), Data { data_type: Type::Secret, info: ref i, .. } if i.is_empty() => println!("Empty secret data!"), _ => println!("Some data..."), } }
      
      





詳細はドキュメント翻訳 )にあります。









構造体フィールドの配置



Dには、個々のフィールドの配置を微調整できる特別な構文があります。







 struct ABC { int z; // z is aligned to the default align(1) int x; // x is byte aligned align(4) { ... // declarations in {} are dword aligned } align(2): // switch to word alignment from here on int y; // y is word aligned }
      
      





Rustでは、個々の構造のアライメントのみを完全に無効にすることができます:







 #[repr(packed)] struct Abc { ... }
      
      





匿名の構造と関連付け



Dは、ネストされたエンティティのフラットな外部インターフェイスを維持できる匿名構造をサポートしています。







 struct Foo { int i; union { struct { int x; long y; } char* p; } } Foo f; fi; fx; fy; fp;
      
      





Rustには匿名の構造や結合はないため、同様のコードは次のようになります。







 enum Bar { Baz {x: i32, y: i32 }, Option(i8), } struct Foo { i: i32, e: Bar, }
      
      





さらに、Rustは、誤って初期化されたユニオンの誤ったフィールドにアクセスすることを許可しません。 したがって、それらを別の方法で有効にする必要があります







 match fe { Bar::Val(a) => println!("{}", a), Bar::Baz { x, y } => println!("{} and {}", x, y), }
      
      





したがって、ユニオンを(半)正当な型変換として使用することはできませんが、潜在的なエラーは排除されます。









構造と変数の定義



どちらの言語も、型と変数を別々に宣言する必要があります。つまり、Cのように、書き込みは機能しません。







 struct Foo { int x; int y; } foo;
      
      







構造体のフィールドオフセットの取得



Dでは、フィールドに特別なoffsetof



プロパティがあります。







 struct Foo { int x; int y; } off = Foo.y.offsetof;
      
      





現時点では、Rustはこの機能をサポートしていないため、必要に応じて、構造体のメンバーへのポインターを操作して手動でオフセットを計算する必要があります。 ただし、 offsetof



は予約キーワードです。つまり、そのような機能は時間とともに現れるはずです。









組合の初期化



Dでは、どのユニオンフィールドに値が割り当てられているかを明示的に示す必要があります。







 union U { int a; long b; } U x = { a : 5 };
      
      





さらに、Rustは同じことを行い、さらに、既に述べたように、初期化されたユニオンの間違ったフィールドへのアクセスを許可しません。







 enum U { A(i32), B(i64), } let u = U::A(10);
      
      





構造の初期化



Dでは、構造は順序とフィールド名の両方で初期化できます。







 struct S { int a; int b; int c; int d; } S x = { 1, 2, 3, 4 }; S y = { b : 3 , a : 5 , c : 2 , d : 10 };
      
      





Rustでは、命名が必要です







 struct S { a: i32, b: i32, c: i32, d: i32, } let x = s { 1, 2, 3, 4 }; // Erorr. let y = S { a: 1, b: 2, c: 3, d: 4 }; // Ok.
      
      





配列の初期化



Dでは、初期化する要素のインデックスを指定するなど、配列を初期化する多くの方法があります。







 int[3] a = [ 3, 2, 0 ]; int[3] a = [ 3, 2 ]; // unsupplied initializers are 0, just like in C int[3] a = [ 2 : 0, 0 : 3, 1 : 2 ]; int[3] a = [ 2 : 0, 0 : 3, 2 ]; // if not supplied, the index is the previous one plus one.
      
      





Rustでは 、配列を初期化するすべての値をリストするか、配列のすべての要素に単一の値を指定することができます。







 let a1 = [1, 2, 3, 4, 5]; let a2 = [0; 6];
      
      





行の特殊文字をエスケープする



両方の言語は、個々のキャラクターのシールドとともに、いわゆる「生の線」をサポートしています。







 string file = "c:\\root\\file.c"; string file = r"c:\root\file.c"; // c:\root\file.c string quotedString = `"[^\\]*(\\.[^\\]*)*"`;
      
      





 let file = "c:\\root\\file.c"; let file = r"c:\root\file.c"; let quoted_string = r#""[^\\]*(\\.[^\\]*)*""#;
      
      





Rustでは、「生の文字列」は非常に単純に形成されますr



文字で始まり、その後に任意の数の#



文字が続き、引用符( "



)が続きます。文字列は同じ数の#



引用符で終わります。









ASCII対マルチバイトエンコーディング



Dは、さまざまなタイプの文字を格納するいくつかの種類の文字列をサポートしています。







 string utf8 = "hello"; // UTF-8 string wstring utf16 = "hello"; // UTF-16 string dstring utf32 = "hello"; // UTF-32 string
      
      





Rustでは、UTF-8バイトのシーケンスを表す文字列は1種類のみです。







 let str = "hello";
      
      





Konstantin別名kstepはハブでRustの文字列型に関する一連の 翻訳を公開しているため、詳細に興味がある場合は、それらに精通することをお勧めします。 まあ、または公式文書翻訳 )付き。









列挙型を配列にマッピング



 enum COLORS { red, blue, green } string[ COLORS.max + 1 ] cstring = [ COLORS.red : "red", COLORS.blue : "blue", COLORS.green : "green", ];
      
      





collectを使用したさびアナログ!マクロ 次のようになります。







 use std::collections::BTreeMap; #[derive(PartialOrd, Ord, PartialEq, Eq)] enum Colors { Red, Blue, Green, } let cstring: BTreeMap<_, _> = collect![ Colors::Red => "red", Colors::Blue => "blue", Colors::Green => "green", ];
      
      





新しいタイプの作成



Dでは、既存のタイプから新しいタイプを作成できます(強いtypedef):







 import std.typecons; alias Handle = Typedef!( void* ); void foo( void* ); void bar( Handle ); Handle h; foo( h ); // syntax error bar( h ); // ok
      
      





含む、デフォルト値で:







 alias Handle = Typedef!( void* , cast( void* ) -1 ); Handle h; h = func(); if( h != Handle.init ) { ... }
      
      





Rustでは、これはタプル構造体tuple structtranslation )を使用して行われます:







 struct Handle(*mut i8); fn foo(_: *mut i8) {} fn bar(_: Handle) {} foo(h); // error bar(h); // ok
      
      





Rustを初期化せずに値を作成することはできません。また、デフォルト値を作成するために、 Defaultトレイトを実装することが適切です。







 struct Handle(*mut i8); impl Default for Handle { fn default() -> Self { Handle(std::ptr::null_mut()) } } let h = Handle::default();
      
      





[実行]









構造比較



 struct A { int a; } if (a1 == a2) { ... }
      
      





 #[derive(PartialEq)] struct A { a: i32, } if a1 == a2 { ... }
      
      





唯一の違いは、Dが暗黙的に比較演算子を実装し、Rustにこれを要求する必要があることです。これは#[derive(PartialEq)]



ます。









文字列比較



 string str = "hello"; if( str == "betty" ) { ... } if( str < "betty" ) { ... }
      
      





 let str = "hello"; if str == "betty" { ... } if str < "betty" { ... }
      
      





どちらの言語でも、文字列は同等かそれ以上/以下で比較できます。









配列ソート



Dは、アルゴリズムの一般化された実装を使用します。







 import std.algorithm; type[] array; ... sort( array ); // sort array in-place array.sort!"a>b" // using custom compare function array.sort!( ( a , b ) => ( a > b ) ) // same as above
      
      





Rustは、少し異なるアプローチを使用します。他のアルゴリズムと同様に、「スライス」に対してソートが実装され、これに意味のあるコンテナをそれらに減らすことができます。







 let mut array = [3, 2, 1]; array.sort(); array.sort_by(|a, b| b.cmp(a));
      
      





[実行]

わずかな違いのうち、比較はbool



返すべきではありませんが、 順序付け (より大きい/より小さい/等しい)を返します。



この比較により、なぜRustではDやC ++と異なる方法で行われたのかと思いました。 率直に言って、私は両方のアプローチの長所と短所を見ていないので、単に言語の機能について書いています。







文字列リテラル



 "This text \"spans\" multiple lines "
      
      





 "This text \"spans\" multiple lines "
      
      





どちらの言語も複数行の文字列定数をサポートしています。









データ構造の走査



名前にもかかわらず、この段落では、私の意見では、外部関数で宣言された変数にアクセスするネストされた関数の能力のみが示されているため、コードを書き換える自由を取りました。







 void foo() { int a = 10; void bar() { a = 20; } bar(); }
      
      





Rustでは、ネストされた関数を宣言できますが、変数をキャプチャすることはできません;このため、クロージャーが使用されます。







 fn foo() { let mut a = 10; fn bar() { //a = 20; // Error. } let mut baz = || { a = 20 }; baz(); }
      
      





[実行]









動的閉鎖



Rustにはラムダ/デリゲート/クロージャもあります。 この例は本文の上位にありましたが、詳細に興味がある場合は、 ドキュメント翻訳 )を参照してください。









可変数の引数



Dには、いくつかのパラメーターを単一の型付き配列として受け入れることができる特別な構造 "..."があります。







 import std.stdio; int sum( int[] values ... ) { int s = 0; foreach( int x ; values ) { s += x; } return s; } int main() { writefln( "sum = %d", sum( 8 , 7 , 6 ) ); int[] ints = [ 8 , 7 , 6 ]; writefln( "sum = %d", sum( ints ) ); return 0; }
      
      





Rustは可変数の引数を直接サポートしていませんが、代わりにスライサーまたは反復子を使用することをお勧めします。







 fn sum(values: &[i32]) -> i32 { let mut res = 0; for val in values { res += *val; } res } fn main() { println!("{}", sum(&[1, 2, 3])); let ints = vec![3, 4, 5]; println!("{}", sum(&ints)); }
      
      





[実行]









おわりに



以上です。 もちろん、3番目の機能に基づいた2つの言語の比較は非常に具体的ですが、特定の結論を引き出すことができます。 自分で行うことをお勧めします。



さて、伝統により、タイトルをコンパイルします







 macro_rules! man { (C => D) => {{ "https://habrahabr.ru/post/276227/" }}; (C => D => Rust) => {{ "https://habrahabr.ru/post/280904/" }}; (Rust => $any:tt) => {{ "You are doing it wrong!" }}; } fn main() { println!("{}", man!(C => D)); println!("{}", man!(C => D => Rust)); println!("{}", man!(Rust => C)); println!("{}", man!(Rust => D)); }
      
      






All Articles