📜 ⬆️ ⬇️

man! (C => D => Rust)

The previous article was perceived better than I expected, so I decided to continue the experiment. This is a kind of answer to the translation of the article Programming in D for C Programmers by Dmitry aka vintage . It seems to me that in the field of application C Rust is more appropriate than the replacement of Go, as suggested in the last article. The more interesting it will be to compare. Again, I will not give the code on C, especially since the analogue on D still looks laconic.



Get type size in bytes


Let me remind you that in C (and in C ++) for this purpose there is a special operator sizeof , which can be applied to both types and variables. In D, the size is accessible through a property (which can also be applied to variables):


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

Rust uses a function that refers to the internals of the compiler (corresponding to intrinsic ):


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

At the same time, due to the lack of overloading of functions, another function is used to obtain the size of variables - size_of_val . Perhaps this separation is somewhat less convenient, but it is not necessary to enter special keywords — the usual language mechanisms are used:


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

Funny thing: in Rust, empty structures (such as Foo from the example) occupy 0 bytes, respectively, an array of any size of such structures will also occupy 0 bytes.
[Play around with the code]


We get the maximum and minimum value of the type


In D, again, type properties are used:


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

Rust uses C-like constants:


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

[Run]
')


Type Mapping Table


 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 

The comparison is not entirely correct, since C uses platform-dependent types, and in D, on the contrary, it is of a fixed size. For Rust, I picked exactly analogs of fixed size.


Special values ​​of floating point numbers


 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 

As you can see, Rust uses constants again, which, by the way, are usually written in upper case.


The remainder of dividing real numbers


There are no revelations - in Rust, as in D, there is the operator% .


Processing NaN Values


In both D and Rust, a comparison with NaN will result in false .


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

[Run]


Acerta - a useful error detection mechanism


Both languages ​​provide assertions out of the box, but in D they are a special language construct:


 assert( e == 0 ); 

A in Rust - just macros:


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

However, there is an interesting difference: in D, the asserts in the release assembly are disabled, except for the special case assert(0) , which is used to indicate the unattainable code in normal execution.

In Rust, they remain in the release, however, similar behavior can be obtained using the macro debug_assert! . For a more explicit designation of the unattainable when using a separate macro unreachable! .


Iteration over an array (collection)


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

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

There is not much difference, although the for loop in Rust is not similar to its relative from C.


Array elements initialization


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

In D, we can initialize an array with a single value, as shown above. It is worth noting that after creating the array, it will first be initialized with the default value of the type it contains.


 let array = [value; 17]; 

Rust has a special syntax for this case.


Creating variable length arrays


D has built-in support for variable-length arrays:


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

Rust, following its "philosophy of clarity," requires you to specify a value with which new elements will be initialized when calling the resize method. Therefore, it is more correct to write the example as follows:


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

Please note that we do not have to specify the type of elements contained in the vector - they will be displayed automatically.


String concatenation


In D, there are special overloaded operators ~ and ~ = designed to join lists:


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

Official documentation argues for the presence of individual operators so that overloading the operator + can lead to surprises.


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

In Rust , on the one hand, these problems are impossible because of the need for explicit type conversions. On the other hand, the += operator for strings is still not implemented.


Formatted output


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

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

[Run]

As you can see, the languages ​​in this regard are not particularly different. Is that in Rust formatting is not similar to the "usual" from C.


Appeal to functions before the announcement


Both languages ​​use modules , so the order of definition does not matter and preliminary declarations are not needed.

Rust example:


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

Functions without Arguments


 void foo() { ... } 

 fn foo() { ... } 

Comparison loses some sense in isolation from C, since both languages ​​do not require to specify void to indicate the absence of arguments.


Exit from multiple code blocks


 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; } } } 

The break / continue syntax with a label is almost identical.


Structure namespace


Again, in both languages ​​there is no separate namespace for structures.


Branching by string values ​​(for example, processing command line arguments)


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

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

In this case, there is not much difference, but in Rust the match construction is a full-fledged comparison with the sample, which allows you to do more clever things:


 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..."), } } 

Read more in the documentation ( translation ).


Alignment of structure fields


D has a special syntax with which you can fine-tune the alignment of individual fields:


 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 } 

In Rust, you can only completely disable alignment for individual structures:


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

Anonymous structures and associations


D supports anonymous structures, which allows you to save a flat front end for nested entities:


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

There are no anonymous structures or unions in Rust, so similar code would look like this:


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

Moreover, Rust will not allow to accidentally turn to the wrong join field that was initialized. Therefore, you will have to address them differently :


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

Thus, associations cannot be used as (semi) legal type conversions, but potential errors are eliminated.


Defining structures and variables


Both languages ​​require separate type and variable declarations, that is, as in C, it will not work:


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


Getting the offset field structure


In D, fields have a special offsetof property:


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

At the moment, Rust does not support this feature, so if necessary, you will have to manually calculate the displacements by manipulating pointers to members of the structure. However, offsetof is a reserved keyword, which means that such functionality should appear over time.


Initializing associations


D requires an explicit indication of which join field is assigned a value:


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

Rust does likewise, moreover, as already mentioned, it will not allow accessing the wrong join field that was initialized.


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

Initialization of structures


In the D structure, you can initialize both in order and with the indication of the field names:


 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 }; 

In Rust, the names must be indicated:


 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. 

Array initialization


In D, there are many ways to initialize an array, including specifying the indexes of elements to be initialized:


 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. 

In Rust, it is possible either to list all the values ​​with which we want to initialize the array, or to specify one value for all the elements of the array:


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

Escaping special characters in strings


Both languages, along with the screening of individual characters, support the so-called "raw lines":


 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#""[^\\]*(\\.[^\\]*)*""#; 

In Rust, "raw lines" are formed quite simply : they begin with the character r , followed by an arbitrary number of characters # , followed by a quote ( " ). The lines are completed with a quote with the same number # . In D, the number of lines is noticeably larger .


ASCII versus multibyte encodings


D supports several types of strings that store different types of characters:


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

In Rust, there is only one type of string that represents a sequence of UTF-8 bytes:


 let str = "hello"; 

Konstantin aka kstep published on Habré a series of translations about string types in Rust, so if you are interested in the details, I recommend reading them. Well, or with official documentation ( translation ).


Display enumeration on array


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

Analog on Rust using the macro collect! would look like this:


 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", ]; 

Creating new types


D allows you to create new types of existing (strong typedef):


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

Including, with the task of default value:


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

In Rust, this is done through the use of a tuple structure ( tuple struct , translation ):


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

Creating a value without initializing Rust will not allow it, and to create a default value, it will be correct to implement the Default trait:


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

[Run]


Comparison of structures


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

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

The only difference is that D implicitly implements a comparison operator for us, and Rust needs to ask about it, which we do through #[derive(PartialEq)] .


String comparison


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

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

In both languages, strings can be compared for equality and more / less.


Sorting arrays


D uses generic implementations of algorithms:


 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 uses a slightly different approach: sorting, like some other algorithms, is implemented for "slices" (slice), and those containers for which it makes sense can be brought to them.


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

[Run]
From small differences: the comparison should return not bool , but Ordering (more / less / equal).

This comparison made you wonder why in Rust it was done differently from D or C ++. Offhand I do not see the advantages and disadvantages of both approaches, so we will write off simply on the peculiarities of the language.


String literals


 "This text \"spans\" multiple lines " 

 "This text \"spans\" multiple lines " 

Both languages ​​support multiline string constants.


Crawling data structures


Despite the name, in this paragraph, in my opinion, only the ability of nested functions to access variables declared in external is demonstrated, so I took the liberty to rewrite the code:


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

In Rust, you can declare nested functions, but they cannot capture variables, for this they use closures:


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

[Run]


Dynamic closures


Rust also has lyabmdy / delegates / closures. The example was higher in the text, but if you are interested in the details, check out the documentation ( translation ).


Variable number of arguments


In D, there is a special "..." construct that allows you to take several parameters as a single typed array:


 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 does not have direct support for a variable number of arguments, instead it is proposed to use slices or iterators:


 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)); } 

[Run]


Conclusion


That's all. Of course, a comparison of two languages, based on the characteristics of the third, is quite specific, but certain conclusions can be made. I suggest you make them yourself.

Well, according to tradition, we will make the title compiled :


 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)); } 

Source: https://habr.com/ru/post/280904/


All Articles