Rust

Jan. 14th, 2015 03:18 pm
thedeemon: (office)
[personal profile] thedeemon
На днях вышла 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 считает, что тут два параллельных заимствования, одно из которых мутабельное, что недопустимо.

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

Date: 2015-01-14 08:21 am (UTC)
From: [identity profile] macrop.livejournal.com
ммм.. как вводу. сижу голову над с++ перещениями ломаю. Как раз rust вспоминал вечером))

Date: 2015-01-14 10:36 am (UTC)
From: [identity profile] kodt-rsdn.livejournal.com

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


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

Date: 2015-01-17 11:08 pm (UTC)
From: [identity profile] stepancheg.livejournal.com
15 лет придумывал перемещения, а сделал в итоге очень плохо. Я там в соседнем треде написал.

Date: 2015-01-14 08:24 am (UTC)
From: [identity profile] diam-2003.livejournal.com
Да-да, вот эти самые пляски с владением в Rust-е с теоретической стороны кажутся весьма привлекательными, особенно для программистов на С++, а на деле порой нетривиально замутняют код. К вопросу о том, почему не всё, что делает компилятор (например, escape-анализ) следует пихать в систему типов.

Date: 2015-01-14 10:36 am (UTC)
From: [identity profile] macrop.livejournal.com
У меня раньше пляска со строками была.
Были адовы горы строк с подсчётом ссылок. Но при этом, иногда их надо было просто передавать куда-то для вывода или обработки, не дёргая попусту всю эту мишуру с механизмом подсчёта. Поэтому была какая-то припарка, вспомогательный объект, через который в функцию передавался объект, без пересчёта ссылок. Очень это геморно было, и от ошибок не защищёно.

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

Date: 2015-01-14 10:41 am (UTC)
From: [identity profile] diam-2003.livejournal.com
Классический вопрос для программиста на С++:
сколько классов строк было в вашем самом большом проекте?

Date: 2015-01-14 11:32 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Без помощи человека escape-анализ работает слишком долго и плохо. Остается выбор между ручной аннотацией всякого (Rust, ATS), полаганием на сборщик мусора, либо полаганием на авось (C/C++).

Date: 2015-01-14 11:45 am (UTC)
From: [identity profile] diam-2003.livejournal.com
Это ваше утверждение про "долго и плохо" как-нибудь можно подтвердить :) ?

Нормальный быстрый escape-анализ + сборщик мусора + арены для особо исключительных случаев.
Контроллеры с весьма маленькой (4М, например) памятью.
Всё хорошо.

(no subject)

From: [identity profile] thedeemon.livejournal.com - Date: 2015-01-14 12:16 pm (UTC) - Expand

(no subject)

From: [identity profile] diam-2003.livejournal.com - Date: 2015-01-14 01:35 pm (UTC) - Expand

(no subject)

From: [personal profile] wizzard - Date: 2015-01-14 04:27 pm (UTC) - Expand

(no subject)

From: [identity profile] nivanych.livejournal.com - Date: 2015-01-14 04:37 pm (UTC) - Expand

(no subject)

From: [identity profile] geniepro.livejournal.com - Date: 2015-01-15 05:45 am (UTC) - Expand

(no subject)

From: [identity profile] diam-2003.livejournal.com - Date: 2015-01-15 07:58 am (UTC) - Expand

Date: 2015-01-14 09:18 am (UTC)
From: [identity profile] gds.livejournal.com
диагноз: невнятная хуита.

Date: 2015-01-14 10:37 am (UTC)
From: [identity profile] kodt-rsdn.livejournal.com

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

Date: 2015-01-14 11:19 am (UTC)
From: [identity profile] v-l-a-d.livejournal.com
по сравнению с чем усложнили, и в каком месте?

(no subject)

From: [identity profile] kodt-rsdn.livejournal.com - Date: 2015-01-14 11:39 am (UTC) - Expand

(no subject)

From: [identity profile] v-l-a-d.livejournal.com - Date: 2015-01-14 12:33 pm (UTC) - Expand

Date: 2015-01-14 11:28 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Они наоборот шли на поводу у парсера, чтобы был быстрый и без длинных look-ahead'ов работал, порой усложняя жизнь человекам. Так получились штуки вроде f::<T>(v).

(no subject)

From: [identity profile] kodt-rsdn.livejournal.com - Date: 2015-01-14 11:35 am (UTC) - Expand

(no subject)

From: [identity profile] thedeemon.livejournal.com - Date: 2015-01-14 12:09 pm (UTC) - Expand

(no subject)

From: [identity profile] kodt-rsdn.livejournal.com - Date: 2015-01-14 12:30 pm (UTC) - Expand

(no subject)

From: [identity profile] dmytrish.livejournal.com - Date: 2015-01-14 02:10 pm (UTC) - Expand

Date: 2015-01-14 12:32 pm (UTC)
From: [identity profile] Денис Жамойдик (from livejournal.com)
а как насчёт переписать ещё на ним? (http://nim-lang.org/)

Date: 2015-01-14 05:07 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Можете попробовать, будет интересно сравнить.
Nim - тоже довольно занятный экземпляр, делающий успехи сейчас. Но я пока для него не созрел, чем-то он мне противен.

Date: 2015-01-14 04:25 pm (UTC)
wizzard: (Default)
From: [personal profile] wizzard
Кажется, что тут нужен gradual typing. Типа, делаем какой-то best effort inference, а где компилятор не угадал - там ему хинтим, что, дескать, делать так-то и так-то.

Date: 2015-01-14 08:32 pm (UTC)
From: [identity profile] swizard.livejournal.com
Оказывается, когда мы структуру передали в ту функцию, мы ее отдали насовсем, это был move. А вот тупл скопировался, передача по значению, оригинал остается у вызывающей ф-ии. Неожиданно.

Ещё буквально пару недель назад это было не так: структуры тоже обладали Copy, если нет деструктора, и все её члены Copy =) Теперь нужно либо явно указывать #[derive(Copy)], либо #[allow(missing_copy_implementations)] (иначе компилятор нервничает).

Date: 2015-01-15 04:21 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Фигасе. Я так понимаю, типичный растовец утром встает и переписывает весь свой код, т.к. за ночь произошли фундаментальные изменения в языке. :)

Date: 2015-01-15 05:22 am (UTC)
From: [identity profile] fi_mihej.livejournal.com
Так и есть. Пару месяцев назад собеседовал чудака который решил писать на расте какой-то свой личный проект (не для того что-бы язык узнать, а именно какой-то нужный этому чуваку проект). Так он таки жаловался что по итогам - ему успели поднадоесть излишества растового синтаксиса, и что на периодическое переписывание кусков кода он потратил огромную кучу времени: он бы по-прикидкам бы уже успел два-три раза полностью тот проект закончить на другом языке (на момент собеседования - проект еще небыл закончен).

(no subject)

From: [identity profile] thedeemon.livejournal.com - Date: 2015-01-15 06:08 am (UTC) - Expand

(no subject)

From: [identity profile] swizard.livejournal.com - Date: 2015-01-15 08:50 am (UTC) - Expand

(no subject)

From: [identity profile] thedeemon.livejournal.com - Date: 2015-01-15 09:13 am (UTC) - Expand

(no subject)

From: [identity profile] alex-akts.livejournal.com - Date: 2015-01-17 10:48 pm (UTC) - Expand

Date: 2015-01-17 11:05 pm (UTC)
From: [identity profile] stepancheg.livejournal.com
Ты сейчас тут очень плохо сравнил Rust с C++:

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

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

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

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


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

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

Во-вторых, и это очень важно, в Rust после того, как делается move, старым объектом уже пользоваться нельзя. Компилятор запрещает. В C++ настоящий move отсутствует, а есть только каст к &&. Скастил ты объект к rvalue-reference, передал куда-то, а старый объект у тебя остался валидный. Может быть пустой, а может и какой был, если ты его не очистил.

Date: 2015-01-18 01:12 am (UTC)
From: [identity profile] kodt-rsdn.livejournal.com
В С++ очень консервативный подход.
Если в первом стандарте (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: [identity profile] stepancheg.livejournal.com - Date: 2015-01-18 01:27 am (UTC) - Expand

(no subject)

From: [identity profile] udpn.livejournal.com - Date: 2015-10-01 09:35 pm (UTC) - Expand

(no subject)

From: [identity profile] stepancheg.livejournal.com - Date: 2015-10-01 09:49 pm (UTC) - Expand

(no subject)

From: [identity profile] udpn.livejournal.com - Date: 2015-10-04 04:26 pm (UTC) - Expand

(no subject)

From: [identity profile] thedeemon.livejournal.com - Date: 2015-10-04 07:04 pm (UTC) - Expand

(no subject)

From: [identity profile] udpn.livejournal.com - Date: 2015-10-01 09:29 pm (UTC) - Expand

Date: 2015-01-18 09:16 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Ну я не говорю, что фичу в точности скопировали, но явно вдохновлялись. Хотя по типам это ближе к уникальным типам из Clean, терминологию заимствовали из плюсов.

Date: 2015-01-18 01:56 am (UTC)
From: [identity profile] stepancheg.livejournal.com
А еще и писать туда хотел? Тогда не забудь при передаче &mut дописать.

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

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

Date: 2015-01-18 09:19 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Я понимаю. Но когда я передаю указатель в функцию, из ее типа компилятор уже знает, что ссылка должна быть мутабельная, писать руками mut можно лишь для пущей вящести, самому компилятору это не нужно.

(no subject)

From: [identity profile] stepancheg.livejournal.com - Date: 2015-01-18 02:53 pm (UTC) - Expand

Date: 2015-01-18 02:04 am (UTC)
From: [identity profile] stepancheg.livejournal.com
Казалось бы, его, как в 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 даже не начинали. Компилятор инкрементальный только в планах. Корутины похоронили, а новые не предложили. И ещё много чего.

Date: 2015-01-18 09:22 am (UTC)
From: [identity profile] thedeemon.livejournal.com
> и это foo(a) должно вычислиться до вычисления второго аргумента

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

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

Я лишь рефлексирую над тем, чем столкнулся, когда писал эти 50 строчек кода. До более продвинутых вещей еще сам на опыте не добрался, а ругать за теорию не хочу.

(no subject)

From: [identity profile] stepancheg.livejournal.com - Date: 2015-01-18 02:51 pm (UTC) - Expand

(no subject)

From: [identity profile] thedeemon.livejournal.com - Date: 2015-01-18 03:48 pm (UTC) - Expand

(no subject)

From: [identity profile] stepancheg.livejournal.com - Date: 2015-01-18 03:57 pm (UTC) - Expand

Profile

thedeemon: (Default)
Dmitry Popov

December 2025

S M T W T F S
 12 3456
789101112 13
14151617181920
21222324252627
28293031   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 27th, 2026 08:52 pm
Powered by Dreamwidth Studios