Обновить
Комментарии 55
Как всегда, предложения и замечания по переводу прошу слать в личку.
Неплохо, подобные статьи весьма полезны для обучения и популяризации. Спасибо.
Штуки типа 'a и 'static это «именованные области жизни» (lifetimes) — концепция, насколько я понимаю, уникальная и существующая только в Rust; по ним бы неплохо отдельную статью…
Вполне возможно, что авторы языка сделают статью и по ним тоже.

Сейчас там уже есть следующая статья, про паттерн-матчинг в контексте владения данными, я её тоже скоро переведу.
Это единственная тема из статьи которая освещена мутно. Про 'а вообще ничего не сказано. Почему именно а, а не б?

С удовольствием почитал бы еще, спасибо за перевод.
Почему именно а, а не б?


Можно написать и 'b, и вообще произвольный идентификатор. Лайфтаймовые параметры очень похожи на типовые параметры, недаром они находятся рядом, в одних и тех же угловых скобках.

В принципе, в статье есть ссылки на другие статьи про концепции владения и заимствования, в частности, на официальную книгу по Rust, где про это всё неплохо разъяснено.
Как я понимаю, lifetime не зависит от того, что напишешь, будь это 'a или 'b, главное тут это краткость в записи. Для строкового слайса используют например 's… Тут важно, что если их несколько lifetime, то если укажешь 'a и 'a, то возьмется минимальный из них, а если 'a и 'b то они будут считается независимыми между друг другом. Есть ещё один lifetime: 'static — он указывает что время жизни равно жизни программы.
То есть тут пошли дальше в идеи ссылок и довели до ума продление их времени жизни? А что будет в случае, если нужно написать биндинг к сишной или плюсовой либе?
В Rust есть и «сырые» указатели, к которым статического анализа не применяется. Такие указатели можно разыменовывать только в unsafe-блоках, потому что это небезопасно в общем случае. Сырые указатели как раз используются при написании байндингов к другим библиотекам.
Очень элегантный подход. В своем роде — прорыв.
Жаль только, что из ядра убрали работу с зелеными тредами. Если я правильно понял, с ними была какая-то проблема с мультиплатформенностью.
Грин-треды в той модели, которая применялась в Rust, были неудобны по многим причинам.

Напомню, раньше (примерно до начала-середины 2014) Rust предоставлял универсальное API для многопоточности и ввода-вывода, которое работало либо поверх нативных тредов и сишного API ввода-вывода, либо поверх грин-тредов в рантаймовом шедулере на основе libuv. При этом желаемый рантайм можно было выбрать при старте программы и даже комбинировать — часть потоков запускать «зелёными», часть — нативными.

Идея сама по себе очень интересная и мощная, но, как оказалось, её очень сложно реализовать правильно, и она накладывает очень серьёзные ограничения на развитие API ввода/вывода и многопоточности в целом. Из-за такой гибридности было сложно пользоваться наиболее продвинутыми возможностями с обоих концов «спектра», плюс необходимость динамической диспетчеризации системных вызовов накладывала отпечаток на производительность любой программы. Также те программы, которые так или иначе использовали рантайм (т.е. практически все), было проблематично использовать через C-интерфейс из других сред. В общем и целом, все проблемы гибридного рантайма и причины, по которым его удалили, описаны в соответствующем RFC.

В итоге поддержку гринтредов из стандартной библиотеки убрали, что оказалось очень правильным решением — программы на Rust стали гораздо более легковесными, с меньшим количеством магии. При этом никто в принципе не запрещает сделать стороннюю библиотеку для зелёных потоков или чего-то подобного. Не так давно, например, выложили библиотеку для поддержки сопроцедур, с явной передачей управления (не могу найти ссылку сейчас).
Понятно, спасибо. Как мне кажется, идея зеленых тредов в ядре — очень вкусная фича для веба. Вырезав ее и отдав «на сторону», мэйнтенеры несколько охладили пыл готовых пересесть с go на rust веб-девелоперов.
Это кто это из веб разработчиков был готов перейти на язык, который позиционирует себя как заменитель си?
Ну библиотеки под это дело активно пилят: arewewebyet.com (информация на сайте уже несколько устарела).
Бэкэнд для сrates.io, например, написан на Rust.
То, что язык позиционирует себя именно как заменитель си, подразумевает, что он должен быть всё-таки удобнее, чем си.
> То, что язык позиционирует себя именно как заменитель си, подразумевает, что он должен быть всё-таки удобнее, чем си.

Это также подразумевает, что область применения у него та же, что и у си и он удобнее, чем си именно в этой области. У языков, на которых пишут веб-программисты нет проблем с контролем памяти. Там либо есть gc, либо каждая программа, как в php, работает несколько секунд, а потом умирает и отдаёт всю память обратно.
Вообще у меня создалось впечатление, что единственная черта Rust, которая могла привлечь веб разработчиков — это похожий на javascript синтаксис.
На javascript синтаксис там вообще не похож :)
Я бы сказал что в Rust синтаксис немного переусложнен, но это связано с большим количеством фич, управлением памятью и т.д.
Имхо они изменили позиционирование языка, и правильно сделали. Грин-тредовые штуки и акторы по моим ощущениям дружат с медленными, gc-рантаймами. В том смысле что:
— акторы, грин-треды — это по-любому оверхед
— зато, типа, очень простой поддерживаемый код и архитектура
— в принципе любое акторное или грин-тредовое приложение можно задизайнить по-другому, на обычных потоках, что будет системным (Раст-) путем
Интересная мысль, а можно какой-нибудь пример такого дизайна?
Ну все ведь тьюринг-полное… я просто исхожу из того, что раньше люди без грин-тредов, акторов (и функциональщины) как-то ведь жили. Видимо, Раст надо брать, либо когда вам это не нужно в принципе, либо когда вы готовы «жить», ради скорости, которой не будет ни с какими акторами. А если хотите акторы — берите медленную Скалу, или Эрланг, или что там еще.
А что у rust-а с ide? Есть какие-то наработки уже у кого-нибудь?
Есть две IDE под Eclipse — Rust-IDE и RustyCage. Так-же есть сносная интеграция Racer'a в SublimeText
Сам пишу в Sublime и RustyCage
Помимо названных danslapman, есть ещё SolidOak (первая ссылка в гугле по запросу «rust ide», кстати). Эта среда написана на Rust, с использованием embedded NeoVim в качестве редактора.

Лично я пользуюсь обычным вимом, даже без специальных плагинов для Rust.
Есть плагин для jetbrains idea, правда пока в зачаточном состоянии.
мутабельная ссылка, которая возвращается функцией access, не может действовать дольше, чем MutexGuard, из которого она получена;
error: `guard` does not live long enough
Не понимаю, как это работает, если в компилятор не знает о мьютексах ничего. Или знает?

Если все компоненты типа являются Send, то и он сам является Send, что покрывает большинство типов.
Что-то тут не то, из нескольких по отдельности атомарных счетчиков можно элементарно сконструировать потоконебезопасный тип.

1) Тут как раз и работает lifetime. Он у них одинаковый и поэтому данные внутри не могут жить дольше чем MutexGuard
2) Send это немного другое. Потокобезопасный это trait Sync, а Send просто позволяет отправлять объект в другой поток
Это все не объяснено в статье (в лучшем случае; а скорее статья вводит в заблуждение) и из вашего краткого комментария тоже не понятно. Объясните обстоятельнее или дайте ссылку на спеку.

1) Что значит одинаковый лайфтайм? Их можно особым образом связывать, для разных данных? Можно определять «родительские» (в данном случае guard) и «дочерние» (vec), чтобы обусловить понятие «что-то живет не дольше чего-то»?

2) Тогда что не есть Send? Почему Rc это не Send, если это по сути «newtype» (кстати, как newtype из Хаскеля называются в Расте?) для какого-нибудь int32?
С чего вы взяли, что Rc — это newtype? Это вполне себе отдельный тип, оборачивающий указатель.
Ок, не важно.

Ограничение Send на T означает, что T можно безопасно пересылать между потоками.
Send это немного другое. Потокобезопасный это trait Sync, а Send просто позволяет отправлять объект в другой поток
Естественно, Arc является Send, а Rc — нет.

Для меня это ни сколько не естественно, судя по тому что есть еще и Sync, по моему ощущению Send должно быть вообще все (что-то я не могу придумать не-Send).
Нет, это не так. Смотрите, что было бы, если бы Rc был бы Send:
let r0: Rc<i32> = Rc::new(1);
let r1 = r0.clone();  // клонируем указатель, счётчик ссылок увеличивается
thread::spawn(move || {
    let r2 = r1.clone();
    let r3 = r2.clone();
    let r4 = r1.clone();
});
let r2 = r0.clone();
let r3 = r0.clone();

clone() на Rc увеличивает счётчик на 1, выход из области действия уменьшает на 1. Как по-вашему, как будет работать эта программа, если операции изменения счётчика неатомарные?
Я так понимаю, это потому, что Rc внутри фигачит unsafe-код и clone() и «уничтожение» (выход из области видимости) делает что-то далеко не стандартное? И для этого Rc явно помечен как !Send, хотя его поля по отдельности — Send?
Да, именно так. Уничтожение/клонирование изменяет счётчик ссылок, общий для всех ссылок на этот участок памяти — это происходит через сырой указатель. Поскольку в случае Rc это делается неатомарными операциями, Rc не может быть Send.
Можно определять «родительские» (в данном случае guard) и «дочерние» (vec), чтобы обусловить понятие «что-то живет не дольше чего-то»?

Непосредственно этого сделать нельзя, компилятор сам выведет лайфтаймы для всех объектов, которые вы используете, на основе того, какие операции вы осуществляете с ними. Вот в данном примере:
    let mut guard = lock(mutex);
    let vec = access(&mut guard);


guard имеет тип MutexGuard. access принимает мутабельную ссылку на MutexGuard и возвращает ссылку на охраняемый вектор (которая присваивается vec). При этом лайфтайм, ассоциированный со ссылкой, связывается с лайфтаймом MutexGuard, потому что access определена вот так:
    fn access<'a, T>(guard: &'a mut MutexGuard<T>) -> &'a mut T

Обратите внимание на то, как применяется лайфтайм-параметр.
Ну вот теперь примерно ясно, а в статье просто постулируется, что &mut Vec не переживет MutexGuard, почему — не объяснено.
Сигнатура access приведена в самом начале соответствующего раздела. Вероятно, авторам действительно стоило написать её в развёрнутом виде. Здесь используется механизм удаления лайфтайм-параметров (lifetime elision) — когда компилятор сам, по довольно простым правилам, выводит то, как связаны лайфтаймы входных и выходных ссылок.
А почему лайфтайм результата функции должен быть связан с лайфтаймом параметра? Мне кажется, это не обязательно. Или, если не связаны, надо явно указать это как-то в сигнатуре?
Да, если не связаны — это нужно указать явно. Просто после анализа кода выяснилось, что около 90% лайфтайм-параметров использовались примерно в таком контексте.

Учтите, правда, что в общем случае вы не сможете вернуть ссылку с лайфтаймом, не связанным с параметрами (ну или не статическим).
(ну или не статическим).

Не понял.

Какой-нибудь
fn vecOfSize(&int size) {
    return Vec::new(size);
}

(Извините за неровный почерк)

С чего бы лайфтайму возвращенного вектора зависеть от параметра?
Здесь вы возвращаете не ссылку, а значение. Я говорил про ссылки. Вот такое, например, вы сделать не сможете:

fn wrong() -> &i32 {
    let x = 10;
    &x
}
Кажется, начинаю понимать — ссылка должна от чего-то зависеть, может от области, но если мы возвращаем ссылку, область функции уже закончилась по определению — значит остается зависеть только от какого-нибудь параметра.

Только как при этом должно выглядеть «чисто языковое» тело функции access(), чтобы компилятор понял, что &mut Vec зависит от &mut MutexGuard? Или компилятор привязывает к первому аргументу, не глядя? Или там на самом деле опять unsafe?
Кажется, начинаю понимать — ссылка должна от чего-то зависеть, может от области, но если мы возвращаем ссылку, область функции уже закончилась по определению — значит остается зависеть только от какого-нибудь параметра.

Да, всё именно так.

Только как при этом должно выглядеть «чисто языковое» тело функции access(), чтобы компилятор понял, что &mut Vec зависит от &mut MutexGuard? Или компилятор привязывает к первому аргументу, не глядя? Или там на самом деле опять unsafe?

Ну в общем случае функция действительно может возвращать указатель на какое-то поле объекта. Вот, например:
struct X {
    x: i32
}

fn access(x: &mut X) -> &mut i32 {
    &mut x.x
}


Здесь компилятор видит доступ к полю и на основании этого делает вывод о связи между входной и выходной ссылкой.

В общем-то с MutexGuard ситуация аналогичная — он содержит поле типа &UnsafeCell — ссылку на UnsafeCell, а у UnsafeCell есть метод типа
unsafe fn get(&self) -> &mut T

который возвращает мутабельную ссылку на свои внутренности. Он unsafe потому что он «обходит» гарантию отсутствия алиасинга мутабельных ссылок. Собственно, метод получения мутабельной ссылки из MutexGuard вызывает get() на UnsafeCell непосредственно. Соблюдение же вышеописанных гарантий в контексте мьютекса обеспечивается реализацией этого мьютекса, в частности, тем, что получить одновременно больше одного MutexGuard'а невозможно.
Здесь компилятор видит доступ к полю и на основании этого делает вывод о связи между входной и выходной ссылкой.
Нет, вывод о связи между входной и выходной ссылкой компилятор делает только на основании сигнатуры функции, игнорируя её тело. Если у функции всего один параметр ссылка, то компилятор предполагает, что у всех возвращаемых ссылок такой же лайфтайм. Если вы к своей функции access добавите второй параметр, то код уже не скомпилируется:

src\main.rs:5:36: 5:40 error: missing lifetime specifier [E0106]
src\main.rs:5 fn access(x: &mut X, y: &mut X) -> &mut i32 {
                                                 ^~~~
src\main.rs:5:36: 5:40 help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`

В этом случае придётся вручную указывать лайфтаймы.
Нет, вывод о связи между входной и выходной ссылкой компилятор делает только на основании сигнатуры функции, игнорируя её тело

Да, я, наверное. неправильно выразился. Правила lifetime elision действительно механические — фактически, синтаксический сахар, и они однозначно определяются только сигнатурой. Я имел в виду то, что компилятор проверит, что код внутри функции действительно вернёт ссылку с корректным лайфтаймом — на основании того, что возвращается ссылка на поле входной структуры.
Не понимаю, как это работает, если в компилятор не знает о мьютексах ничего. Или знает?

Нет, компилятор не знает о мьютексах ничего. В этом как раз и главная идея.

Работает это за счёт общей системы заимствования. Ссылка на объект не может действовать дольше, чем живёт сам объект, и это гарантируется статически. В начале статьи недаром даются объяснения про ownership и borrowing — именно за счёт них вы не сможете получить ссылку на внутренность мьютекса, если он не захвачен.
Кажется, я понял (точнее придумал сам как это могло бы быть) — MutexGuard хранит &Vec, то есть при его уничтожении должен уничтожиться &mut Vec, который из него вышел. Только из текста статьи это вовсе не очевидно
MutexGuard ничего не хранит (вернее, хранит, но точно не &mut T и не &T) — всё дело в том, какими операциями вы с ним работаете.
Да, я понял, когда увидел настоящую сигнатуру access()
Непонятно про lock-free. Вот нужно мне по рингбуферу бегать несколькими потоками (читатели и писатели), как передавать им всем мутабельные заимствования на этот буфер?
Одновременно — никак. В Rust в один и тот же момент времени может существовать только одна мутабельная ссылка (т.е. мутабельное заимствование) на один и тот же объект. За счёт механизмов синхронизации вы можете статически обеспечить, что никакие два потока не имеют мутабельную ссылку на один объект в одно и то же время. В вашем случае вам, вероятно, понадобится RWLock, похожий, например, на ReadWriteLock из джавы.

Но это, конечно, не lock-free-структуры. Насколько я в курсе, на данный момент в стандартной библиотеке локфри-структур данных нет, но их ничего принципиально не мешает сделать.
Будет Раст 2.0 или они начнут есть Java-кактус?
Однозначно будет. Если вам интересно, почитайте, пожалуйста, предыдущие статьи из того блога (они есть и в переводе на хабре) — там рассказывается про то, как Rust будет развиваться в дальнейшем и как именно будет обеспечиваться стабильность без стагнации.
После 1.0 будут версии 1.x, которые будут обратно совместимы с 1.0, там ещё много всего запланировано.
2.0 — это что-то более отдалённое и туманное.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.