Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Знание обоих языков — читаю со словарём
На D можно написать так:
Это как-то выделяет Rust? А D что, разрешает?
D, по умолчанию, просто инициализирует все переменные:
int val;
writeln(val); // выведем int.initRust этого не делает и требует нас самих инициализировать:
let val: i32;
println!("{}", val); // use of possibly uninitialized variableСогласитесь, что есть разница. Да, в плане "безопасности" она не особо существенна, но подход-то другой.
Ну и если честно, не совсем понял аргумент. Как процитированное относится к знанию языка? Я разве что-то неправильно сказал?
Точку с запятой в этом примере можно и опустить — она используeтся, чтобы превратить выражения (expressions) в инструкции (statements), а так как и main и println! ничего не возвращают (на самом деле, возвращается специальный пустой тип ()), то разницы нет.А как всё же реализовать несколько точек выхода? Типа такого:
Явно указывать название модуля не нужно, если мы не хотим объявить вложенный модуль, так как оно зависит от имени файла или директории.В 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 не говорит какой именно параметр мы не так написали
так как с ними много проблем
Очень опасный приём, особенно в свете множественной диспетчеризации
Конечно же, в Rust есть return. Но его использование идиоматично только для ранних возвратов.
В отличие от шаблонов в C++ или в D, дженерики в Rust позволяют применять к типам-параметрам только те операции, которые разрешаются ограничениями на эти параметры.В D можно делать и так и так. Можно использовать структурную типизацию через шаблоны, а можно номинативную, через интерфейсы.
Ну и сообщение об ошибке на самом деле говорит какой конкретно типовый параметр неправильный, просто автор статьи не привёл это сообщение полностью.Вот полностью из песочницы на сайте:
Ну в Rust с ними нет проблем, потому что переменные, которым ничего не присвоено, использовать нельзя, компилятор это проверяет статически и способа обойти это нет.Но в 99% случаев инициализируются они всё равно дефолтными значениями. Наверняка в процессе хитрых кастов можно случайно обойти ограничение на неиспользование неинициированной памяти.
Вообще говоря, вывод типов — это безумно удобная вещь.Речь не о выводе типов, которая есть и в D, а изменение типа в зависимости от того, какая функция была вызвана первой. Редактирование такого кода, как хождение по минному полю. Ну или покажите пример, где оно действительно полезно.
Тогда не очень понятно, что они сэкономили сделав return опциональным. Неконсистентный синтаксис получился
if:let y = if x > 0 { ... } else { ... };номинативную, через интерфейсы
Вот полностью из песочницы на сайте
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% случаев инициализируются они всё равно дефолтными значениями. Наверняка в процессе хитрых кастов можно случайно обойти ограничение на неиспользование неинициированной памяти.
unsafe.изменение типа в зависимости от того, какая функция была вызвана первой
auto в C++.Редактирование такого кода, как хождение по минному полю. Ну или покажите пример, где оно действительно полезно.
Отсутствие необходимости писать return для возврата значения, если это значение — последнее выражение в функции, вытекает из этого совершенно естественно.Кажется я понял. Не «точка с запятой не обазательна» или «точка с запятой превращает выражение в утверждение», а просто всё есть выражения, и функция возвращает значение последнего, просто последнее выражение может быть пустым и возвращать соответственно пустоту.
Интерфейсы — это всё-таки немного не то. Если я не ошибаюсь, в D, как и в C++ и в Java, интерфейсы подразумевают динамическую диспетчеризацию.Нет, не подразумевают. И речь не только про интерфейсы классов.
На мой взгляд, понятнее некуда.Ну то есть, приходится отказывать себе в удобстве, чтобы был понятный вывод.
дна из целей Rust — это гарантия полной безопасности работы с памятью; это включает в себя полный запрет на работу с неинициализированной памятью вне unsafe.Ну а в unsafe вы получаете все эти проблемы, что и требовалось доказать :-)
Редактировать и рефакторить такой код ничуть не сложнее, чем код без вывода типов, даже в чём-то проще — меньше печатать нужно.Я всеми руками за выведение типов и каждый день его использую. Речь исключительно о: «в Rust вывод типов действует внутри функции целиком, а не только при присваивании значения».
Пример «действительной полезности» привести сложно — это всё-таки не фича уровня borrow checker'а, без которой невозможно жить, а элементарное удобство.Так покажите это удобство.
Нет, не подразумевают
отказывать себе в удобстве
Ну а в unsafe вы получаете все эти проблемы, что и требовалось доказать :-)
mem::uninitialized():let x: i32 = unsafe { mem::uninitialized() };#![forbid(unsafe)].Так покажите это удобство.
Речь не о выводе типов, которая есть и в D, а изменение типа в зависимости от того, какая функция была вызвана первой.
Редактирование такого кода, как хождение по минному полю. Ну или покажите пример, где оно действительно полезно.
let a = 10;
let a = a.to_string();
let a = Some(a);a неизменяемая, просто тут три разных а. Когда впервые увидел, то показалось, что это ужасное решение и источник непонятных ошибок. А на деле оказывается удобно.и правда ужасное решение ибо вносит неразбериху. Не стоит оно того сомнительного удобства.
А чтобы его получить нужно разделить имена. Вручную.
let foo = foo.unwrap();.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
Тогда не очень понятно, что они сэкономили сделав return опциональным.
А как всё же реализовать несколько точек выхода?
В D на самом деле аналогично.
module main; нет.В D просто нет такого понятия как «неинициированная переменная», так как с ними много проблем.
int a = void;
writeln(a); // ???Вот, кстати, что это, чтоcast(long) a— одинаково не удобны в выражениях, в отличие отa.to!long
a as i64
Вот, кстати, что это, чтоcast(long) a— одинаково не удобны в выражениях, в отличие отa.to!long
struct Man {
basics: &'static str,
}
macro_rules! man {
( $x:expr => $y:expr ) => {
Man{basics: "https://habrahabr.ru/post/280642"}
}
}
fn main() {
println!("{}", man!(D => Rust).basics);
}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);
}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 и Go просочились сюда, например в Go byte это алиас для uint8, а rune для int32.
@noreturn атрибут. В Rust такие функции имеются и "результат" можно присвоить любому типу. Применяется, например, так: let a: String = match 10 {
10 => "normal value".to_owned(),
_ => panic!(),
};Наоборот, аналогом ptrdiff_t в го является int.
isize/usize длиннее типов с фиксированным размером, да и название говорящее.Но сам int имеет более широкую область применения, чем смещение указателей.
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 более практичный подход.
но затраты времени на написание простых вещей очень уж непрактичны
man!(D => Rust).basics