Предыдущую статью восприняли лучше, чем я ожидал, так что решился на продолжение эксперимента. Это своеобразный ответ на перевод статьи Programming in D for C Programmers за авторством Дмитрия aka vintage. Как мне кажется, в области применения C Rust более уместен, чем замена Go, как предполагалось в прошлой статье. Тем интереснее будет сравнить. Опять таки, код на С приводить не буду, тем более что аналог на D всё равно смотрится лаконичнее.



Получаем размер типа в байтах


Напомню, что в С (и в С++) для этой цели существует специальный оператор sizeof, который может применяться как к типам, так и к переменным. В D же размер доступен через свойство (которое тоже можно применять и к переменным):


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

В Rust используется функция, которая обращается к внутренностям компилятора (соответствующему intrinsic):


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 используются С-подобные константы:


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

[Запустить]


Таблица соответствия типов


C                         D          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

Сравнение не совсем правильное, так как в С используются платформозависимые типы, а в 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, имеется оператор %.


Обработка NaN значений


И в D, и в Rust сравнение с NaN даст в результате false.


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

[Запустить]


Асерты — полезный механизм выявления ошибок


Оба языка предоставляют асерты "из коробки", но в D они являются специальной языковой конструкцией:


assert( e == 0 );

A в Rust — просто макросами:


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

Впрочем, есть и интересное отличие: в D асерты в релизной сборке отключаются, кроме специального случая assert(0), который используется для обозначения недостижимого при нормальном выполнении кода.

В Rust они остаются и в релизе, впрочем, аналогичное поведение можно получить при помощи макроса debug_assert!. Для более явного обозначения недостижимого когда используется отдельный макрос unreachable!.


Итерирование по массиву (коллекции)


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

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

Особой разницы нет, хотя цикл for в Rust и не похож на своего родственника из С.


Инициализация элементов массива


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 форматирование не похоже на "привычное" из С.


Обращение к функциям до объявления


Оба языка используют модули, поэтому порядок определения не имеет значения и предварительные объявления не нужны.

Пример на Rust:


fn foo() -> Test {
    bar()
}

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

struct Test {
    a: i32,
    b: i32,
}

Функции без аргументов


void foo() {
    ...
}

fn foo() {
    ...
}

Сравнение несколько теряет смысл в отрыве от С, так как оба языка не требуют указывать void для обозначения отсутствия аргументов.


Выход из нескольких блоков кода


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;

f.i;
f.x;
f.y;
f.p;

В Rust нет анонимных структур или объединений, поэтому аналогичный код будет выглядеть вот так:


enum Bar {
    Baz {x: i32, y: i32 },
    Option(i8),
}

struct Foo {
    i: i32,
    e: Bar,
}

Более того, Rust не позволит случайно обратиться не к тому полю объединения, которые было инициализировано. Поэтому и обращаться к ним придётся иначе:


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

Таким образом, объединения нельзя использовать как (полу)легальное преобразование типов, зато исключаются потенциальные ошибки.


Определение структур и переменных


Оба языка требуют раздельного объявления типа и переменной, то есть, как на С, записать не получится:


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, за которым следует произвольное количество символов #, с последующей кавычкой ("). Завершаются строки кавычкой с таким же количеством #. В D разновидностей строк заметно больше.


ASCII против многобайтных кодировок


В D поддерживается несколько видов строк, которые хранят символы разного типа:


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

В Rust существует только один тип строк, которые представляют последовательность UTF-8 байт:


let str = "hello";

Константин aka kstep опубликовал на хабре серию переводов про строковые типы в Rust, так что если вас интересуют подробности, то рекомендую ознакомиться с ними. Ну или с официальной документацией (перевод).


Отображение перечисления на массив


enum COLORS { red, blue, green }

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

Аналог на Rust с применением макроса 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 позволяет создавать новые типы из имеющихся (strong 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 struct, перевод):


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 используется несколько другой подход: сортировка, как и некоторые другие алгоритмы, реализована для "срезов" (slice), а те контейнеры, для которых это имеет смысл, умеют к ним приводиться.


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

[Запустить]
Из мелких отличий: сравнение должно возвращать не bool, а Ordering (больше/меньше/равно).

Данное сравнение заставило задуматься, почему в Rust сделано не так как в D или С++. Навскидку не вижу преимуществ и недостатков обоих подходов, так что спишем просто на особенности языка.


Строковые литералы


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

[Запустить]


Заключение


Вот и всё. Конечно, сравнение двух языков, отталкивающееся от особенностей третьего, получается довольно специфическим, но определённые выводы сделать можно. Предлагаю вам сделать их самостоятельно.

Ну и по традиции сделаем заголовок компилирующимся:


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

Комментарии (35)


  1. zolkko
    11.04.2016 03:06
    +2

    На предыдущей неделе union (https://github.com/joshtriplett/rfcs/blob/untagged_union/text/0000-union.md) фигурировали в this week in rust в final comment period.


    1. kstep
      11.04.2016 09:15
      +1

      Эх, а мне больше unsafe enum были по душе...


      1. DarkEld3r
        11.04.2016 10:11
        +1

        Мне тоже — меньше ключевых слов, да и такое применение unsafe вполне логично.


        1. Gorthauer87
          11.04.2016 12:03
          +1

          А меня вот словосочетание unsafe enum сбивает с толку, не сразу очевидно, что речь идет о сишных union'ах.


          1. DarkEld3r
            11.04.2016 12:38

            Тоже аргумент… с другой стороны, это всё-таки "продвинутая часть языка", то есть к использованию FFI, как мне кажется, подходят уже с определёнными знаниями. Опять же, различий и так хватает. Скажем, repr(packed) против __attribute__((__packed__)) или прагм.


  1. Halt
    11.04.2016 09:17
    +1

    Про выравнивание в Rust неплохо написано в Растономиконе.


    1. DarkEld3r
      11.04.2016 10:10
      +1

      Да там много о чём хорошо написано, но ведь про (не)возможность задавать выравнивание на уровней отдельных полей я ничего упустил?..


      1. Halt
        11.04.2016 10:16
        +1

        Насколько я помню, нет. В RFC часто эту тему муссируют, но в ближайших релизах такого не видно.


  1. vintage
    11.04.2016 09:44
    +4

    В Rust они остаются и в релизе, впрочем, аналогичное поведение можно получить при помощи макроса debug_assert!.

    Тут стоит заметить, что в D есть конструкция debug(name) вида:


    debug( anyDebugName ) {
        // any code
    }

    Она вырезает код, если при компиляции не указан соответствующий флаг: --debug=anyDebugName


    Аналогично, есть конструкция version(name), позволяющая варьировать даже релизный код в зависимости от платформы, архитектуры, компилятора или опять же вручную заданного условия.


    В Rust, с одной стороны, эти проблемы невозможны из-за необходимости явного приведения типов. С другой стороны, оператор += для строк всё-таки не реализован.

    Всё же сложение и соединение — разные операции. Хорошо это видно на примере векторов (массивов чисел), которые можно соединять, а можно поэлементно складывать. Но для векторов необходимо и 3 вида умножения (скалярное, поэлементное, векторное), которых зачастую нет.


    Более того, Rust не позволит случайно обратиться не к тому полю объединения, которые было инициализировано. Поэтому и обращаться к ним придётся иначе

    Тут вы путаете объединение и тип-сумму. Суть объединения в том, что одни и те же данные могут представлять из себя разные типы одновременно. Например: масссив байт и число. А вот тип-сумма в D выражается через Algebraic:


    Algebraic!(int, string) variant;
    
    variant = 10;
    
    variant.visit!(
        (string s) { writeln( "Hello " , s ); },
        (int i) { writeln( "Hello " , i ); }
    );

    В Rust существует только один тип строк, которые представляют последовательность UTF-8 байт

    Это же крайне не эффективно. Даже длину строки не узнать, не пробежавшись по ней. Та же ситуация со срезами.


    let cstring: BTreeMap<_, _> = collect![

    Не думаю, что бинарное дерево тут будет эффективней простого массива. Есть ли возможность отобразить перечисление именно на массив?


    В Rust это делается через использование структуры-кортежа (tuple struct, перевод)

    На самом деле и в D это всего-лишь параметризированная структура:


    struct Typedef(T, T init = T.init, string cookie=null)

    В Rust используется несколько другой подход: сортировка, как и некоторые другие алгоритмы, реализована для «срезов» (slice), а те контейнеры, для которых это имеет смысл, умеют к ним приводиться.

    Всё же, диапазоны в D — более мощная абстракция, чем срезы, которые в D тоже есть.


    1. DarkEld3r
      11.04.2016 10:32
      +5

      Тут стоит заметить, что в D есть конструкция debug(name) вида:

      В Rust тоже:


      if cfg!(debug) {
          ...
      }

      Всё же сложение и соединение — разные операции.

      Пожалуй, соглашусь, но "сложение" строк — штука уже привычная и существует во многих языках. А пример с векторами, как по мне, как раз показывает, что операции с произвольными типами мало смысла укладывать в общий интерфейс.


      Тут вы путаете объединение и тип-сумму.

      Ну не то чтобы путаю. Просто в С есть только "старый добрый" union, да и в С++ тоже, разве что добавляются более удобные обёртки поверх него. И используются они и для того и для другого.

      А вот про Algebraic! в D действительно не знал, спасибо.


      Это же крайне не эффективно. Даже длину строки не узнать, не пробежавшись по ней.

      Длину (в байтах) узнать легко. Нужно ли легко/постоянно узнавать длину в символах — вопрос спорный.

      Ну и тут дело в том, что строки в Rust — это немного больше, чем "тупой контейнер", как в том же С++. Тем не менее, если есть необходимость, никто не запрещает нам работать с ними как с контейнером u8/u16/u32, просто это будут уже не строки в терминах языка.


      Есть ли возможность отобразить перечисление именно на массив?

      Наверное, я не совсем понял, что делается в D в том фрагменте кода. Как именно будет выглядеть результирующий массив?


      Всё же, диапазоны в D — более мощная абстракция, чем срезы, которые в D тоже есть.

      В расте используются итераторы, которые, несмотря на название (которое у меня вызывает ассоциации, в первую очередь с С++), представляют собой (в общем случае) как раз указатель плюс размер.


      1. vintage
        11.04.2016 13:50

        Наверное, я не совсем понял, что делается в D в том фрагменте кода. Как именно будет выглядеть результирующий массив?

        string[ 3 ] cstring = [ "red", "blue", "green" ];

        Перечисления по умолчанию имеют тип int.


        В расте используются итераторы, которые, несмотря на название (которое у меня вызывает ассоциации, в первую очередь с С++), представляют собой (в общем случае) как раз указатель плюс размер.

        Это классические срезы. Диапазоны же могут быть ленивыми, бесконечными, не иметь произвольного доступа, входными, выходными и тп. То есть срез — это частный случай конечного диапазона с произвольным доступом.


        1. DarkEld3r
          11.04.2016 14:18

          Это классические срезы.

          Нет, это просто моё неудачное описание. Ну то есть, да, срезы представляются как указатель и размер, итераторы по массиву — так же. Но там, как и в диапазонах D, может быть функция (а значит и ленивость) генерирующая бесконечную последовательность.


          Перечисления по умолчанию имеют тип int.

          То есть, нужен просто массив строковых представлений перечисления? Думал, что нам надо именно в обе стороны мапить значения. С массивом это ведь не будет работать в общем случае — ведь в enum можно произвольные значения задавать.

          В таком случае, как в Rust легко и "из коробки" сделать именно такое не знаю. Зато можно сделать иначе:


          #[derive(Debug)]
          enum Colors {
              Red,
              Blue,
              Green,
          }
          
          println!("{:?}", Colors::Red);
          
          let colors = [Colors::Red, Colors::Blue, Colors::Green];
          println!("{:?}", colors);

          А именно (автоматически) реализовать трейт Debug. Тогда сможем приводить значения перечисления к строке. Правда если захочется самим задать строковое представление, то придётся реализовывать трейт (Debug или Display) вручную.


    1. DarkEld3r
      11.04.2016 10:44

      На самом деле и в D это всего-лишь параметризированная структура:

      Почитал подробнее… пожалуй, в этом подход языков опять различается. Скажем, в Rust для получения "обёрнутого" значения используется всё тоже сопоставление с образцом, а в D имеется ещё один хелпер-шаблон TypedefType. Опять же, tuple struct может использоваться не только для этого.

      И правильно я понимаю, что по умолчанию Typedef для одинаковых типов будет использовать и одинаковый cookie, то есть типы будут одинаковы? а


      1. vintage
        11.04.2016 14:02

        TypedefType позволяет получить оригинальный тип, если вы его не знаете. Если знаете, то можете просто привести к нему. Зачем тут сопоставление с образом?


        Правильно:


        alias TypeInt1 = Typedef!int;
        alias TypeInt2 = Typedef!int;
        
        // The two Typedefs are the same type.
        static assert(is(TypeInt1 == TypeInt2));
        
        alias MoneyEuros = Typedef!(float, float.init, "euros");
        alias MoneyDollars = Typedef!(float, float.init, "dollars");
        
        // The two Typedefs are _not_ the same type.
        static assert(!is(MoneyEuros == MoneyDollars));


        1. DarkEld3r
          11.04.2016 14:34
          +2

          Зачем тут сопоставление с образом?

          В Rust это общий механизм. Так можно "вытаскивать значения" из структур и кортежей, в том числе и из tuple struct:


          struct Handle(i32);
          
          let h = Handle(10);
          let Handle(v) = h; // v: i32

          Просто так скастовать структуру в произвольный тип не получится.


          1. vintage
            11.04.2016 16:28
            +1

            А, фактически в D тоже используется сопоставление с образом, правда много более многословно:


            template TypedefType(T)
            {
                static if (is(T : Typedef!Arg, Arg))
                    alias TypedefType = Arg;
                else
                    alias TypedefType = T;
            }


    1. Bronx
      11.04.2016 12:10
      +5

      > Но для векторов необходимо и 3 вида умножения (скалярное, поэлементное, векторное)

      Лучше всё же не смешивать «векторы» (массивы чисел, с которыми можно делать что угодно) и векторы (математические объекты, подчиняющиеся строго определённым правилам преобразования). Скалярное и векторное (а лучше внешнее) умножение — это операции, имеющие смысл лишь для настоящих векторов, при этом поэлементное умножение векторов практически не нужно. Такие векторы должны описываться отдельными типами, с собственным набором операций. То же относится к «матрицам» и матрицам.


  1. Bozaro
    11.04.2016 10:37
    +3

    Это же крайне не эффективно. Даже длину строки не узнать, не пробежавшись по ней. Та же ситуация со срезами.

    Но, по крайне мере, это более честно и не маскирует проблемы.


    Представление строки в виде последовательности wchar_t так же, в общем случае, не позволяет узнать длину строки, не пробежавшись по ней.


    Мало того, что wchar_t-строка в Windows является UTF-16, так еще и не очень понятно, что нужно считать длиной строки (например: последовательность U+0438 (и), U+0306 (кратка) визуально выглядит как U+0439 (й)).


  1. QtRoS
    11.04.2016 11:28
    -7

    Выше по комментариям «высокосветские» беседы знатоков, дополню их комментарием абсолютно незнакомого с этими языками человека: D выглядит намного приятнее, чем Rust. Соблюдается принцип наименьшей неожиданности. Кратко, лаконично, все есть из коробки. Даже что-нибудь написать на нем захотелось, после Go даже как-то повеселее будет.