На днях вышла 1.0.0.alpha, решил наконец приобщиться. Почитал онлайн книжку. Если она не слишком много скрывает, язык довольно маленький и простой, это хорошо. Для начала сделал вариант для недавнего микробенчмарка про маленький интерпретатор. В тот раз добрые люди помогли ускорить наивные решения, так что все времена опустились ниже 1 секунды, что делает замеры менее осмысленными. Тем не менее, вот текущие результаты:
D - 0.40 s (при использовании LDC)
Rust - 0.44 s
OCaml - 0.57 s
Haskell - 0.85 s
(с одной закавыкой - Rust тут 64-битный, все остальные 32-битные, так уж получилось)
Т.к. опыт с Rust'ом у меня пока минимальный, впечатления смутные. Одной фразой - "ML в руках плюсовиков". Видишь знакомый набор из алгебраиков, паттерн-матчинга, лямбд, expression-based syntax, начинаешь писать как на ML, и тут на тебя выпрыгивает наследие С++: а ты здесь это значение насовсем передал (у нас move-семантика по-умолчанию, уж больно нам эта фича из С++ понравилась) или хотел лишь по указателю? Ах по указателю, тогда так и напиши везде, и где принимаешь, и где передаешь. А еще и писать туда хотел? Тогда не забудь при передаче &mut дописать. И это же выскакивает при паттерн-матчинге: вот тут ты поле алгебраика заматчил, тебе его так отдать или по ссылке? А обращаться хорошо с ней будешь?
Причем как-то странно сделано, вот есть структуры и есть туплы, разница между ними довольно косметическая, так? Можем пару значений передать как тупл, а можем как структуру. Сделаем пару одинаковых функций, складывающих два поля:
Обе получают аргумент по значению.
Теперь попробуем их повызывать:
И получаем ошибку:
Оказывается, когда мы структуру передали в ту функцию, мы ее отдали насовсем, это был move. А вот тупл скопировался, передача по значению, оригинал остается у вызывающей ф-ии. Неожиданно.
Еще занятный момент. В растовском варианте, что по ссылке выше, есть такое выражение:
1 + (if a[i] > a[j] { evalBlock(a, b1) } else { evalBlock(a, b2) })
Казалось бы, его, как в ML вариантах, можно заменить более простым:
1 + evalBlock(a, if a[i] > a[j] { b1 } else { b2 })
Но не тут-то было. Rust считает, что в первом аргументе evalBlock происходит мутабельное заимствование массива а, а при вычислении второго аргумента имеет место иммутабельное заимствование этого же массива. И хотя аргументы должны быть вычислены до вызова функции, и по времени эти два использования массива не пересекаются никак, Rust считает, что тут два параллельных заимствования, одно из которых мутабельное, что недопустимо.
Буду продолжать наблюдения. В целом штука занятная.
D - 0.40 s (при использовании LDC)
Rust - 0.44 s
OCaml - 0.57 s
Haskell - 0.85 s
(с одной закавыкой - Rust тут 64-битный, все остальные 32-битные, так уж получилось)
Т.к. опыт с Rust'ом у меня пока минимальный, впечатления смутные. Одной фразой - "ML в руках плюсовиков". Видишь знакомый набор из алгебраиков, паттерн-матчинга, лямбд, expression-based syntax, начинаешь писать как на ML, и тут на тебя выпрыгивает наследие С++: а ты здесь это значение насовсем передал (у нас move-семантика по-умолчанию, уж больно нам эта фича из С++ понравилась) или хотел лишь по указателю? Ах по указателю, тогда так и напиши везде, и где принимаешь, и где передаешь. А еще и писать туда хотел? Тогда не забудь при передаче &mut дописать. И это же выскакивает при паттерн-матчинге: вот тут ты поле алгебраика заматчил, тебе его так отдать или по ссылке? А обращаться хорошо с ней будешь?
Причем как-то странно сделано, вот есть структуры и есть туплы, разница между ними довольно косметическая, так? Можем пару значений передать как тупл, а можем как структуру. Сделаем пару одинаковых функций, складывающих два поля:
#[derive(Show)]
struct S { x : i32, y : i32 }
fn eat_struct(s : S) -> i32 {
s.x + s.y
}
fn eat_tuple(t : (i32, i32)) -> i32 {
let (x,y) = t;
x + y
} Обе получают аргумент по значению.
Теперь попробуем их повызывать:
fn main() {
let s = S { x: 1, y : 2 };
let t = (1, 2);
let rs = eat_struct(s);
let rt = eat_tuple(t);
println!("{} {} {:?} {:?}", rs, rt, s, t);
}И получаем ошибку:
hi.rs:18:39: 18:40 error: use of moved value: `s`
hi.rs:18 println!("{} {} {:?} {:?}", rs, rt, s, t);
^Оказывается, когда мы структуру передали в ту функцию, мы ее отдали насовсем, это был move. А вот тупл скопировался, передача по значению, оригинал остается у вызывающей ф-ии. Неожиданно.
Еще занятный момент. В растовском варианте, что по ссылке выше, есть такое выражение:
1 + (if a[i] > a[j] { evalBlock(a, b1) } else { evalBlock(a, b2) })
Казалось бы, его, как в ML вариантах, можно заменить более простым:
1 + evalBlock(a, if a[i] > a[j] { b1 } else { b2 })
Но не тут-то было. Rust считает, что в первом аргументе evalBlock происходит мутабельное заимствование массива а, а при вычислении второго аргумента имеет место иммутабельное заимствование этого же массива. И хотя аргументы должны быть вычислены до вызова функции, и по времени эти два использования массива не пересекаются никак, Rust считает, что тут два параллельных заимствования, одно из которых мутабельное, что недопустимо.
Буду продолжать наблюдения. В целом штука занятная.
no subject
Date: 2015-01-14 08:21 am (UTC)no subject
Date: 2015-01-14 10:36 am (UTC)Стобы понять с++ные перемещения, надо знать предысторию. С их велосипедами народ трахался пятнадцать лет, пока не пришёл к пониманию ценности, цены, области применения и стандарта.
С передачей ссылок и значений, кстати, народ также натрахался вволю, но ещё в фортране.
no subject
Date: 2015-01-17 11:08 pm (UTC)no subject
Date: 2015-01-14 08:24 am (UTC)no subject
Date: 2015-01-14 10:36 am (UTC)Были адовы горы строк с подсчётом ссылок. Но при этом, иногда их надо было просто передавать куда-то для вывода или обработки, не дёргая попусту всю эту мишуру с механизмом подсчёта. Поэтому была какая-то припарка, вспомогательный объект, через который в функцию передавался объект, без пересчёта ссылок. Очень это геморно было, и от ошибок не защищёно.
Не понимаю как в си раньше до такого нужного механизма не додумались. В Rust, я так понимаю, он более развит, хотя не все проблемы решает.
no subject
Date: 2015-01-14 10:41 am (UTC)сколько классов строк было в вашем самом большом проекте?
no subject
Date: 2015-01-14 11:32 am (UTC)no subject
Date: 2015-01-14 11:45 am (UTC)Нормальный быстрый escape-анализ + сборщик мусора + арены для особо исключительных случаев.
Контроллеры с весьма маленькой (4М, например) памятью.
Всё хорошо.
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2015-01-14 09:18 am (UTC)no subject
Date: 2015-01-14 10:37 am (UTC)Йес ыт ыз!
За углоскобки отдельное фи. На ровном месте усложнили жизнь парсеру.
no subject
Date: 2015-01-14 11:19 am (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2015-01-14 11:28 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2015-01-14 12:32 pm (UTC)no subject
Date: 2015-01-14 05:07 pm (UTC)Nim - тоже довольно занятный экземпляр, делающий успехи сейчас. Но я пока для него не созрел, чем-то он мне противен.
no subject
Date: 2015-01-14 04:25 pm (UTC)no subject
Date: 2015-01-14 08:32 pm (UTC)Ещё буквально пару недель назад это было не так: структуры тоже обладали Copy, если нет деструктора, и все её члены Copy =) Теперь нужно либо явно указывать #[derive(Copy)], либо #[allow(missing_copy_implementations)] (иначе компилятор нервничает).
no subject
Date: 2015-01-15 04:21 am (UTC)no subject
Date: 2015-01-15 05:22 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2015-01-17 11:05 pm (UTC)у нас move-семантика по-умолчанию, уж больно нам эта фича из С++ понравилась
Это в Rust move-семантика. В C++ какая-то хрень непонятная.
В C++, во-первых, по-умолчанию генерируется конструктор копирования. И если ты объект передаёшь в функцию, то как ты сигнатуру функции, не объявляй, у тебя всё равно объект будет копироваться, если ты не скастишь объект к &&.
Типа:
тут всегда будет вызываться конструктор копирования, а не move.
Поэтому несчастные программисты на C++ вынуждены везде явно расставлять идиотский std::move.
Во-вторых, и это очень важно, в Rust после того, как делается move, старым объектом уже пользоваться нельзя. Компилятор запрещает. В C++ настоящий move отсутствует, а есть только каст к &&. Скастил ты объект к rvalue-reference, передал куда-то, а старый объект у тебя остался валидный. Может быть пустой, а может и какой был, если ты его не очистил.
no subject
Date: 2015-01-18 01:12 am (UTC)Если в первом стандарте (1998) была семантика значений, а следовательно, копирования, то и во всех остальных она останется при том же самом синтаксисе.
А не так, что вышел 2011 стандарт, и опа, весь код в мире резко сломался.
Далее. Семантика значений очень хорошо дружит со временем жизни. Каждая копия валидна до момента формального разрушения.
А семантика перемещения контрынтуитивна. (Как контрынтуитивно написание этого слова :)) )
{ // вход в блок Foo x(123), y(456); // создали bar(x); // после этой точки x полуживое?! buz(x); // сюда мы подсунем какой-то дефолтный мусор?! x = y; // после этой точки x нормальное, y полуживое? buz(y); // сюда снова подсунем мусор? } // выход из блока, окончательное разрушение объектов x, yЕдинственный тип, который из коробки обладал таким свойством, это был auto_ptr. И всем программистам кричали в ухо: "берегись автопоинтера".
То, что компилятор Rust умеет ограничивать область видимости до точки перемещения - это он, конечно, молодец!
С другой стороны, перемещение отличается тем, что деструктор/конструктор не вызывается на каждый чих. Полудохлый объект можно повторно использовать, - например, присваивать содержимое, экономя на развёртывании инфраструктуры этого объекта.
std::string x; for(int i.....) { std::string y (foo(i)); // каждый раз новый буфер x = foo(i); // буфер размещён один раз ..... }Так что принудительно сужать область видимости - это платить ненужные деньги.
Да, и ещё момент. В С++, глядя на код, почти всегда видно, где заканчивается время жизни переменной. (Исключение - ссылки на временные объекты).
А в расте, я так понимаю, - фиг. Надо дополнительно лезть читать сигнатуру каждой функции, куда передаётся эта переменная.
То ли дело move. Написал - и сам себе дал знать, что далее там мусор, как если бы присвоил этой переменной белиберду.
Единственно, что синтаксис громоздкий: std::move(var), можно было б какой-нибудь унарный оператор для этого припахать, - да тот же (&&var).
Но это уже консервативность языка.
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2015-01-18 09:16 am (UTC)no subject
Date: 2015-01-18 01:56 am (UTC)Это ты тролишь, или правда не понимаешь?
В Rust запрещено в любой строчке иметь больше одного mut-указателя. Потому что иначе возникает любимая проблема плюсовиков, когда в первой строчке ты сохраняешь указатель на элемент вектора, во второй строчке делаешь push_back в вектор, и получаешь проезд по памяти, если повезёт, то сразу, если не повезёт, то на продакшне через месяц. Поэтому все указатели по-умолчанию не-mut.
no subject
Date: 2015-01-18 09:19 am (UTC)(no subject)
From:no subject
Date: 2015-01-18 02:04 am (UTC)1 + evalBlock(a, if a[i] > a[j] { b1 } else { b2 })
Но не тут-то было. Rust считает, что в первом аргументе evalBlock происходит мутабельное заимствование массива а, а при вычислении второго аргумента имеет место иммутабельное заимствование этого же массива. И хотя аргументы должны быть вычислены до вызова функции, и по времени эти два использования массива не пересекаются никак, Rust считает, что тут два параллельных заимствования, одно из которых мутабельное, что недопустимо.
У тебя в первом аргументе может стоять foo(a), и это foo(a) должно вычислиться до вычисления второго аргумента. Просто a — это частный случай foo(a). Не делать же ради этого частного случая исключение из правила вычисления аргументов? Это только запутает и спецификацию, и тех, кто язык изучает.
В общем, неправильно ты Rust ругаешь. У него другие большие проблемы есть, но не там, где тебе кажется.
Например, метапрограммирование, или HKT какие-нибудь в Rust отсутствуют. IDE даже не начинали. Компилятор инкрементальный только в планах. Корутины похоронили, а новые не предложили. И ещё много чего.
no subject
Date: 2015-01-18 09:22 am (UTC)Вот именно, что до. Не одновременно. Порядок вычисления аргументов в расте определен или нет?
>В общем, неправильно ты Rust ругаешь.
Я лишь рефлексирую над тем, чем столкнулся, когда писал эти 50 строчек кода. До более продвинутых вещей еще сам на опыте не добрался, а ругать за теорию не хочу.
(no subject)
From:(no subject)
From:(no subject)
From: