Эм, языки более высокого уровня и JavaScript в браузере? GUI-программы под винду пишутся в основном на .NET-платформе, под линукс и кроссплатформенные — да, здесь пока альтернативы Qt и GTK нет. Под iOS и OS X пишут на Objective C, но это, во-первых, не C++, во-вторых, vendor lock-in, а в третьих — недавно появился Swift. Но таких программ подавляющее становится всё меньше и меньше по сравнению с веб-приложениями.
Нет, вывод о связи между входной и выходной ссылкой компилятор делает только на основании сигнатуры функции, игнорируя её тело
Да, я, наверное. неправильно выразился. Правила lifetime elision действительно механические — фактически, синтаксический сахар, и они однозначно определяются только сигнатурой. Я имел в виду то, что компилятор проверит, что код внутри функции действительно вернёт ссылку с корректным лайфтаймом — на основании того, что возвращается ссылка на поле входной структуры.
Кажется, начинаю понимать — ссылка должна от чего-то зависеть, может от области, но если мы возвращаем ссылку, область функции уже закончилась по определению — значит остается зависеть только от какого-нибудь параметра.
Да, всё именно так.
Только как при этом должно выглядеть «чисто языковое» тело функции access(), чтобы компилятор понял, что &mut Vec зависит от &mut MutexGuard? Или компилятор привязывает к первому аргументу, не глядя? Или там на самом деле опять unsafe?
Ну в общем случае функция действительно может возвращать указатель на какое-то поле объекта. Вот, например:
Здесь компилятор видит доступ к полю и на основании этого делает вывод о связи между входной и выходной ссылкой.
В общем-то с MutexGuard ситуация аналогичная — он содержит поле типа &UnsafeCell — ссылку на UnsafeCell, а у UnsafeCell есть метод типа
unsafe fn get(&self) -> &mut T
который возвращает мутабельную ссылку на свои внутренности. Он unsafe потому что он «обходит» гарантию отсутствия алиасинга мутабельных ссылок. Собственно, метод получения мутабельной ссылки из MutexGuard вызывает get() на UnsafeCell непосредственно. Соблюдение же вышеописанных гарантий в контексте мьютекса обеспечивается реализацией этого мьютекса, в частности, тем, что получить одновременно больше одного MutexGuard'а невозможно.
Да, именно так. Уничтожение/клонирование изменяет счётчик ссылок, общий для всех ссылок на этот участок памяти — это происходит через сырой указатель. Поскольку в случае Rc это делается неатомарными операциями, Rc не может быть Send.
Да, если не связаны — это нужно указать явно. Просто после анализа кода выяснилось, что около 90% лайфтайм-параметров использовались примерно в таком контексте.
Учтите, правда, что в общем случае вы не сможете вернуть ссылку с лайфтаймом, не связанным с параметрами (ну или не статическим).
Нет, это не так. Смотрите, что было бы, если бы 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. Как по-вашему, как будет работать эта программа, если операции изменения счётчика неатомарные?
Сигнатура access приведена в самом начале соответствующего раздела. Вероятно, авторам действительно стоило написать её в развёрнутом виде. Здесь используется механизм удаления лайфтайм-параметров (lifetime elision) — когда компилятор сам, по довольно простым правилам, выводит то, как связаны лайфтаймы входных и выходных ссылок.
Одновременно — никак. В Rust в один и тот же момент времени может существовать только одна мутабельная ссылка (т.е. мутабельное заимствование) на один и тот же объект. За счёт механизмов синхронизации вы можете статически обеспечить, что никакие два потока не имеют мутабельную ссылку на один объект в одно и то же время. В вашем случае вам, вероятно, понадобится RWLock, похожий, например, на ReadWriteLock из джавы.
Но это, конечно, не lock-free-структуры. Насколько я в курсе, на данный момент в стандартной библиотеке локфри-структур данных нет, но их ничего принципиально не мешает сделать.
Однозначно будет. Если вам интересно, почитайте, пожалуйста, предыдущие статьи из того блога (они есть и в переводе на хабре) — там рассказывается про то, как Rust будет развиваться в дальнейшем и как именно будет обеспечиваться стабильность без стагнации.
Можно определять «родительские» (в данном случае 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
Обратите внимание на то, как применяется лайфтайм-параметр.
Не понимаю, как это работает, если в компилятор не знает о мьютексах ничего. Или знает?
Нет, компилятор не знает о мьютексах ничего. В этом как раз и главная идея.
Работает это за счёт общей системы заимствования. Ссылка на объект не может действовать дольше, чем живёт сам объект, и это гарантируется статически. В начале статьи недаром даются объяснения про ownership и borrowing — именно за счёт них вы не сможете получить ссылку на внутренность мьютекса, если он не захвачен.
Можно написать и 'b, и вообще произвольный идентификатор. Лайфтаймовые параметры очень похожи на типовые параметры, недаром они находятся рядом, в одних и тех же угловых скобках.
В принципе, в статье есть ссылки на другие статьи про концепции владения и заимствования, в частности, на официальную книгу по Rust, где про это всё неплохо разъяснено.
Грин-треды в той модели, которая применялась в Rust, были неудобны по многим причинам.
Напомню, раньше (примерно до начала-середины 2014) Rust предоставлял универсальное API для многопоточности и ввода-вывода, которое работало либо поверх нативных тредов и сишного API ввода-вывода, либо поверх грин-тредов в рантаймовом шедулере на основе libuv. При этом желаемый рантайм можно было выбрать при старте программы и даже комбинировать — часть потоков запускать «зелёными», часть — нативными.
Идея сама по себе очень интересная и мощная, но, как оказалось, её очень сложно реализовать правильно, и она накладывает очень серьёзные ограничения на развитие API ввода/вывода и многопоточности в целом. Из-за такой гибридности было сложно пользоваться наиболее продвинутыми возможностями с обоих концов «спектра», плюс необходимость динамической диспетчеризации системных вызовов накладывала отпечаток на производительность любой программы. Также те программы, которые так или иначе использовали рантайм (т.е. практически все), было проблематично использовать через C-интерфейс из других сред. В общем и целом, все проблемы гибридного рантайма и причины, по которым его удалили, описаны в соответствующем RFC.
В итоге поддержку гринтредов из стандартной библиотеки убрали, что оказалось очень правильным решением — программы на Rust стали гораздо более легковесными, с меньшим количеством магии. При этом никто в принципе не запрещает сделать стороннюю библиотеку для зелёных потоков или чего-то подобного. Не так давно, например, выложили библиотеку для поддержки сопроцедур, с явной передачей управления (не могу найти ссылку сейчас).
Помимо названных danslapman, есть ещё SolidOak (первая ссылка в гугле по запросу «rust ide», кстати). Эта среда написана на Rust, с использованием embedded NeoVim в качестве редактора.
Лично я пользуюсь обычным вимом, даже без специальных плагинов для Rust.
В Rust есть и «сырые» указатели, к которым статического анализа не применяется. Такие указатели можно разыменовывать только в unsafe-блоках, потому что это небезопасно в общем случае. Сырые указатели как раз используются при написании байндингов к другим библиотекам.
В блоге rust-lang уже появилась следующая статья (про FFI), скоро переведу и её.
Да, я, наверное. неправильно выразился. Правила lifetime elision действительно механические — фактически, синтаксический сахар, и они однозначно определяются только сигнатурой. Я имел в виду то, что компилятор проверит, что код внутри функции действительно вернёт ссылку с корректным лайфтаймом — на основании того, что возвращается ссылка на поле входной структуры.
Да, всё именно так.
Ну в общем случае функция действительно может возвращать указатель на какое-то поле объекта. Вот, например:
Здесь компилятор видит доступ к полю и на основании этого делает вывод о связи между входной и выходной ссылкой.
В общем-то с MutexGuard ситуация аналогичная — он содержит поле типа &UnsafeCell — ссылку на UnsafeCell, а у UnsafeCell есть метод типа
который возвращает мутабельную ссылку на свои внутренности. Он unsafe потому что он «обходит» гарантию отсутствия алиасинга мутабельных ссылок. Собственно, метод получения мутабельной ссылки из MutexGuard вызывает get() на UnsafeCell непосредственно. Соблюдение же вышеописанных гарантий в контексте мьютекса обеспечивается реализацией этого мьютекса, в частности, тем, что получить одновременно больше одного MutexGuard'а невозможно.
Учтите, правда, что в общем случае вы не сможете вернуть ссылку с лайфтаймом, не связанным с параметрами (ну или не статическим).
clone() на Rc увеличивает счётчик на 1, выход из области действия уменьшает на 1. Как по-вашему, как будет работать эта программа, если операции изменения счётчика неатомарные?
RWLock, похожий, например, на ReadWriteLock из джавы.Но это, конечно, не lock-free-структуры. Насколько я в курсе, на данный момент в стандартной библиотеке локфри-структур данных нет, но их ничего принципиально не мешает сделать.
Непосредственно этого сделать нельзя, компилятор сам выведет лайфтаймы для всех объектов, которые вы используете, на основе того, какие операции вы осуществляете с ними. Вот в данном примере:
guardимеет тип MutexGuard. access принимает мутабельную ссылку на MutexGuard и возвращает ссылку на охраняемый вектор (которая присваивается vec). При этом лайфтайм, ассоциированный со ссылкой, связывается с лайфтаймом MutexGuard, потому что access определена вот так:Обратите внимание на то, как применяется лайфтайм-параметр.
Нет, компилятор не знает о мьютексах ничего. В этом как раз и главная идея.
Работает это за счёт общей системы заимствования. Ссылка на объект не может действовать дольше, чем живёт сам объект, и это гарантируется статически. В начале статьи недаром даются объяснения про ownership и borrowing — именно за счёт них вы не сможете получить ссылку на внутренность мьютекса, если он не захвачен.
Можно написать и
'b, и вообще произвольный идентификатор. Лайфтаймовые параметры очень похожи на типовые параметры, недаром они находятся рядом, в одних и тех же угловых скобках.В принципе, в статье есть ссылки на другие статьи про концепции владения и заимствования, в частности, на официальную книгу по Rust, где про это всё неплохо разъяснено.
Напомню, раньше (примерно до начала-середины 2014) Rust предоставлял универсальное API для многопоточности и ввода-вывода, которое работало либо поверх нативных тредов и сишного API ввода-вывода, либо поверх грин-тредов в рантаймовом шедулере на основе libuv. При этом желаемый рантайм можно было выбрать при старте программы и даже комбинировать — часть потоков запускать «зелёными», часть — нативными.
Идея сама по себе очень интересная и мощная, но, как оказалось, её очень сложно реализовать правильно, и она накладывает очень серьёзные ограничения на развитие API ввода/вывода и многопоточности в целом. Из-за такой гибридности было сложно пользоваться наиболее продвинутыми возможностями с обоих концов «спектра», плюс необходимость динамической диспетчеризации системных вызовов накладывала отпечаток на производительность любой программы. Также те программы, которые так или иначе использовали рантайм (т.е. практически все), было проблематично использовать через C-интерфейс из других сред. В общем и целом, все проблемы гибридного рантайма и причины, по которым его удалили, описаны в соответствующем RFC.
В итоге поддержку гринтредов из стандартной библиотеки убрали, что оказалось очень правильным решением — программы на Rust стали гораздо более легковесными, с меньшим количеством магии. При этом никто в принципе не запрещает сделать стороннюю библиотеку для зелёных потоков или чего-то подобного. Не так давно, например, выложили библиотеку для поддержки сопроцедур, с явной передачей управления (не могу найти ссылку сейчас).
Лично я пользуюсь обычным вимом, даже без специальных плагинов для Rust.
Сейчас там уже есть следующая статья, про паттерн-матчинг в контексте владения данными, я её тоже скоро переведу.