Пять лет Rust

Original author: The Rust Core Team
  • Translation

В этом бардаке, который сейчас происходит в мире, легко забыть, что прошло уже пять лет с выпуска 1.0 в 2015 году! Rust за эти пять лет сильно изменился, так что мы хотели бы вспомнить о работе всех участников сообщества, начиная с момента стабилизации языка.


Напомним, если кто забыл: Rust — это язык программирования общего назначения, который обладает средствами, позволяющими строить надёжное и эффективное программное обеспечение. Rust может быть использован в любой области: от ядра вашей операционной системы до вашего следующего web-приложения. Этот язык полностью построен участниками открытого многоликого сообщества, в основном волонтёрами, кто щедро делился своим временем и знаниями для того, чтобы помочь сделать Rust таким, какой он есть сейчас.


Основные изменения с версии 1.0


2015


1.2 — Параллельная кодогенерация: уменьшение времени компиляции всегда являлось главной темой в каждом выпуске Rust, и сейчас трудно представить, что когда-то был короткий период времени, когда Rust вообще не имел параллельной кодогенерации.


1.3 — The Rustonomicon: наш первый выпуск фантастической книги "The Rustonomicon", книги, которая исследует тёмную сторону языка Rust, Unsafe Rust и смежные темы. Она стала прекрасным источником сведений для любого, кто хочет изучить и понять самые трудные аспекты языка.


1.4 — Поддержка Windows MSVC уровня 1: продвижение платформы на уровень поддержки 1 принесло нативную поддержку 64-битной Windows с инструментарием Microsoft Visual C++ (MSVC). До 1.4 вам нужно было устанавливать MinGW (порт окружения GNU под Windows) чтобы использовать и компилировать ваши программы на Rust. Поддержка Windows стала одним из самых больших улучшений Rust за эти пять лет. Лишь недавно Microsoft анонсировала публичный пре-релиз официальной поддержки Rust для WinRT API! И наконец, сейчас стало значительно легче строить высококачественные и кроссплатформенные приложения, нежели это было когда-либо ранее.


1.5 — Cargo Install: добавлена поддержка возможности сборки исполняемых файлов с помощью компилятора Rust вместе с поддержкой предустановленных дополнений cargo породило целую экосистему приложений, утилит и инструментов для разработчиков, которые сообщество обожает и от которых зависит. Cargo теперь поддерживает довольно много команд, которые сначала были просто плагинами, сделанными участниками сообщества и выложенными на crates.io!


2016


1.6 — Libcore: libcore — это подмножество стандартной библиотеки, содержащее только API, которое не требует функций выделения памяти или функций уровня операционной системы. Стабилизация libcore позволила компилировать Rust без выделения памяти или зависимости от операционной системы, что стало одним из первых основных шагов к поддержке Rust для разработки встраиваемых систем.


1.10 — Динамические библиотеки с C ABI: крейт типа cdylib компилируется в динамическую библиотеку с C-интерфейсом, что позволяет встраивать проекты Rust в любую систему, которая поддерживает ABI языка C. Некоторые из самых больших историй успеха Rust среди пользователей — это возможность написать небольшую критическую часть системы на Rust и легко интегрировать в большую кодовую базу. И теперь это стало проще, чем когда-либо.


1.12 — Cargo Workspaces: позволяют организовать несколько проектов Rust и совместно использовать один и тот же lock-файл. Рабочие пространства были неоценимы при создании крупных многоуровневых проектов.


1.13 — Оператор Try: первым основным синтаксическим изменением был оператор ? или оператор "Try". Он позволяет легко распространять ошибку по стеку вызовов. Раньше вам приходилось использовать макрос try!, который требовал оборачивать всё выражение каждый раз, когда вы сталкивались с Result, теперь вместо этого можно легко связать методы с помощью ?.


try!(try!(expression).method()); // Было
expression?.method()?;           // Стало

1.14 — Rustup 1.0: Rustup — это менеджер инструментальных средств. Он позволяет беспрепятственно использовать любую версию Rust или любой его инструментарий. То, что начиналось как скромный скрипт, стало тем, что персонал по эксплуатации нежно называет "химера". Возможность обеспечить первоклассное управление версиями компилятора в Linux, macOS, Windows и десятке целевых платформ была мифом ещё пять лет назад.


2017


1.15 — Процедурные макросы вывода типажей: макросы вывода типажей позволяют создавать мощные, обширные и строго типизированные API без часто повторяющегося кода. Это была первая стабильная версия Rust с которой стало можно использовать макросы из библиотек serde или diesel.


1.17 — Rustbuild: одним из самых больших улучшений для наших контрибьюторов в разработке языка стал переход от начальной системы сборки на основе make на использование Cargo. Благодаря этому участникам и новичкам rust-lang/rust стало намного проще создавать и вносить свой вклад в проект.


1.20 — Ассоциированные константы: ранее константы могли быть связаны только с модулем. В 1.20 мы стабилизировали ассоциативные константы для структур, перечислений и, что важно, для типажей. Это упростило добавление богатых наборов предустановленных значений в типы вашего API, таких как общие IP-адреса или использующиеся числовые константы.


2018


1.24 — Инкрементальная компиляция: до версии 1.24, когда вы вносили изменения в библиотеку, компилятору приходилось перекомпилировать весь код. Теперь он стал намного умнее в плане кэширования, насколько это было возможно, и ему нужно только заново перегенерировать изменения.


1.26impl Trait: добавление impl Trait дало вам выразительные динамические API с преимуществами и производительностью
статической диспетчеризации.


1.28 — Глобальные аллокаторы: ранее вы были ограничены использованием аллокатора, предоставленного Rust. Теперь с помощью API глобального аллокатора можно выбрать аллокатор, который соответствует вашим потребностям. Это был важный шаг в создании библиотеки alloc, другого подмножества стандартной библиотеки, содержащей только те её части, для которых требуется аллокатор, например Vec или String. Теперь стало проще, чем когда-либо, переиспользовать части стандартной библиотеки на различных системах.


1.31 — 2018 редакция: выпуск 2018 редакции стал нашим самым большим выпуском языка после версии 1.0, добавив изменения синтаксиса и улучшения в код на Rust, написанного полностью обратно совместимым образом. Это позволяет библиотекам, созданным с разными редакциями, беспрепятственно работать вместе.


  • Нелексические времена жизни (NLL): огромное улучшение в анализаторе заимствований Rust, позволяющее ему принимать более проверяемый безопасный код.
  • Улучшения в системе модулей: большие улучшения UX в объявлении и использовании модулей.
  • Константные функции: константные функции позволяют запускать и вычислять код Rust во время компиляции.
  • Rustfmt 1.0: новый инструмент форматирования кода, созданный специально для Rust.
  • Clippy 1.0: анализатор Rust для поиска распространённых ошибок. Clippy значительно упрощает проверку того, что ваш код не только безопасен, но и корректен.
  • Rustfix: со всеми изменениями синтаксиса мы знали, что хотим предоставить инструментарий, позволяющий сделать переход максимально простым. Теперь, когда требуются изменения в синтаксисе Rust, можно просто выполнить команду cargo fix для решения проблем, связанной с изменениями синтаксиса.

2019


1.34 — Альтернативный реестр крейтов: поскольку Rust всё больше и больше используется в производстве, возникает большая потребность в возможности размещении и использовании проектов в приватных пространствах. Вместе с этим в Cargo всегда разрешены зависимости из git-репозиториев. С помощью альтернативных реестров ваша организация может легко создать и делиться своим собственным реестром крейтов, которые можно использовать в ваших проектах, как если бы они были на crates.io.


1.39 — Async/Await: стабилизация ключевых слов async/await для обработки Future была одной из главных вех в превращении асинхронного программирования в "объект первого класса" Rust. Уже через шесть месяцев после его выпуска, асинхронное программирование превратилось в разнообразную и производительную экосистему.


2020


1.42 — Шаблоны срезов: хотя это было и не самое большое изменение, добавление шаблона .. (остальное) был долгожданной функцией, которая значительно улучшает выразительность сопоставления образцов для срезов.


Диагностики и ошибки


Одна вещь, которую мы не упомянули, это то насколько улучшены сообщения об ошибках и диагностика в Rust с 1.0. Глядя на старые сообщения об ошибках кажется, что это теперь другой язык.


Мы выделили несколько примеров, которые лучше всего демонстрируют насколько мы улучшили сообщения об ошибках. Теперь они показывают пользователям, где они допустили ошибки, и помогают понять почему код не работает, а также подсказывают, как можно их исправить.


Первый пример (Типажи)

use std::io::Write;

fn trait_obj(w: &Write) {
    generic(w);
}

fn generic<W: Write>(_w: &W) {}

Сообщение об ошибке в 1.2.0
   Compiling error-messages v0.1.0 (file:///Users/usr/src/rust/error-messages)
src/lib.rs:6:5: 6:12 error: the trait `core::marker::Sized` is not implemented for the type `std::io::Write` [E0277]
src/lib.rs:6     generic(w);
                 ^~~~~~~
src/lib.rs:6:5: 6:12 note: `std::io::Write` does not have a constant size known at compile-time
src/lib.rs:6     generic(w);
                 ^~~~~~~
error: aborting due to previous error
Could not compile `error-messages`.

To learn more, run the command again with --verbose.

A terminal screenshot of the 1.2.0 error message.


Сообщение об ошибке в 1.43.0
   Compiling error-messages v0.1.0 (/Users/ep/src/rust/error-messages)
error[E0277]: the size for values of type `dyn std::io::Write` cannot be known at compilation time
 --> src/lib.rs:6:13
  |
6 |     generic(w);
  |             ^ doesn't have a size known at compile-time
...
9 | fn generic<W: Write>(_w: &W) {}
  |    ------- -       - help: consider relaxing the implicit `Sized` restriction: `+  ?Sized`
  |            |
  |            required by this bound in `generic`
  |
  = help: the trait `std::marker::Sized` is not implemented for `dyn std::io::Write`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `error-messages`.

To learn more, run the command again with --verbose.

A terminal screenshot of the 1.43.0 error message.


Второй пример (помощь)

fn main() {
    let s = "".to_owned();
    println!("{:?}", s.find("".to_owned()));
}

Сообщение об ошибке в 1.2.0
   Compiling error-messages v0.1.0 (file:///Users/ep/src/rust/error-messages)
src/lib.rs:3:24: 3:43 error: the trait `core::ops::FnMut<(char,)>` is not implemented for the type `collections::string::String` [E0277]
src/lib.rs:3     println!("{:?}", s.find("".to_owned()));
                                    ^~~~~~~~~~~~~~~~~~~
note: in expansion of format_args!
<std macros>:2:25: 2:56 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
src/lib.rs:3:5: 3:45 note: expansion site
src/lib.rs:3:24: 3:43 error: the trait `core::ops::FnOnce<(char,)>` is not implemented for the type `collections::string::String` [E0277]
src/lib.rs:3     println!("{:?}", s.find("".to_owned()));
                                    ^~~~~~~~~~~~~~~~~~~
note: in expansion of format_args!
<std macros>:2:25: 2:56 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
src/lib.rs:3:5: 3:45 note: expansion site
error: aborting due to 2 previous errors
Could not compile `error-messages`.

To learn more, run the command again with --verbose.

A terminal screenshot of the 1.2.0 error message.


Сообщение об ошибке в 1.43.0
   Compiling error-messages v0.1.0 (/Users/ep/src/rust/error-messages)
error[E0277]: expected a `std::ops::FnMut<(char,)>` closure, found `std::string::String`
 --> src/lib.rs:3:29
  |
3 |     println!("{:?}", s.find("".to_owned()));
  |                             ^^^^^^^^^^^^^
  |                             |
  |                             expected an implementor of trait `std::str::pattern::Pattern<'_>`
  |                             help: consider borrowing here: `&"".to_owned()`
  |
  = note: the trait bound `std::string::String: std::str::pattern::Pattern<'_>` is not satisfied
  = note: required because of the requirements on the impl of `std::str::pattern::Pattern<'_>` for `std::string::String`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `error-messages`.

To learn more, run the command again with --verbose.

A terminal screenshot of the 1.43.0 error message.


Третий пример (Проверка заимствований)

fn main() {
    let mut x = 7;
    let y = &mut x;

    println!("{} {}", x, y);
}

Сообщение об ошибке в 1.2.0
   Compiling error-messages v0.1.0 (file:///Users/ep/src/rust/error-messages)
src/lib.rs:5:23: 5:24 error: cannot borrow `x` as immutable because it is also borrowed as mutable
src/lib.rs:5     println!("{} {}", x, y);
                                   ^
note: in expansion of format_args!
<std macros>:2:25: 2:56 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
src/lib.rs:5:5: 5:29 note: expansion site
src/lib.rs:3:18: 3:19 note: previous borrow of `x` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x` until the borrow ends
src/lib.rs:3     let y = &mut x;
                              ^
src/lib.rs:6:2: 6:2 note: previous borrow ends here
src/lib.rs:1 fn main() {
src/lib.rs:2     let mut x = 7;
src/lib.rs:3     let y = &mut x;
src/lib.rs:4
src/lib.rs:5     println!("{} {}", x, y);
src/lib.rs:6 }
             ^
error: aborting due to previous error
Could not compile `error-messages`.

To learn more, run the command again with --verbose.

A terminal screenshot of the 1.2.0 error message.


Сообщение об ошибке в 1.43.0
   Compiling error-messages v0.1.0 (/Users/ep/src/rust/error-messages)
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
 --> src/lib.rs:5:23
  |
3 |     let y = &mut x;
  |             ------ mutable borrow occurs here
4 |
5 |     println!("{} {}", x, y);
  |                       ^  - mutable borrow later used here
  |                       |
  |                       immutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
error: could not compile `error-messages`.

To learn more, run the command again with --verbose.

A terminal screenshot of the 1.43.0 error message.


Цитаты от участников команд


Конечно, мы не можем охватить все произошедшие изменения. Поэтому мы обратились к некоторым из наших команд и спросили, какими изменениями они больше всего гордятся:


Для rustdoc значительными изменениями были:
  • автоматически сгенерированная документация для автоматических реализованных типажей,
  • сам поиск и его оптимизации (последним является преобразование метаданных в JSON),
  • возможность более точного тестирования документационных блоков "compile_fail, should_panic, allow_fail",
  • тесты документации теперь генерируются как отдельные двоичные файлы.


— Guillaume Gomez (rustdoc)

Rust теперь имеет базовую поддержку IDE! Я полагаю, что между IntelliJ Rust,
RLS и rust-analyzer большинство пользователей должны получить «не ужасный» опыт
для своего редактора. Пять лет назад под «написанием Rust» подразумевалось
использование старой школы Vim/Emacs.

— Алексей Кладов (IDEs and editors)

Для меня это было бы следующим: добавление первоклассной поддержки популярных
встроенных архитектур и усилия по созданию экосистемы, позволяющей сделать
разработку микроконтроллеров с помощью Rust лёгкой, безопасной и в то же время
увлекательной.

— Daniel Egger (Embedded WG)

Команда релизов работала только с начала 2018 года, но даже за это время
мы получили ~ 40000 коммитов только в rust-lang/rust без каких-либо существенных
регрессий в стабильной версии.
Учитывая, как быстро мы улучшаем компилятор и стандартные библиотеки, я думаю, что
это действительно впечатляет (хотя, конечно, команда релизов здесь не является
единственным участником). В целом, я обнаружил, что команда релизов проделала
отличную работу по управлению масштабированием для увеличения трафика на
трекерах, публикуемых PR и т. д.

— Mark Rousskov (Release)

За последние 3 года нам удалось превратить интерпретатор Miri
из экспериментального в практический инструмент для изучения дизайна языка и
поиска ошибок в реальном коде, отличное сочетание теории и практики
проектирования языков. С теоретической точки зрения у нас есть
Stacked Borrows,
на данный момент наиболее конкретное предложение для модели псевдонимов Rust.
С практической точки зрения, хотя изначально мы проверяли только несколько
ключевых библиотек в Miri, мы недавно увидели большое количество людей,
использующих Miri для поиска и исправления ошибок в своих
собственных крейтах, зависимостях и аналогичное понимание среди участников,
улучшающих Miri, например, добавив поддержку доступа к файловой системе,
раскручивания стека и многопоточности.

— Ralf Jung (Miri)

Если бы мне пришлось выбрать одну вещь, которой я больше всего горжусь,
это была работа над NLL. Это не только потому, что я думаю, что это сильно
повлияло на удобство использования Rust, но и потому, что мы реализовали его,
сформировав рабочую группу NLL. Эта рабочая группа привлекла много замечательных
участников, многие из которых до сих пор работают над компилятором. Открытый
исходный код в лучшем виде!

— Niko Matsakis (Language)

Сообщество


Поскольку язык изменился и сильно вырос за последние пять лет, изменилось и его сообщество. На Rust было написано очень много замечательных проектов, и присутствие Rust в промышленной разработке выросло в геометрической прогрессии. Мы хотим поделиться статистикой о том, насколько вырос Rust.


  • Rust четыре года подряд признавался «Самым любимым языком программирования» по опросам разработчиков Stack Overflow, проводимых в последние года, начиная с версии 1.0.
  • Только в этом году мы обработали более 2,25 петабайта (1PB = 1000 ТБ) различных версий компилятора, инструментария и документации!
  • В то же время мы обработали более 170 ТБ пакетов для примерно 1,8 миллиарда запросов на crates.io, удвоив ежемесячный трафик по сравнению с прошлым годом.
  • Когда Rust версии 1.0 был выпущен, можно было подсчитать количество компаний, которые использовали его в промышленной разработке. Сегодня его используют сотни технологических компаний, а некоторые из крупнейших технологических компаний, таких как Apple, Amazon, Dropbox, Facebook, Google и Microsoft, решили использовать Rust в своих проектах для надёжности и производительности.

Заключение


Очевидно, что мы не смогли охватить все изменения или улучшения в Rust, которые произошли с 2015 года. Какими были ваши любимые изменения или новые любимые проекты Rust? Не стесняйтесь размещать свой ответ и обсуждение на нашем форуме Discourse.


Наконец, мы хотели бы поблагодарить всех кто внёс свой вклад в Rust, независимо от того, добавили ли вы новую функцию или исправили опечатку, ваша работа сделала Rust удивительным сегодня. Мы не можем дождаться, чтобы увидеть как Rust и его сообщество будут продолжать расти и меняться, и посмотреть, что вы все будете строить с Rust в ближайшее десятилетие!


От переводчиков и участников сообщества


Я помню свои первые впечатления, когда я сделал какой-то простой алгоритм на Rust, скомпилировал и попытался запустить. Что такое? Ничего не произошло? Я попытался ещё раз нажать ENTER на этот исполняемый файл, и вроде снова ничего не произошло… Нажатие Ctrl+O выявило собственно "проблему" — код отрабатывал настолько быстро, что я даже не успевал заметить моргание панелей MC
Это было настолько свежим впечатлением после других, более тяжеловесных платформ, что я тут же полез скачивать и изучать Rust Book.
С тех пор прошла целая куча времени, и видно, как платформа (а язык Rust теперь смело можно называть не просто языком, а платформой) растёт и матереет, для меня наиболее значимым изменением оказались NLL, я прямо ощутил, насколько легче стало делать алгоритмический код, включающий в себя, как правило, множество изменяемых структур данных. И нельзя не отметить также насколько улучшился Rust плагин для моей любимой среды программирования Intellij Idea.
За эти 5 лет я стал участником русскоязычного Rust сообщества и Rust стал моим профессиональным инструментом. Мой путь в Rust был довольно долог и тернист, но сейчас у меня нет ощущения, что мне хочется переходить куда-то ещё. Так что пока я поработаю на Rust-е

nlinker (Translation Team)

О Rust узнал в январе 2017 года.
Очень скоро нашел перевод книг на русский язык.
Стал читать и помогать с переводом.
На то время проделанная работа по переводам была значительной.

Были переведены почти полностью
  • Rust book first edition
  • Rust by example
  • Rustonomicon.


Главной сложностью было следить за оригиналом и делать синхронизацию с нашей версий.
Но мы преодолели эти сложности.

За прошедший год мы полностью перевели Rust by example, Rust book second edition, Async book

Для меня одно из открытий в Rust стало, то что отладчиком за 3 года пользовался менее 5 раз. Такая выразительная система типов в этом языке.

andreevlex (Участник команды rust-lang-ru)



С любыми вопросами по языку Rust вам смогут помочь в русскоязычном Телеграм-чате или же в аналогичном чате для новичковых вопросов.


Данную статью совместными усилиями перевели andreevlex, funkill, nlinker, l4l, Hirrolot, P0lunin и blandger.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 52

    +5
    1.2 — Параллельная кодогенерация: уменьшение времени компиляции всегда являлось главной темой в каждом выпуске Rust, и сейчас трудно представить, что когда-то был короткий период времени, когда Rust вообще не имел параллельной кодогенерации.

    Ну, лично у меня этот период и не заканчивался :)


    [profile.release]
    lto = true
    codegen-units = 1

    А вообще, с тех пор большой прогресс был сделан. Надеюсь, все же после того как домучают асинк-авейт наконец посмотрят в сторону GAT/Chalk, доделают импл трейты, ну и вообще начнут наконец делать удобнее не одну только асинхронность.

      +5

      А в Токио есть ржавый дизель.

        +3

        А на Собрании Паутины делают ржавый подъёмный кран.


        Лопата

        Cranelift для WebAssembly

        0
        Какая-то дикость, «Шаблоны срезов», «менеджер инструментальных средств», «объект первого класса»… а чего crates как «ящики» не перевели?
        Задача перевода же вроде — сделать текст более понятным.
          0

          Переводы раньше: потратить несколько дней в окружении словарей и справочников, чтобы по сути переписать статью заново на своём языке.


          Переводы сейчас: скопировать текст в DeepL, скопировать выхлоп на сайт.

            +12

            Объект первого класса уже вполне устоявшийся термин, я его при неформальном общении и использую. По остальным согласен, лично для меня понятнее будут англицизмы.

            +1

            а расскажите в чем преимущество ? над try!?

              +1
              Короче и по месту
                +7

                try!(try!(try!(foo()).bar()).baz()) vs foo()?.bar()?.baz()? — согласитесь, второй проще писать и читать.
                Ещё пример, код с прокидыванием ошибок выглядит так будто их нет:
                diff
                Главная цель, чтобы обработку ошибок было делать проще чем их игнорировать.

                  0
                  Главная цель, чтобы обработку ошибок было делать проще чем их игнорировать.
                  так и до исключений дойдут
                    +5

                    Исключения как раз очень удобно игнорировать (для этого вообще ничего не нужно делать) и довольно громоздко обрабатывать.

                      0

                      Вот тут мне интересно. В дотнете, например они по умолчанию стандартными обработчиком — выводятся пользователю.


                      В моей практике в большинстве ситуаций с исключениями ничего не надо делать особенного — просто передать на уровень абстракции выше. На каком-то уровне они записываются в лог или показываются пользователю или заворачиваются в исключение более высокого уровня.


                      Очень редко нужно делать что-то кроме этих нескольких вариантов (например сгенерировать дамп при oom, retry при разных ситуациях с сетью и блокировках).


                      Т.е. умолчания с моей точки зрения позволяют не засорять код монотонным повторением "здесь все как и везде".


                      А как у вас? Какую особенную обработку исключений вы делаете?

                        +7
                        Простой пример, возьмём вызов goto.bar()
                        Внимание вопрос, кидает ли этот метод исключения, а если да то какие когда и как?
                        Проблема в том, что он может кидать их как угодно — например крайне редко, или в зависимости от окружения итд. Обычно код исключений не кидает, разработчик запускает код и вроде как всё работает, но предположим что метод не умеет ходить в бар на сетевом диске и приложение начинает крашиться только у заказчика.
                        Конечно документация и всё такое, но положа руку на сердце — кто её внимательно читает? А при обновлении версии библиотеки кто будет её читать и делать полный код ревью что новый код работает корректно?
                        Скомпилировалось? Тесты прошли? Релизим.
                        Другой минус исключений в том что они не бесплатные. В том плане что это не просто накладные расходы на обслуживание разкрутки стека, но ещё и произвольный рантайм оверхед. Для .net это допустим не проблема, но для Rust это проблема, не завозить же рантайм из за этого. И да, в С++ эту проблему не решили, там есть исключения и по сути они тащат с собой мини рантайм для раскрутки стека, со всякими оптимизациями и ухищрениями (например хороший вопрос как кинуть исключение о том что кончилась память если для раскрутки стека нужно выделить память :D ).
                        Подход Rust больше ориентирован на системное программирование, что хорошо укладывается в парадигму безопасного языка с поведением предсказанным во время компиляции.
                          0
                          А разве паника не вид исключения и не имеет тех же накладных расходов?
                          Вопрос без подвоха, мне действительно интересно с точки зрения познания.
                            0

                            Я бы сказал, что перехватывание паники это редкая ситуация (а при некоторых ключах компиляции — невозможная), а перехватывание эксепшнов один из основных флоу использования.

                              0
                              Это кстати отличный вопрос. Согласно доке на раст есть 4 уровня исключений
                              — всё вполне может пойти не так = Optional
                              — всё обычно ок = Result
                              — поток накрылся — panic()
                              — всё совсем плохо — abort()

                              Первые два это синтаксические конструкции которые при желании можно написать полностью на расте.
                              Последний в принципе тоже т.к. приложение просто убивается через вызов к ОС.
                              А вот паника это как раз раскрутка стека и вызов деструкторов, однако оптимизированная на то что паника это исключительный случай — т.е. код раскрутки стека по идее будет работать медленнее чем в С++, однако расходы на обслуживание самой возможности раскрутки будут меньше (увы, нулём они быть не могут т.к. в любом случае нужно как то оставлять информацию для зачистки кадра.

                              Но это к сожалению всё что даёт документация, если смотреть на простые примеры то там раст и С++ почти одинаковы (раст на ошибки аллокации памяти вызывает abort что логично и даже возможно когда-нибудь будет что-то аналогичное в С++ )
                                +2
                                увы, нулём они быть не могут

                                При статической линковке и настройке panic = abort могут.

                            +3

                            Наличие триллиона TryXXX методов показывают, что всё не так просто. Почему Parse не возвращает Option<T>, а бросает исключение? Почему нет способа получить значение из словаря которого там может не быть (кроме того же неуклюжего TryGetValue)?


                            Короче нет, эксепшны это хорошо когда их не надо обрабатывать, но обычно надо обрабатывать или нет знает только код выше.


                            Т.е. умолчания с моей точки зрения позволяют не засорять код монотонным повторением "здесь все как и везде".

                            Это не монотонное засорение. Так, например, если функция раньше всегда возвращала знчение а теперь может упасть, это ломающее изменение. И если у вас там какие-нибудь CommitTransaction() за вызовом этой функции были то очень желательно учесть, что транзакции теперь иногда могут отменяться.

                              –2

                              Наличие триллиона TryXXX методов показывают, что всё не так просто. Почему Parse не возвращает Option, а бросает исключение? Почему нет способа получить значение из словаря которого там может не быть (кроме того же неуклюжего TryGetValue)?

                              Возможно, потому, что nullable появились не сразу.


                              Короче нет, эксепшны это хорошо когда их не надо обрабатывать, но обычно надо обрабатывать или нет знает только код выше.

                              Поэтому оно обычно просто передается наверх до того кода, который знает как его обрабатывать.


                              Это не монотонное засорение. Так, например, если функция раньше всегда возвращала знчение а теперь может упасть, это ломающее изменение.

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


                              И если у вас там какие-нибудь CommitTransaction() за вызовом этой функции были то очень желательно учесть, что транзакции теперь иногда могут отменяться.

                              Тут см выше. Транзакции, наверное, бывают разные но в РСУБД, практически все внутри транзакции может упасть.


                              В-общем, насколько я понял, из-за упора на системность и быстродействие "исключения везде" для раста не подходят, потму, что это не zero cost abstraction.


                              Я бы тогда согласился с предложением, как здесь сказали, do-нотации, чтобы хотя бы вопросики ставить не на каждуй строчку, а на блок.


                              Что вы об этом думаете?

                                +1
                                Возможно, потому, что nullable появились не сразу.

                                Nullable не хватит, ведь эксепшн несет в себе информацию об ошибке. Чтобы получить не меньше информации нужен по крайней мере Either, или как он в расте называется — резалт.


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

                                Ну, в прикладных поменьше надо, да.


                                Тут см выше. Транзакции, наверное, бывают разные но в РСУБД, практически все внутри транзакции может упасть.

                                Имеется в виду, что раньше в коде было CommitTransaction() и тарнзакция коммитилась, а теперь там эксепшн падает и до него выполнение не доходит.


                                В-общем, насколько я понял, из-за упора на системность и быстродействие "исключения везде" для раста не подходят, потму, что это не zero cost abstraction.

                                Думаю, тут много моментов: и зирокостность, и надежность, в большей степени. По тем причинам, что я выше написал. Нельзя молчаливо подменить всегда успешно отрабатывающую функцию на падающую так, чтобы никто не заметил.


                                Что вы об этом думаете?

                                Try-блоки и так хотят добавить, но они немного не так работают, как вы хотите. В целом, мне кажется явный флоу во многом упрощает понимание. Поставить вопросик — не велика беда. Зато всегда видно, где ошибка может произойти и какая. Ваш вариант еще и не будет работать, потому что тип ошибки конкретный. То есть если у нас есть грубо говоря функция которая с консоли текст читает, там возможна одна ошибка (например, io::Error). Допустим теперь мы начали парсить в этой функции что-нибудь (соответственно ParseError), так что у нас должен измениться тип ошибки функции. С неявным преобразованием и вы тут же получите непонятную ошибку "Cannot convert ParseError to io::Error", и откуда она взялась в случае с неявными флоу — ХЗ. Ошибки в боксах в расте таскать не очень принято.


                                Из моего недавнего кода:


                                let user_model: UserModel = request
                                    .send()
                                    .await
                                    .map_err(GenericError)?
                                    .json::<UserModelRaw>()
                                    .await
                                    .map_err(GenericError)?
                                    .into();

                                Функции send и json возвращают разные ошибки (ошибку HTTP и парсинга жсона соответственно), и мне нужно явно их оборачивать в общую ошибку, иначе ничего не заработает. То есть функции могут завершиться с одной ошибкой, оборачиваю я её во вторую, а возвращается вообще третий: actix_web::Error (вопросик умеет конвертацию ошибки производить если такое преобразолание задано). Так что без него как минимум в расте не обойтись никак

                                  0
                                  Nullable не хватит

                                  Мы говорили про методы try* — они не возвращают ничего о причине, потому, что считают ровно одну причину не исключением, а нормальным ходом выполнения.


                                  а теперь там эксепшн падает и до него выполнение не доходит

                                  Если там было что-то связанное с РСУБД то такой ситуации нет — там и раньше могло упасть.


                                  "Cannot convert ParseError to io::Error"

                                  Как в Java checked exceptions, только там, похоже, многие признают это не очень удачной идеей.

                                    +2
                                    Мы говорили про методы try* — они не возвращают ничего о причине, потому, что считают ровно одну причину не исключением, а нормальным ходом выполнения.

                                    Ну так это минус же. В расте я могу если не распарсил получить причину почему так:


                                    use std::error::Error;
                                    
                                    fn main() -> Result<(), Box<dyn Error>>{
                                        let mut input = String::new();
                                        std::io::stdin().read_line(&mut input)?;
                                        let x: u32 = input.trim().parse()?;
                                        println!("{0}*{0}={1}", x, x*x);
                                        Ok(())
                                    }
                                    

                                    -24
                                    Error: ParseIntError { kind: InvalidDigit }
                                    
                                    Error: ParseIntError { kind: Empty }

                                    А тут у меня либо вариант с дорогим эксепшном, либо просто "упс, нишмагла". Если тип ошибки мне не важен, я могу восстановить поведение Try-методов таким образом:


                                    let x: Option<u32> = input.trim().parse().ok();

                                    Но в обратную сторону оно так не работает.


                                    Если там было что-то связанное с РСУБД то такой ситуации нет — там и раньше могло упасть.

                                    Нет, не связанное. Например, раньше там в какой-нибудь кэш локально что-то записывалось (тут только с оомом если упадёт, а весь код всегда предполагает, что аллокации завершаются без ошибок), а тепепрь начали по сети писать, соответственно появился шанс на сетевую ошибку.


                                    Как в Java checked exceptions, только там, похоже, многие признают это не очень удачной идеей.

                                    Что плохая идея для джавы, то хорошая для раста. Плюс, подход к ошибкам в приложениях и библиотеках различается: в библиотеках стараются как можно более специальный тип ошибки иметь, в приложениях часто идут по пути "что-то". Нужно понимать ведь, что в расте нет рефлекшна, и сдаункастить ошибку из разряда if error is MyException ex { ... } в общем случае не выйдет. Плюс с АДТ я бы сказал что комбинировать их куда проще. Плюс макросы для облегчения бойлерплейта.

                            0
                            Исключения как раз очень удобно игнорировать (для этого вообще ничего не нужно делать)...
                            ну это уже от реализации зависит. Мы можем потребовать чтобы вызов бросающего foo из небросающего bar без обработки приводил к ошибке компиляции, например.
                            … и довольно громоздко обрабатывать.
                            мне кажется обработка исключений как минимум не более громоздкая чем почти любого другого вида ошибок
                              0

                              Да идельно было бы скрестить do-нотанцию из монад (т.к. она достаточно явная и удобная), только чтобы она компилировалась в исключения (т.к. они быстрее). Вообщем-то Result/? это примерно это и есть. В блоге withoutboats про это было, что надо бы сделать по аналогии с Future/async/await некий Result/сatch/?.


                              На сколько я понимаю С++ (а в других языках тем более) все исключения ловятся по сути через dynamic_cast, а в расте отличие в том что тип Result может быть известен в компайл тайм.


                              Мы можем потребовать чтобы вызов бросающего foo из небросающего bar без обработки приводил к ошибке компиляции

                              Тогда прийдётся аннотировать все. и получим ту же вербозность что и в раст. Сейчас исключения везде работают так, что bar абсолютно прозрачен для исключений и как бы ничего о них не знает, что удобно.

                                0
                                … только чтобы она компилировалась в исключения (т.к. они быстрее
                                ну… не совсем. Там много нюансов и в общем случае без исключений быстрее чем с ними, т.к. из-за потенциальных исключений компилятор не может проводить множество оптимизаций
                                … а в расте отличие в том что тип Result может быть известен в компайл тайм.
                                ну Result раста тоже не самый оптимальный вариант, однако чтобы сделать лучше нужны изменения ABI:
                                Герб Саттер (Herb Sutter) в P709 описал новый механизм передачи исключений. Идейно, функция возвращает std::expected, однако вместо отдельного дискриминатора типа bool, который вместе с выравниванием будет занимать до 8 байт на стеке, этот бит информации передаётся каким-то более быстрым способом, например, в Carry Flag.
                                цитата из этой статьи. По сути, можно даже далее улучшить этот механизм, генерируя под обработку ошибок отдельный landing pad (так же, как это в текущих исключениях с++), но сохраняя информацию о типе ошибки и, тем самым, избавляясь от dynamic_cast'а. В абстрактном идеальном языке это могло бы выглядеть так:
                                fn foo() -> ResultType or ErrorType;
                                  +1

                                  Исключения быстрее только если они не происходят. А через резалты удобно производить и обычные "Ошибающиеся" вычисления, вроде упомянутого парсинга. В сишарпе как раз в угоду безопасности есть эти убогие TryXXX функции, с резалтами можно использовать один механизм. И да, ни разу не слышал чтобы у кого-то резалты тормозили, так что реализовывать через исключения то что не надо возможно и не стоит.

                                    0
                                    Исключения быстрее только если они не происходят
                                    вы кажется проигнорировали то место где я писал что это альтернативный механизм, через который можно было бы реализовать исключения/ошибки.

                                    Давайте такой пример: вот есть у нас тип T, не помещающийся в регистры. Тогда Result, содержащий еще флаг заполненности, будет передан через стек вместе с T. Как минимум, меняя ABI, можно сделать чтобы этот флаг передавался в отдельном регистре, и это точно будет не медленнее, чем положить его на стек в вызываемой функции, а потом прочитать со стека в вызывающей. А еще учтите что существует множество таких T, которые можно передавать в регистрах, но для которых Result будет передан через стек. Простой пример — было бы оптимальнее, если бы square2 принимал аргумент в трех регистрах, верно?
                                      –1

                                      А это мы уже переизобретаем MonadError :)

                                        +2

                                        Нет, не переизобретаем. Потому что монады (точнее, способы работы с ними) — часть синтаксиса языка, а тут обсуждаются соглашения о вызовах. Друг с другом эти вещи не связаны.

                                          0

                                          Ну как по мне это как раз такая вещь, можно использовать Either в качестве ошибки, а можно что-то другое.

                                  +4
                                  Мы можем потребовать чтобы вызов бросающего foo из небросающего bar без обработки приводил к ошибке компиляции, например.

                                  Если вы встанете на этот путь и пройдёте его до конца, вы получите checked exceptions, которые абсолютно эквивалентны обработке ошибок с помощью возврата алгебраических типов. Отличия чисто синтаксические, да и они минимальны.

                                    +1

                                    Кстати, отличия всё-таки есть и мне кажется, что checked exceptions не такие универсальные.


                                    Разница появляется, если есть несколько сущностей и "исключительная" ситуация не настолько исключительная, чтобы прерывать выполнение, но сообщить о ней всё-таки надо.


                                    Например, мы пытаемся скачать список файлов:
                                    listFiles.map(downloadFile)
                                    Или даже так:
                                    futures = threadPoolExecutor.executeAll(listOfTasks)
                                    Если downloadFile или запущенная задача будет бросать checked exceptions, то нам придётся обрабатывать их прямо в месте вызова. В случае алгебраичечких типов такой проблемы нет — результат можно не распаковывать и сохранить/передать дальше.


                                    Так что получается, что алгебраические типы данных в любом случае нужны, и если они есть — можно не усложнять язык исключениями.

                                      +3

                                      Исключения являются эффектами, а возвращаемые значения — нет. Разница не только синтаксическая:


                                      xs.iter().map(|x| f(x)).collect::<Vec<_>>()

                                      Если f бросает исключение, то это серьезно мешает композиции функций, особенно в ленивых и асинхронных вычислениях.

                                        0

                                        Если f бросает исключение, будет что-то вроде


                                        xs.iter().map((|x| f(x)).catch()).collect::<Vec<_>>()

                                        Не очень-то и мешает.

                                          +5

                                          Ну вот вы и сами написали ответ: чтобы достичь композируемости нужно избавиться от исключений и преобразовать их в возвращаемые значения.


                                          В вашем примере для (гипотетической) f: Fn(A) throws E -> B по-видимому catch вернёт нечто вроде R<B> и вы получите вектор из этих зиначений: Vec<R<B>>.


                                          А чтобы дальше работать с этими R, нужно будет научиться составлять композицию значений R<A> и Fn(A) -> R<B> для любых R, A и B (хотя R лучше бы удовлетворять трём законам).

                                            0
                                            0
                                            Исключения являются эффектами, а возвращаемые значения — нет. Разница не только синтаксическая:
                                            это если предположить что checked exceptions реализуются через тот же механизм, что и обычные. При этом вопрос делать ли исключения эффектами или реализовывать как синтаксический сахар над возвратом типа-суммы — полностью на усмотрение компилятора.
                                              0

                                              Checked exceptions такие как в джаве не изоморфны алгебраическим типам. Почему?
                                              Попробуйте ответить на вопрос: какая сигнатура будет у функций map и filter в случае checked exceptions?

                                                0

                                                Примерно такая же, какая была бы у mapM и filterM для Result, а что?

                                                  0

                                                  Вы-таки отказываете функции map в параллелизме?


                                                  Плюс (раз уж мы о Хаскеле) у нас есть всегда значения типа IO a. Соответственно типы [IO a] и IO [a] довольно сильно различаются. Как будет выглядеть тип таких значений с checked exceptions, но без типа IO мне совершенно непонятно.

                                    0

                                    Ну это только один пример удобства, хотя только подталкивает все ошибки обрабатывать одинаково.
                                    А вот второй аргумент непонятен, что try! что ? просто возвращают ошибку, разницы никакой.

                                      +1

                                      Ещё ? отличается тем, что его можно вызвать на значении любого типа, реализующего Try, в частности, на Option.

                                      +5

                                      Удобство и более чистый код. Особенно это видно для цепочек вызовов.


                                      let var = try!(try!(try!(
                                          foo()
                                      )
                                      .bar(),)
                                      .baz());
                                      
                                      let var = foo()?
                                          .bar()?
                                          .baz()?;
                                      +5

                                      Моё знакомство с Rust началось совсем недавно, узнал я о нём по большей части из дискуссий на этом прекрасном сайте, однако одного знания было мало. Именно изучать его как язык меня сподвигла библиотека orjson для рабочего проекта по Python. Мне захотелось узнать, как она выглядит изнутри и изучить язык. Потом прошёлся по тегу Rust уже с какой-то целью, которой придерживался, и, наконец, установил себе rustup.


                                      Когда я начал писать на нём первые строчки, изучать rustbook(спасибо переводчикам! даже со знанием английского, чтение на родном языке проще), решать алгоритмические задачи просто чтобы получше изучить синтаксис и собрать побольше шишек… появилось странное ощущение удовольствия от языка. Почему-то мне он кажется компромиссом между Python и C/C++, то есть глубокие системные возможности, производительность, но при этом язык не пытается убить меня синтаксисом и показывает вменяемые ошибки, а не плюёт в меня куском памяти из моего прошлого ввода.


                                      Желаю языку дальнейшего развития, популяризации и улучшения в той же степени, в какой себе хотя бы когда-нибудь его понять :)

                                        +1

                                        Вопрос по макросам и кодогенерации в расте. Есть событие, есть подписчики. Можно ли сделать так:
                                        0 подписчиков => удаяляем emitter(dispatcher) на этапе компиляции
                                        1 подписчик => заменяем emitter на прямой вызов обработчика или инлайним его
                                        2+ подписчиков => заменяем emitter на итерацию по коллекции
                                        ???

                                          +1

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

                                          –4
                                          Внесите Царя!
                                            +1

                                            Унесите Царя!

                                            0

                                            Давно не следил за развитием rust. Раз язык преподносится как язык общего назначения, уже стоит смотреть в его сторону тому, кто пишет низкоуровневый софт для арм? Интересует возможность создания кода под такие платформы как nRF52811/33 от Nordic semi, например. Или пока си и асм вне конкуренции? Учитывая, что свои библиотеки nordic предоставляет на си, можно ли часть своей логики написать на расте? Имеется ли тулчейн наподобие arm-none-eabi?

                                              +1
                                              это всё относится к бекенду компилятора. Если clang умеет компилировать си под эти платформы, значит умеет и раст.
                                                +1

                                                Дёргать из rust библиотеки с C ABI проблемы большой нет, равно как и наоборот предоставлять C ABI наружу. А поддержка конкретной платформы — дело llvm, как выше написал Antervis.


                                                Для ARM всё довольно неплохо и постепенно развивается. Не знаю как ситуация с Cortex-R, но Cortex-M живёт и здравствует с постепенным наращиванием объёма библиотек, улучшение hal и средств отладки. IIRC, какие-то шевеления в рамках nRF были, посмотрите https://github.com/nrf-rs/nrf-hal и соседние проекты, также https://github.com/rust-embedded/wg.


                                                UPD: судя по devkit'у nRF52 там простой Cortex-M4F, который прекрасно поддерживается текущим компилятором rust, так что больших проблем быть не должно, если говорить про низкоуровневую часть.

                                                  0

                                                  Ага, большое спасибо, буду изучать.

                                              Only users with full accounts can post comments. Log in, please.