Как стать автором
Обновить

Комментарии 53

На D можно написать так:
auto add( X, Y )( X x, Y y ) if (__traits(compiles, x + y)) {
(это не самый правильный вариант, но самый простой) и тогда ошибка будет вполне внятной.
a.d(13): Error: template main.add cannot deduce function from argument types !()(int, string), candidates are:
a.d(5): main.add(X, Y)(X x, Y y) if (__traits(compiles, x + y))

P.S. Вобще, сравнение двух языков, да еще с примерами без _отличного_ знания обоих языков напоминает мне «перевожу с японского на сомалийский. Знание обоих языков — читаю со словарём». Извините.
Знание обоих языков — читаю со словарём

А можно какие-то аргументы? Хотелось бы думать, что Rust я знаю не так уж плохо.

Для D я брал готовый код и, разумеется, мог упустить какие-то нюансы. Впрочем, подозреваю, что гоферы точно так же не были довольны оригинальной статьёй. (:
На D можно написать так:

В курсе. Речь шла о том, что на Rust нельзя написать без явного указания требований к типам. То есть, в D мы можем написать "быстро и коротко", а можем "правильно". В первом случае, перекладываем проблемы на пользователей. При этом, я признаю, что далеко не всегда нужен такой сильный контроль над типами — шаблон может быть приватным, например. Но опять же, для Rust это последовательное решение в духе языка.
Да хотя бы вот (листнул на первый попавшийся пример)
Вы пишете «В Rust компилятор запрещает обращениe к не инициализированным переменным.». И все. Это как-то выделяет Rust? А D что, разрешает?
Это как-то выделяет Rust? А D что, разрешает?

D, по умолчанию, просто инициализирует все переменные:


int val;
writeln(val); // выведем int.init

Rust этого не делает и требует нас самих инициализировать:


let val: i32;
println!("{}", val); // use of possibly uninitialized variable

Согласитесь, что есть разница. Да, в плане "безопасности" она не особо существенна, но подход-то другой.

Ну и если честно, не совсем понял аргумент. Как процитированное относится к знанию языка? Я разве что-то неправильно сказал?

Вот сейчас вы раскрыли разницу. А в статье — нет.
Для языка с таким небольшим сообществом D поражает своими возможностями. Многое, что только ползёт в С++ (или напротив, ещё и не планирует), здесь уже есть: ranges, modules, template constraints (~concepts), contracts, unit tests, scope guards*. При этом сочетается всё как-то более гармонично, чем в C++. Меньше базовых принципов что ли.
Жаль, что работодателей мало интересуют D и Rust. Они разные, но выглядят очень интересно оба.
* Про последние не так давно узнал, к слову. На первый взгляд, удобнейшие возможности. Есть выступление Александреску на тему поддержки C++:
Скрытый текст

И описание на сайте D: dlang.org/exception-safe.html

scope guards

Справедливости ради, в плюсах оно есть в виде библиотечных решений. Например, BOOST_SCOPE_EXIT. Хотя в D оно и несколько более навороченное.
Точку с запятой в этом примере можно и опустить — она используeтся, чтобы превратить выражения (expressions) в инструкции (statements), а так как и main и println! ничего не возвращают (на самом деле, возвращается специальный пустой тип ()), то разницы нет.
А как всё же реализовать несколько точек выхода? Типа такого:

```d
int getHash( string str )
{
if( string.length == 0 ) return 0;
// считаем хеш по хитрому алгоритму
return hash;
}

```

Явно указывать название модуля не нужно, если мы не хотим объявить вложенный модуль, так как оно зависит от имени файла или директории.
В D на самом деле аналогично.

мы жертвуем лаконичностью и, отчасти, гибкостью ради «явности» и более удобных сообщений об ошибках — из-за необходимости указывать ограничения типам, проблема не может просочиться через много уровней, порождая кучу сообщений.
Но в примере как раз наоборот, сообщение в Rust не говорит какой именно параметр мы не так написали (trait Add is not implemented for the type), а вот в D нам не просто сказали, что нет реализации такой сигнатуры (Error: template instance main.add!(int, string) error instantiating), но и подсказали почему (Error: incompatible types for ((x) + (y)): 'int' and 'string').

В Rust компилятор запрещает обращениe к не инициализированным переменным.
В D просто нет такого понятия как «неинициированная переменная», так как с ними много проблем. От проблем с безопасностью, до сложностей с побитовым сравнением. Например, структуры в D сравниваются наиболее быстрым способом — через сравнение участков памяти.

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

a as i64
Вот, кстати, что это, что `cast(long) a` — одинаково не удобны в выражениях, в отличие от `a.to!long`
А как всё же реализовать несколько точек выхода?

Конечно же, в Rust есть return. Но его использование идиоматично только для ранних возвратов.
Но в примере как раз наоборот, сообщение в Rust не говорит какой именно параметр мы не так написали

На самом деле здесь важны не сообщения об ошибках, а типобезопасность. В отличие от шаблонов в C++ или в D, дженерики в Rust позволяют применять к типам-параметрам только те операции, которые разрешаются ограничениями на эти параметры. В этом плане Rust ближе к языкам типа Java или к концептам (так и не вошедшим в стандарт, емнип) в C++. Ну и сообщение об ошибке на самом деле говорит какой конкретно типовый параметр неправильный, просто автор статьи не привёл это сообщение полностью. Вообще у rustc сообщения об ошибках просто феноменальные.
так как с ними много проблем

Ну в Rust с ними нет проблем, потому что переменные, которым ничего не присвоено, использовать нельзя, компилятор это проверяет статически и способа обойти это нет. То, как в Rust сравниваются объекты, зависит от реализации трейта PartialEq. В случаях, когда этот трейт автоматически выведен через derive (99%), то если структура состоит целиком из примитивных типов, то всё сведётся к побайтовому сравнению.
Очень опасный приём, особенно в свете множественной диспетчеризации

Вообще говоря, вывод типов — это безумно удобная вещь. Вы писали когда-нибудь на Haskell? В отличие от хаскеля, в Rust вывод типов исключительно локальный. Проблем с ним не возникает никогда, а удобства — выше крыши. И если под "множественной диспетчеризацией" вы понимаете перегрузку функций, то её в расте нет, так что этой проблемы не возникает :)
Конечно же, в Rust есть return. Но его использование идиоматично только для ранних возвратов.

Тогда не очень понятно, что они сэкономили сделав return опциональным. Неконсистентный синтаксис получился.
В отличие от шаблонов в C++ или в D, дженерики в Rust позволяют применять к типам-параметрам только те операции, которые разрешаются ограничениями на эти параметры.
В D можно делать и так и так. Можно использовать структурную типизацию через шаблоны, а можно номинативную, через интерфейсы.

Ну и сообщение об ошибке на самом деле говорит какой конкретно типовый параметр неправильный, просто автор статьи не привёл это сообщение полностью.
Вот полностью из песочницы на сайте:

```rust
error: the trait `core::ops::Add<&str>` is not implemented for the type `_` [E0277]
:2:25: 2:56 note: in this expansion of format_args!
:3:1: 3:54 note: in this expansion of print! (defined in )
```

Как-то не очень понятно.

Ну в Rust с ними нет проблем, потому что переменные, которым ничего не присвоено, использовать нельзя, компилятор это проверяет статически и способа обойти это нет.
Но в 99% случаев инициализируются они всё равно дефолтными значениями. Наверняка в процессе хитрых кастов можно случайно обойти ограничение на неиспользование неинициированной памяти.

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

В большинстве случаев early returns не используются. Кроме того, Rust — это expression-based язык, как Scala. Например, вместо тернарного оператора в нём if:
let y = if x > 0 { ... } else { ... };

Отсутствие необходимости писать return для возврата значения, если это значение — последнее выражение в функции, вытекает из этого совершенно естественно.
номинативную, через интерфейсы

Интерфейсы — это всё-таки немного не то. Если я не ошибаюсь, в D, как и в C++ и в Java, интерфейсы подразумевают динамическую диспетчеризацию. В Rust динамическая диспетчеризация используется только если вы сами её попросите (через трейт-объекты).
Вот полностью из песочницы на сайте

Такое сообщение появляется, если в качестве параметра add() используется нетипизированный литерал (42). Для удобства в Rust, как и во многих языках, числовые литералы могут автоматически принимать любой тип. Но в данном контексте он используется в качестве аргумента дженериковой функции, поэтому компилятор не может вывести тип литерала так просто. Вот такая ошибка возникает, если тип литерала указать явно (42i32):
<anon>:9:20: 9:23 error: the trait `core::ops::Add<&str>` is not implemented for the type `i32` [E0277]
<anon>:9     println!("{}", add(42i32, "eee"));
                            ^~~

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

Неинициализированные переменные ничем не инициализируются, если этого не указать явно, и нет, случайно обойти это ограничение нельзя в принципе. Одна из целей Rust — это гарантия полной безопасности работы с памятью; это включает в себя полный запрет на работу с неинициализированной памятью вне unsafe.
изменение типа в зависимости от того, какая функция была вызвана первой

Вообще-то это и есть вывод типов. Компилятор выводит тип переменной в зависимости от того, как она используется. Просто в Rust вывод типов действует внутри функции целиком, а не только при присваивании значения, как с auto в C++.
Редактирование такого кода, как хождение по минному полю. Ну или покажите пример, где оно действительно полезно.

Вам это так кажется. Редактировать и рефакторить такой код ничуть не сложнее, чем код без вывода типов, даже в чём-то проще — меньше печатать нужно. Почти не бывает такого, чтобы при рефакторинге тип переменной внезапно менялся, хотя бы потому, что объявлять отдельностоящие неинициализированные переменные неидиоматично. Кроме того, объявления типов обязательны в сигнатурах функций и констант/статических переменных, даже если они объявляются внутри другой функции.
Пример "действительной полезности" привести сложно — это всё-таки не фича уровня borrow checker'а, без которой невозможно жить, а элементарное удобство. Обойтись без этого можно всегда. Но я вот совершенно не могу придумать несинтетического примера, где это бы помешало. Специально сейчас пять минут посвятил просмотру кода своих библиотек, и не нашёл ни одного проблемного места.
Отсутствие необходимости писать return для возврата значения, если это значение — последнее выражение в функции, вытекает из этого совершенно естественно.
Кажется я понял. Не «точка с запятой не обазательна» или «точка с запятой превращает выражение в утверждение», а просто всё есть выражения, и функция возвращает значение последнего, просто последнее выражение может быть пустым и возвращать соответственно пустоту.

Интерфейсы — это всё-таки немного не то. Если я не ошибаюсь, в D, как и в C++ и в Java, интерфейсы подразумевают динамическую диспетчеризацию.
Нет, не подразумевают. И речь не только про интерфейсы классов.

На мой взгляд, понятнее некуда.
Ну то есть, приходится отказывать себе в удобстве, чтобы был понятный вывод.

дна из целей Rust — это гарантия полной безопасности работы с памятью; это включает в себя полный запрет на работу с неинициализированной памятью вне unsafe.
Ну а в unsafe вы получаете все эти проблемы, что и требовалось доказать :-)

Редактировать и рефакторить такой код ничуть не сложнее, чем код без вывода типов, даже в чём-то проще — меньше печатать нужно.
Я всеми руками за выведение типов и каждый день его использую. Речь исключительно о: «в Rust вывод типов действует внутри функции целиком, а не только при присваивании значения».

Пример «действительной полезности» привести сложно — это всё-таки не фича уровня borrow checker'а, без которой невозможно жить, а элементарное удобство.
Так покажите это удобство.
Нет, не подразумевают

Что же это за такие интерфейсы?
отказывать себе в удобстве

Всё ещё не понимаю, про какое удобство вы говорите. На мой взгляд, шаблоны по типу C++, хоть и мощнее, значительно сложнее в правильном использовании. Дженерики с ограничениями трейтов проще и практичнее. И удобнее, потому что я точно знаю, когда пишу дженериковую функцию, что она будет работать везде и всегда, какие бы типы в неё не передали.
Ну а в unsafe вы получаете все эти проблемы, что и требовалось доказать :-)

Простите, но это настолько избитая тема, что я уже не хочу на неё отвечать подробно. Кратко — нет, это не то что требовалось доказать, и да, гарантия того, что подобные вещи возможны исключительно в unsafe, это game breaker. Единственный способ получить непроинициализированную переменную — это вызвать unsafe-функцию mem::uninitialized():
let x: i32 = unsafe { mem::uninitialized() };

И хотя бы поэтому случайно обратиться к неинициализированной памяти сложно. Особенно если у вас проекте указано #![forbid(unsafe)].
Так покажите это удобство.

Я не могу этого сделать, потому что вывод типов используется везде, и в каждом конкретном участке кода вывода типов не очень много, и примеры будут неубедительными. Если же писать код много, удобство становится очень заметным.
Речь не о выводе типов, которая есть и в D, а изменение типа в зависимости от того, какая функция была вызвана первой.

От этого зависит сообщение об ошибке. Если программа компилируется, то тип от перемены порядка вызовов меняться не будет.
Редактирование такого кода, как хождение по минному полю. Ну или покажите пример, где оно действительно полезно.

Лучше покажите как при помощи этой фичи можно в ногу неожиданно выстрелить. (:

Впрочем, в Rust есть ещё одна "спорная фича":
let a = 10;
let a = a.to_string();
let a = Some(a);

На всякий случай, уточняю: переменная a неизменяемая, просто тут три разных а. Когда впервые увидел, то показалось, что это ужасное решение и источник непонятных ошибок. А на деле оказывается удобно.
Я пацифист.
А три разных а — и правда ужасное решение ибо вносит неразбериху. Не стоит оно того сомнительного удобства.
и правда ужасное решение ибо вносит неразбериху. Не стоит оно того сомнительного удобства.

А ведь кто-то так думает про шаблоны...
В D порядок объявления шаблонов ни на что не влияет. А тут одна переменная втихую перекрывает другую.
Я не про порядок, а про точку зрения. Или про "парадокс Блаба", если угодно. Гоферы живут без шаблонов и не (особо) жалуются, а вам кажется, что языки без обобщённого программирования заведомо убоги. Так и тут — вы видите источник проблем, а кто-то удобную возможность. Стоит её распробовать перед тем как критиковать.

Собственно, не в расте эту штуку придумали. И ограниченное "скрытие" есть даже в С++.
В JS вполне частая ситуация, когда кому-то лень было придумывать новое имя и он воспользовался существующей переменной. В итоге во второй половине функции нет доступа к затёртому значению. А чтобы его получить нужно разделить имена. Вручную.
А чтобы его получить нужно разделить имена. Вручную.

Дык, если нам нужен доступ к переменным, то код мы, очевидно, и так модифицируем. В чём проблема переименовать?

А если код был бы вообще в виде цепочки вызовов, за которую вы так ратуете, записан? Тогда потребовалось бы ещё больше изменений.
Если сразу именовать по разному, то не будет проблемы переименовать вообще.
Чтобы разорвать цепочку не надо перелопачивать весь код — место разрыва локализуется в нескольких строках.
Оно досталось внаследство от функциональщины, того же OCaml, насколько я помню.
Плюсы:
  • помогает убрать изменяемость объекта без лишнего блока, иногда очень удобно.
  • хорошо сочетается с семантикой перемещения — let foo = foo.unwrap();.
  • если что-то пошло не так, то ты почти всегда получишь предупреждение о неиспользованной затененной переменной.
  • ну и уж если совсем не нравится, то для своего проекта можно clippy подтянуть, в нем три проверки есть (по умолчанию все выключены):
    shadow_reuse — rebinding a name to an expression that re-uses the original value, e.g. let x = x + 1
    shadow_same — rebinding a name to itself, e.g. let mut x = &mut x
    shadow_unrelated — The name is re-bound without even using the original value

В некоторых языках явно различают let и let req. Отсутствие этого разделения в Haskell меня расстраивает: достаточно типичная ошибка в нем — в блоке let n = 5 in let n1 = n+1 in… можно перепутать переменные n и n1.
Реализация let req в неленивом языке нетривиальна, а в C-подобных языках требуется он достаточно редко. По этому нерекурсивный let с возможностью переопределить переменную зависимым от нее значением очень полезна.
Тогда не очень понятно, что они сэкономили сделав return опциональным.

Короткие функции выглядят гораздо приятнее. return в 1-2-строчных функциях меня очень раздражает. А в C++ до недавнего времени константные функции могли быть только однострочными.
Кроме того, в Rust принято использовать return для нештатного завершения, обычно при ошибке. Так как исключения не поддерживаются, такая двойственность синтаксиса делает код более читабельным.
А как всё же реализовать несколько точек выхода?

Точно так же: http://is.gd/WQfIrK
Суть в том, что можно опускать return в конце любого scope, получая значение последнего выражения в блоке: http://is.gd/UhSmzV
В D на самом деле аналогично.

Да, знаю, просто не решился переделывать оригинальные примеры кода. Признаю, сравнение из-за этого может выглядеть не совсем объективно…
Хотя некоторая разница всё-таки есть: в расте невозможно дать имя модулю изнутри, так что аналога конструкции module main; нет.
В D просто нет такого понятия как «неинициированная переменная», так как с ними много проблем.

Хм… а вот это что такое?
int a = void;
writeln(a); // ???

Вот, кстати, что это, что cast(long) a — одинаково не удобны в выражениях, в отличие от a.to!long

Почему?
Ок, есть, но по умолчанию, все переменные инициализированы. :-)
Потому что, когда цепочки пишешь приходится в дополнительные скобки брать.
Потому что, когда цепочки пишешь приходится в дополнительные скобки брать.

Да, про это не подумал.
a as i64

Вот, кстати, что это, что cast(long) a — одинаково не удобны в выражениях, в отличие от a.to!long

Они и должны быть громоздкими и обращающими на себя внимание, как по мне. Это ж преобразования типов — в этом месте легко вносятся логические ошибки.
В любом коде легко вносятся логические ошибки. Но в преобразовании типов нет ничего особо страшного.
НЛО прилетело и опубликовало эту надпись здесь
Зачёт! :-)
Вот даже 2.0, а то чего-то я параметры макроса не использовал:
use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash)]
enum Lang {
    Go,
    D,
    Rust,
}

struct Info {
    basics: &'static str,
    concurrency: &'static str,
}

macro_rules! man {
    ($lang1:ident => $lang2:ident) => {{
        let mut db = HashMap::new();
        db.insert((Lang::Go, Lang::D), Info {
            basics: "https://habrahabr.ru/post/279657",
            concurrency: "https://habrahabr.ru/post/280378",
        });
        db.insert((Lang::D, Lang::Rust), Info {
            basics: "https://habrahabr.ru/post/280642",
            concurrency: "TODO: DarkEld3r еще не написал",
        });
        db.remove(&(Lang::$lang1, Lang::$lang2)).unwrap()
    }}
}

fn main() {
    println!("Go => D basics: {}", man!(Go => D).basics);
    println!("Go => D concurrency: {}", man!(Go => D).concurrency);
    println!("D => Rust basics: {}", man!(D => Rust).basics);
    println!("D => Rust concurrency: {}", man!(D => Rust).concurrency);
}

play.rust-lang
Эх, вечно я над всякой ерундой залипаю.
У Вас каждый man! новый хэшмап создает и заполняет каждый раз.
enum Lang {
Go,
D,
Rust,
}
use Lang::*;

struct Info {
basics: &'static str,
concurrency: &'static str,
}

macro_rules! man {
($lang1:ident => $lang2:ident) => {{
match ($lang1, $lang2) {
(Go, D) => Info {
basics: «habrahabr.ru/post/279657»,
concurrency: «habrahabr.ru/post/280378»,
},
(D, Rust) => Info {
basics: «habrahabr.ru/post/280642»,
concurrency: «TODO: DarkEld3r еще не написал»,
},
_ => unreachable!()
}
}}
}

fn main() {
println!(«Go => D basics: {}», man!(Go => D).basics);
println!(«Go => D concurrency: {}», man!(Go => D).concurrency);
println!(«D => Rust basics: {}», man!(D => Rust).basics);
println!(«D => Rust concurrency: {}», man!(D => Rust).concurrency);
}
Ага, я об этом подумал уже когда запостил и даже накидал аналогичную версию 3.0, но потом решил что не очень клево превращать комментарии к статье в git)
Аналог на D:
import std.traits;

alias Lang = int;

enum : int {
    Go ,
    D ,
    Rust ,
};

struct Info {
    string basics;
    string concurrency;
}

enum articles = [
    [ Go , D ] : Info( "https://habrahabr.ru/post/279657" , "https://habrahabr.ru/post/280378" ) ,
    [ D , Rust ] : Info( "https://habrahabr.ru/post/280642" , "TODO: DarkEld3r еще не написал, скорее бы" ) ,
];

template man( Lang function( Lang ) axis )
{
    static if( is( FunctionTypeOf!axis args == __parameters ) )
    {
        enum from = mixin( __traits( identifier , args ) );
        enum to = axis( from );
        enum man = articles[[ from , to ]];
    }
}

unittest {
    static assert( man!(Go => D).basics == "https://habrahabr.ru/post/279657" );
    static assert( man!(Go => D).concurrency == "https://habrahabr.ru/post/280378" );
    static assert( man!(D => Rust).basics == "https://habrahabr.ru/post/280642" );
    static assert( man!(D => Rust).concurrency == "TODO: DarkEld3r еще не написал, скорее бы" );
    static assert( !is( man!(Go => Rust).concurrency ) );
}

void main( ) { }
Скоро еще будет сравнение D с Pony ;)
НЛО прилетело и опубликовало эту надпись здесь
Мелкие неточности из сравнения типов D и Go просочились сюда, например в Go byte это алиас для uint8, а rune для int32.

Согласен, но решил не переделывать оригинал. Меня ещё и int смущает, который имеет фиксированный размер в D, но зачем-то приведён как аналог платформозависимого int из Go.

Кстати, в Go есть возможность помечать функции как никогда не возвращающие управление? Насколько я вижу, в D были предложения ввести @noreturn атрибут. В Rust такие функции имеются и "результат" можно присвоить любому типу. Применяется, например, так:
let a: String = match 10 {
    10 => "normal value".to_owned(),
    _  => panic!(),
};
Потому, что если вы пишете кроссплатформенный софт, то вынуждены писать его предполагая, что размер инта минимальный из возможных.
Дык, если нам надо, то будем использовать типы фиксированного размера. В Go int — это аналог ptrdiff_t, а не int из D.
Наоборот, аналогом ptrdiff_t в го является int. Но сам int имеет более широкую область применения, чем смещение указателей. Согласитесь, при портировании программы будет странно менять общий тип int на зачастую не относящийся к делу ptrdiff_t.
Наоборот, аналогом ptrdiff_t в го является int.

А в чём разница?
Собственно, я могу согласиться с тем, что название int для платформозависимого типа не самое удачное. В этом плане мне нравится решение раста: isize/usize длиннее типов с фиксированным размером, да и название говорящее.
Но сам int имеет более широкую область применения, чем смещение указателей.

В Go или вообще? И неужели в D не возникает желания использовать "знаковый size_t" не только для разницы указателей?

А при портировании всё равно какие-то изменения вносить придётся, иначе код получится убогим. Заодно будет поводом пересмотреть действительно ли там необходим ptrdiff_t.
Разница в направлении портирования. В D больше типов, в Go меньше. Так что в одном направлении приходится сводить несколько типов к одному (что относительно просто), а в другом — выбирать нужный тип (и тут конечно есть сложности, ибо в коде не хватает информации для выбора правильного типа — нужно анализировать алгоритм).
И в Го и вообще. А почему должно быть желание использовать типы не по назначению?
Я всё-таки продолжаю думать, что в 99% случаев, при портировании проблемы с типами будут в последнюю очередь.
А почему должно быть желание использовать типы не по назначению?

В смысле?
НЛО прилетело и опубликовало эту надпись здесь
Да, panic — это макрос, который раскрывается в вызов как раз такой специальной функции:
fn begin_unwind<M: Any + Send>(msg: M, file_line: &(&'static str, u32)) -> ! { ... }

Для наглядности мой пример можно и переписать заменив панику на собственную функцию:
fn my_panic() -> ! {
    loop {}
}

fn fake_panic() {}

fn main() {
    let _a: String = match 10 {
        10 => "normal value".to_owned(),
        //_ => fake_panic(), // Error.
        _ => my_panic(),
    };
}
Да нет, всё правильно. Не важно чему оно там алиас. Область применения у них эквивалентна.
Самое забавное тут то, что после изучения Rust отношение к D несколько изменилось — в плане лаконичности и выразительности последний сильно выигрывает. Впрочем, «явность» Rust-сообщество наоборот считает преимуществом. По моим ощущениям, в Rust чаще руководствуются «академической правильностью», а в D более практичный подход.

У меня после продолжительных экспериментов с Rust осталось очень похожее впечатление. Его строгость очень привлекает в больших проектах с критической важностью корректности, но затраты времени на написание простых вещей очень уж непрактичны. Вероятнее всего, даже если я решу начинать какой-либо новый проект на Rust, прототип всё равно будет написан на D.
но затраты времени на написание простых вещей очень уж непрактичны

Всё-таки думаю, что ситуация не настолько плоха. Если мы пишем что-то действительно простое, то и мест, где придётся "воевать" с бороу чекером будет мало. Сильнее всего это будет проявляться, если кода в общем мало и при этом активно изобретаются какие-то свои структуры данных — вот тут придётся вникать в тонкости языка и использовать unsafe. Если же пользоваться готовым, то всё куда проще. Конечно, у D есть сборка мусора, что должно давать выигрыш для быстрого прототипирования… хотя тут любители динамическиx языков предпочтут что-нибудь другое.

Впрочем, у меня маловато практического опыта с Rust (а с D вообще нет), чтобы делать окончательные выводы.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории