На днях вышла 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-18 01:27 am (UTC)А не так, что вышел 2011 стандарт, и опа, весь код в мире резко сломался.
Можно было сделать так, что если у тебя параметр функции размечен как rvalue-reference, то включается move по-умолчанию, а не после явного вызова std::move. И ничего бы не сломалось.
Или, альтернативно, можно было бы сделать у классов некоторый модификтор, который говорит, что этот класс по-умолчанию мувается (как в Rust), а не копируется.
В C++ выбрали самый худший вариант, потому что неудобный.
Тот вариант с шорткатом для std::move, который ты предложил, приемлимый вариант, зря его не сделали.
Так что принудительно сужать область видимости - это платить ненужные деньги.
Нет. Если ты строку смувал, у тебя в классе string останется нулевой указатель, а не буфер. Ничего лишнего ты не платишь. Точнее даже, чуть-чуть платишь в C++, вызывая деструктор на объект, про который ты (но не компилятор) знаешь, что он пустой.
А если ты владение буфером никому передавать не собираешься, то и не делай мув, а передавай указатель на строку.
Да, и ещё момент. В С++, глядя на код, почти всегда видно, где заканчивается время жизни переменной. (Исключение - ссылки на временные объекты).
А в расте, я так понимаю, - фиг. Надо дополнительно лезть читать сигнатуру каждой функции, куда передаётся эта переменная.
Нет. Если ты передаёшь указатель, то перед переменной надо явно амперсанд поставить. В Rust, в отличие от C++, нет неявного преобразования значения к указателю (который ссылка). Поэтому глядя на код сразу понятно, сколько живёт переменная.
Это для сложных типов.
А для Copy (это, примерно то, что в C++ называется POD) время жизни — до закрывающей фигурной скобки.
Надо, конечно, знать, тип переменной у тебя Copy или не Copy. Но это не представляется мне значимой проблемой.
no subject
Date: 2015-10-01 09:35 pm (UTC)Обратная совместимость бы сломалась. Хоть указатель на указатель в одном выражении взять и не выйдет, поскольку &x это уже rvalue, перегружать operator & никто не запрещал.
no subject
Date: 2015-10-01 09:49 pm (UTC)no subject
Date: 2015-10-04 04:26 pm (UTC)http://ideone.com/kxoZaa
no subject
Date: 2015-10-04 07:04 pm (UTC)