?

Log in

No account? Create an account

Previous Entry | Next Entry

Rust

На днях вышла 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 дописать. И это же выскакивает при паттерн-матчинге: вот тут ты поле алгебраика заматчил, тебе его так отдать или по ссылке? А обращаться хорошо с ней будешь?
Причем как-то странно сделано, вот есть структуры и есть туплы, разница между ними довольно косметическая, так? Можем пару значений передать как тупл, а можем как структуру. Сделаем пару одинаковых функций, складывающих два поля:
#[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 считает, что тут два параллельных заимствования, одно из которых мутабельное, что недопустимо.

Буду продолжать наблюдения. В целом штука занятная.

Tags:

Comments

( 51 comments — Leave a comment )
macrop
Jan. 14th, 2015 05:21 am (UTC)
ммм.. как вводу. сижу голову над с++ перещениями ломаю. Как раз rust вспоминал вечером))
kodt_rsdn
Jan. 14th, 2015 07:36 am (UTC)

Стобы понять с++ные перемещения, надо знать предысторию. С их велосипедами народ трахался пятнадцать лет, пока не пришёл к пониманию ценности, цены, области применения и стандарта.


С передачей ссылок и значений, кстати, народ также натрахался вволю, но ещё в фортране.

(no subject) - stepancheg - Jan. 17th, 2015 08:08 pm (UTC) - Expand
diam_2003
Jan. 14th, 2015 05:24 am (UTC)
Да-да, вот эти самые пляски с владением в Rust-е с теоретической стороны кажутся весьма привлекательными, особенно для программистов на С++, а на деле порой нетривиально замутняют код. К вопросу о том, почему не всё, что делает компилятор (например, escape-анализ) следует пихать в систему типов.
macrop
Jan. 14th, 2015 07:36 am (UTC)
У меня раньше пляска со строками была.
Были адовы горы строк с подсчётом ссылок. Но при этом, иногда их надо было просто передавать куда-то для вывода или обработки, не дёргая попусту всю эту мишуру с механизмом подсчёта. Поэтому была какая-то припарка, вспомогательный объект, через который в функцию передавался объект, без пересчёта ссылок. Очень это геморно было, и от ошибок не защищёно.

Не понимаю как в си раньше до такого нужного механизма не додумались. В Rust, я так понимаю, он более развит, хотя не все проблемы решает.
(no subject) - diam_2003 - Jan. 14th, 2015 07:41 am (UTC) - Expand
(no subject) - thedeemon - Jan. 14th, 2015 08:32 am (UTC) - Expand
(no subject) - diam_2003 - Jan. 14th, 2015 08:45 am (UTC) - Expand
(no subject) - thedeemon - Jan. 14th, 2015 09:16 am (UTC) - Expand
(no subject) - diam_2003 - Jan. 14th, 2015 10:35 am (UTC) - Expand
(no subject) - wizzard0 - Jan. 14th, 2015 01:27 pm (UTC) - Expand
(no subject) - nivanych - Jan. 14th, 2015 01:37 pm (UTC) - Expand
(no subject) - geniepro - Jan. 15th, 2015 02:45 am (UTC) - Expand
(no subject) - diam_2003 - Jan. 15th, 2015 04:58 am (UTC) - Expand
gds
Jan. 14th, 2015 06:18 am (UTC)
диагноз: невнятная хуита.
kodt_rsdn
Jan. 14th, 2015 07:37 am (UTC)

Йес ыт ыз!
За углоскобки отдельное фи. На ровном месте усложнили жизнь парсеру.

(no subject) - v_l_a_d - Jan. 14th, 2015 08:19 am (UTC) - Expand
(no subject) - kodt_rsdn - Jan. 14th, 2015 08:39 am (UTC) - Expand
(no subject) - v_l_a_d - Jan. 14th, 2015 09:33 am (UTC) - Expand
(no subject) - thedeemon - Jan. 14th, 2015 08:28 am (UTC) - Expand
(no subject) - kodt_rsdn - Jan. 14th, 2015 08:35 am (UTC) - Expand
(no subject) - thedeemon - Jan. 14th, 2015 09:09 am (UTC) - Expand
(no subject) - kodt_rsdn - Jan. 14th, 2015 09:30 am (UTC) - Expand
(no subject) - dmytrish - Jan. 14th, 2015 11:10 am (UTC) - Expand
zxc_by
Jan. 14th, 2015 09:32 am (UTC)
а как насчёт переписать ещё на ним? (http://nim-lang.org/)
thedeemon
Jan. 14th, 2015 02:07 pm (UTC)
Можете попробовать, будет интересно сравнить.
Nim - тоже довольно занятный экземпляр, делающий успехи сейчас. Но я пока для него не созрел, чем-то он мне противен.
wizzard0
Jan. 14th, 2015 01:25 pm (UTC)
Кажется, что тут нужен gradual typing. Типа, делаем какой-то best effort inference, а где компилятор не угадал - там ему хинтим, что, дескать, делать так-то и так-то.
swizard
Jan. 14th, 2015 05:32 pm (UTC)
Оказывается, когда мы структуру передали в ту функцию, мы ее отдали насовсем, это был move. А вот тупл скопировался, передача по значению, оригинал остается у вызывающей ф-ии. Неожиданно.

Ещё буквально пару недель назад это было не так: структуры тоже обладали Copy, если нет деструктора, и все её члены Copy =) Теперь нужно либо явно указывать #[derive(Copy)], либо #[allow(missing_copy_implementations)] (иначе компилятор нервничает).
thedeemon
Jan. 15th, 2015 01:21 am (UTC)
Фигасе. Я так понимаю, типичный растовец утром встает и переписывает весь свой код, т.к. за ночь произошли фундаментальные изменения в языке. :)
(no subject) - fi_mihej - Jan. 15th, 2015 02:22 am (UTC) - Expand
(no subject) - thedeemon - Jan. 15th, 2015 03:08 am (UTC) - Expand
(no subject) - swizard - Jan. 15th, 2015 05:50 am (UTC) - Expand
(no subject) - thedeemon - Jan. 15th, 2015 06:13 am (UTC) - Expand
(no subject) - alex_akts - Jan. 17th, 2015 07:48 pm (UTC) - Expand
stepancheg
Jan. 17th, 2015 08:05 pm (UTC)
Ты сейчас тут очень плохо сравнил Rust с C++:

у нас move-семантика по-умолчанию, уж больно нам эта фича из С++ понравилась

Это в Rust move-семантика. В C++ какая-то хрень непонятная.

В C++, во-первых, по-умолчанию генерируется конструктор копирования. И если ты объект передаёшь в функцию, то как ты сигнатуру функции, не объявляй, у тебя всё равно объект будет копироваться, если ты не скастишь объект к &&.

Типа:
Foo foo = ...;
bar(foo);


тут всегда будет вызываться конструктор копирования, а не move.

Поэтому несчастные программисты на C++ вынуждены везде явно расставлять идиотский std::move.

Во-вторых, и это очень важно, в Rust после того, как делается move, старым объектом уже пользоваться нельзя. Компилятор запрещает. В C++ настоящий move отсутствует, а есть только каст к &&. Скастил ты объект к rvalue-reference, передал куда-то, а старый объект у тебя остался валидный. Может быть пустой, а может и какой был, если ты его не очистил.
kodt_rsdn
Jan. 17th, 2015 10:12 pm (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) - stepancheg - Jan. 17th, 2015 10:27 pm (UTC) - Expand
(no subject) - udpn - Oct. 1st, 2015 06:35 pm (UTC) - Expand
(no subject) - stepancheg - Oct. 1st, 2015 06:49 pm (UTC) - Expand
(no subject) - udpn - Oct. 4th, 2015 01:26 pm (UTC) - Expand
(no subject) - thedeemon - Oct. 4th, 2015 04:04 pm (UTC) - Expand
(no subject) - udpn - Oct. 1st, 2015 06:29 pm (UTC) - Expand
(no subject) - thedeemon - Jan. 18th, 2015 06:16 am (UTC) - Expand
stepancheg
Jan. 17th, 2015 10:56 pm (UTC)
А еще и писать туда хотел? Тогда не забудь при передаче &mut дописать.

Это ты тролишь, или правда не понимаешь?

В Rust запрещено в любой строчке иметь больше одного mut-указателя. Потому что иначе возникает любимая проблема плюсовиков, когда в первой строчке ты сохраняешь указатель на элемент вектора, во второй строчке делаешь push_back в вектор, и получаешь проезд по памяти, если повезёт, то сразу, если не повезёт, то на продакшне через месяц. Поэтому все указатели по-умолчанию не-mut.
thedeemon
Jan. 18th, 2015 06:19 am (UTC)
Я понимаю. Но когда я передаю указатель в функцию, из ее типа компилятор уже знает, что ссылка должна быть мутабельная, писать руками mut можно лишь для пущей вящести, самому компилятору это не нужно.
(no subject) - stepancheg - Jan. 18th, 2015 11:53 am (UTC) - Expand
stepancheg
Jan. 17th, 2015 11:04 pm (UTC)
Казалось бы, его, как в ML вариантах, можно заменить более простым:

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 даже не начинали. Компилятор инкрементальный только в планах. Корутины похоронили, а новые не предложили. И ещё много чего.
thedeemon
Jan. 18th, 2015 06:22 am (UTC)
> и это foo(a) должно вычислиться до вычисления второго аргумента

Вот именно, что до. Не одновременно. Порядок вычисления аргументов в расте определен или нет?

>В общем, неправильно ты Rust ругаешь.

Я лишь рефлексирую над тем, чем столкнулся, когда писал эти 50 строчек кода. До более продвинутых вещей еще сам на опыте не добрался, а ругать за теорию не хочу.
(no subject) - stepancheg - Jan. 18th, 2015 11:51 am (UTC) - Expand
(no subject) - thedeemon - Jan. 18th, 2015 12:48 pm (UTC) - Expand
(no subject) - stepancheg - Jan. 18th, 2015 12:57 pm (UTC) - Expand
( 51 comments — Leave a comment )