Команда Rust рада представить новую версию языка — 1.58.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.


Если у вас есть предыдущая версия Rust, установленная через rustup, то для обновления до версии 1.58.0 вам достаточно выполнить команду:


rustup update stable

Если у вас ещё нет rustup, то можете установить его со страницы на нашем веб-сайте, а также ознакомиться с подробным описанием выпуска 1.58.0 на GitHub.


Что стабилизировано в 1.58.0


В Rust 1.58 появились захваченные идентификаторы в форматируемых строках, изменился путь поиска в Windows для Command, в стандартной библиотеке стало больше аннотаций #[must_use], а также были стабилизированы некоторые функции.


Захваченные идентификаторы в форматируемых строках


Форматируемые строки теперь могут захватывать аргументы, если вы просто напишете {ident} в строке. Форматируемые строки уже давно принимают позиционные аргументы (возможно, по индексу) и именованные аргументы. Например:


println!("Hello, {}!", get_person());                // implicit position
println!("Hello, {0}!", get_person());               // explicit index
println!("Hello, {person}!", person = get_person()); // named

Сейчас именованные аргументы также могут быть захвачены из окружения, например:


let person = get_person();
// ...
println!("Hello, {person}!"); // captures the local `person`

Это также может использоваться в параметрах форматирования:


let (width, precision) = get_format();
for (name, score) in get_scores() {
  println!("{name}: {score:width$.precision$}");
}

Форматируемые строки могут захватывать только простые идентификаторы, но не произвольные пути или выражения. Для более сложных аргументов либо сначала присвойте им локальное имя, либо используйте старый стиль форматирования аргументов name = expression.


Эта функция работает во всех макросах, принимающих форматируемые строки. Тем не менее, существует один крайний случай — работа макроса panic! в выпусках 2015 и 2018, где panic!("{ident}") по-прежнему обрабатывается как обычная строка. Компилятор предупредит об этом, но ожидаемого эффекта не произойдёт. Благодаря обновлению макроса паники в выпуске 2021 года для улучшения согласованности в panic! это будет работать ровно так, как и ожидалось.


Сокращение пути поиска для Command на Windows


На Windows std::process::Command больше не ищет исполняемые файлы в текущей директории. Этот эффект был связан с поведением win32 API CreateProcess, из-за чего поиск осуществлялся в следующем порядке:


  1. (Специфично для Rust) Директории перечисленные в переменной окружения PATH дочернего процесса, если эта переменная явно изменялась родительским процессом.
  2. Директория, из которой было загружено приложение
  3. Текущая директория для родительского процесса
  4. Системная директория 32-битной Windows
  5. Системная директория 16-битной Windows
  6. Директория Windows
  7. Директории, указанные в переменной окружения PATH

Однако использование текущего каталога может привести к неожиданным результатам или даже опасному поведению при работе с ненадёжными директориями. Например, ripgrep опубликовали CVE-2021-3013 когда узнали, что их дочерние процессы могут быть перехвачены таким образом. Даже собственные документы PowerShell Microsoft не используют текущую директорию ради безопасности.


Теперь Rust осуществляет свой поиск без учёта текущей директории и старой 16-битной директории, так как нет API, чтобы найти её расположение. Так что новый порядок поиска для Command на Windows таков:


  1. Директории, указанные в дочерней переменной окружения PATH
  2. Директория, из которой было загружено приложение
  3. Системная директория 32-битной Windows
  4. Директория Windows
  5. Директории, указанные в переменной окружения PATH

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


Больше атрибутов #[must_use] в стандартной библиотеке


Атрибут #[must_use] применяется к типам и функциям, у которых отсутствие явной обработки или результата считается ошибкой. Это давно используется в стандартной библиотеке для типов, подобных Result, которые должны быть проверены на наличие ошибок. Атрибут также помогает отловить ошибки ожидания изменения функцией передаваемого значения, в то время как она возвращает новое значение.


Библиотечное предложение №35 было одобрено для проверки в октябре 2021 года и расширяет применение #[must_use] в стандартной библиотеке. Оно покрывает больше функций, основной эффект которых — возвращение значения. Похоже на идею чистоты функций, но более слабо, чем настоящая языковая черта. Часть функций была представлена в 1.57.0 — теперь же добавлена оставшаяся часть.


Стабилизированные API


Стабилизированы следующие методы и реализации трейтов:



Следующие ранее стабилизированные API стали const:



Прочие изменения


В синтаксис, пакетный менеджер Cargo и анализатор Clippy также внесены некоторые изменения.


Участники 1.58.0


Множество людей объединились для создания Rust 1.58.0. Мы не смогли бы сделать это без всех вас. Спасибо!


От переводчиков


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


Данную статью совместными усилиями перевели belanchuk, andreevlex, SomeAkk, TelegaOvoshey, torgeek, olafars и funkill.

Комментарии (42)


  1. tbl
    14.01.2022 22:37

    Смотрю на захват переменных, вспоминаю log4shell и думаю: "Надеюсь, парсинга шаблонов {...} в рантайме и исполнения инъектированного кода в планах нет?"


    1. rmuskovets
      14.01.2022 22:49
      +8

      В Rust println! и ему подобные принимают как шаблон только строковые литералы, такое кинет ошибку компиляции:

      let fmt = "{fmt}";
      println!(fmt);


      1. tbl
        15.01.2022 03:13

        Да, это знаю, просто как бы кому не пришла в голову "светлая" идея протащить это в рантайм для обычных строк (а не литералов, которые раздербаниваются на куски во время предкомпиляции)


        1. ZyXI
          15.01.2022 05:47
          +4

          В рантайм протащить это было бы сложновато. Как технически, так и организационно. Во‐первых, у Rust в runtime просто нет такой кучи информации, которую можно получить в той же Java, если её туда специально не добавит компилятор. Я примерно представляю, как это «специально добавит» должно выглядеть и оно содержит множество принципиальных проблем и вообще непросто в реализации.


          Во‐вторых, пока даже на простенькие форматные строки с произвольными выражениями RFC не собрали — а там из проблем по сути только «как экранировать» и «а оно точно нужно?». Предложение вида «давайте потратим несколько человекомесяцев, чтобы получить в std свой собственный log4shell», конечно же найдёт широкую поддержку у разработчиков и сообщества.


          Более ограниченный вариант вида «пользователь должен сам собрать словарь со всеми переменными, которые он хочет сделать доступными» ещё может появится в std и наверняка в каком‐то виде уже есть в крейтах. Но это не особо опасно.


  1. amarao
    14.01.2022 23:48

    Мне очень жалко, что фичу завезли только в макросах и в очень куцем виде. Я бы очень, очень хотел видеть полноценные f-строки.


    1. Medeyko
      15.01.2022 00:01

      А так уж ли это много даст? Это не так уж часто нужно, по-моему, а format! должен бы справиться во всех ситуациях, когда хочется f-строк, если я правильно понимаю.


    1. Amomum
      15.01.2022 00:28
      +1

      А разве это вообще возможно в компилируемом языке?

      Я сходу не представляю, как это сделать, не таща в каждый бинарник мини-компилятор или не навешивая на все переменные море мета-информации.


      1. amarao
        15.01.2022 01:02
        +7

        Мне кажется, вполне возможно.

        Если я могу написать так:

        [
          (expr1).to_string(),
          (expr2).to_sting(),
        ].join('')

        То и компилятор может развернуть f-string в что-то подобное. Это же просто синтаксический сахар.

        ... а, я понял вопрос. Нет, речь не про eval для произвольных строк. Речь про поддержку f-нотации с строковыми литералами.

        Чтобы можно было так: `f"Hello {username}! Now is {time.time()}!"`


        1. Amomum
          15.01.2022 01:04

          Ну, в компайл-тайм - да.

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


          1. amarao
            15.01.2022 01:06

            Вот я бы хотел тут больше сахара, чтобы без макросов, а на уровне языка. В конце-концов, нам же насыпали сахара в районе for, который на Iter и IntoIter завязан.


            1. Amomum
              15.01.2022 01:09

              А чем макрос плох? Они в расте достаточно гибкие, чтобы сделать почти все что угодно. А как обкатают фичу и если нужда будет - можно и в язык запилить, как с try! было


              1. amarao
                15.01.2022 01:23

                Много букв. Чем плох BEGIN END в языке?

                FOR x IN range(0, 10).into_iter()
                BEGIN
                
                   PRINT(FORMAT!("{}", x));
                
                END

                Нравится?


                1. Amomum
                  15.01.2022 01:27
                  +2

                  Но позвольте, тут в итоге больше на один восклицательный знак..

                  println!("Hello, {person}!"); - это же сам println делает, будь это встроено в язык - что бы поменялось?


                  1. amarao
                    15.01.2022 01:29

                    `println!("Hello, {person}! Now is {time.time()}");` не скомпилируется. Только чистый захват, без выражений. Что сделает условный {person.to_lower()} и т.д. недоступным.


                    1. Amomum
                      15.01.2022 01:31
                      +2

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

                      Допилить макрос, мне кажется, существенно проще, чем допилить компилятор.


                      1. mayorovp
                        17.01.2022 10:35

                        Вот только этот макрос именно что реализуется компилятором:


                            #[rustc_builtin_macro]
                            #[macro_export]
                            macro_rules! format_args_nl {
                                ($fmt:expr) => {{ /* compiler built-in */ }};
                                ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
                            }


            1. Medeyko
              15.01.2022 01:48

              Почитал немного - в принципе, рассматривается и введение f-строк, причём скорее всего они будут просто синтаксическим сахаром к макросу format!

              Ну, в принципе, конечно, сахарнее, но это не настолько большая разница, как с for'ом, где этот сахар реально кардинально меняет степень читаемости и довольно част.


          1. tbl
            15.01.2022 03:55

            GraalVM даже при компиляции в нативный код тащит с собой jit-компилятор байт-кода, чтобы в рантайме оптимизировать то, что напрофилировал, плюс то, что при кодогенерации подъехало в classpath. А возможности для кодогенерации там обширные: агенты, ASM + CGLIB, LambdaMetaFactory и т.п.


            1. Amomum
              15.01.2022 03:57

              ._.


          1. horror_x
            15.01.2022 06:47

            Прост я не припомню нигде в компилируемых языках ничего подобного

            C# же как минимум.


            1. Cerberuser
              15.01.2022 10:29

              А если компилируемые в нативный код? C# всё-таки AOT-компилируется только в байткод, насколько я помню.


              1. horror_x
                15.01.2022 18:42
                +1

                А какая разница? Что бы изменилось от компиляции в нейтив (тем более что при желании можно и так)?


              1. mayorovp
                17.01.2022 10:37

                Вот как раз AOT-компиляция идёт в машинный код, в отличии от JIT-компиляции.


            1. Amomum
              15.01.2022 18:01

              оО А как там это работает?


              1. horror_x
                15.01.2022 18:41

                Из документации:

                // Composite formatting:
                Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
                // String interpolation:
                Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");

                If an interpolated string has the type string, it's typically transformed into a String.Format method call. The compiler may replace String.Format with String.Concat if the analyzed behavior would be equivalent to concatenation.

                If an interpolated string has the type IFormattable or FormattableString, the compiler generates a call to the FormattableStringFactory.Create method.


                1. Amomum
                  16.01.2022 01:42

                  Интересно.. т.е. это преобразование заменяет имена переменных в строке на позиционные аргументы для String.Format?


                  1. NN1
                    16.01.2022 03:16

                    Нет.

                    Это в итоге вызовет WriteLine(string) или WriteLine(FormattableString) или другие варианты.

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

                    А само форматированние строки претерпело уже несколько реинкарнаций: https://habr.com/ru/company/skillfactory/blog/599341/


                    1. Amomum
                      16.01.2022 03:17

                      Ну так а как значения-то подставлены будут?


                      1. NN1
                        16.01.2022 03:28

                        Посмотрите статью, там всё сложно:)

                        Зависит от того, в какой тип попадает строка с интерполяцией.

                        Скажем в простом варианте:

                        string a="x", b = "y";

                        string c = $"{x}{y}";

                        Будет просто string.Concat(x,y)

                        В других вариантах будет посложнее.


                      1. Amomum
                        16.01.2022 03:36

                        Ага, ну то есть строка в компайл-тайм таки парсится? Только это делает особая компиляторная магия, специально сделанная для этого случая, а не макрос?


                      1. NN1
                        16.01.2022 10:40
                        -1

                        В Rust тоже магия компилятора.

                        macro_rules! format_args {

                        ($fmt:expr) => {{ /* compiler built-in */ }};
                        ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
                        }

                        https://github.com/rust-lang/rust/blob/master/library/core/src/macros/mod.rs#L842

                        Rust ещё немного не дорос до полноценных макросов :)
                        Вот пример макроса printf без магии компилятора: printf


                      1. Amomum
                        16.01.2022 13:14

                        Справедливо :)


                      1. AnthonyMikh
                        16.01.2022 17:59
                        +1

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


                      1. NN1
                        16.01.2022 19:43

                        Как же неочевиден.

                        Можно обновлять реализацию без изменения самого компилятора.

                        В идеале всё, что можно вынести в макросы в виде библиотек, а компилятор не менять.


                      1. DarkEld3r
                        17.01.2022 12:41

                        Не очень в теме, но в расте стандартную библиотеку и компилятор не одни и те же люди делают?.. Версионируется оно точно вместе. В целом я согласен, что библиотечная реализация лучше, но в данном конкретном случае разница не кажется принципиальной. Плюс есть подозрения, что такая "магическая" реализация — это последствия поздней стабилизации макросов, когда раст 1.0 уже был, а свой format! написать ещё было нельзя.


                      1. AnthonyMikh
                        17.01.2022 17:20

                        Плюс есть подозрения, что такая "магическая" реализация — это последствия поздней стабилизации макросов, когда раст 1.0 уже был, а свой format! написать ещё было нельзя.

                        Скорее всего.


                        Вдобавок, вынос этого макроса в либу — это +1 процедурный макрос со всеми вытекающими.


                      1. DarkEld3r
                        17.01.2022 20:00
                        +2

                        Вдобавок, вынос этого макроса в либу — это +1 процедурный макрос со всеми вытекающими.

                        Честно говоря, не понял о каких последствиях речь. Реализация ведь и так есть, просто спрятана внутри компилятора, а не стандартной библиотеки. Если её перенести, то почему станет хуже?


                  1. horror_x
                    16.01.2022 03:28

                    Стоит понимать, что это не строка в привычном смысле, это скорей шаблон строки. Компилятор разбивает её на куски и ищет оптимальный способ склеить всё в итоговый результат.

                    В простейшем случае $"{foo} {bar}" будет заменено чем-то вроде String.Concat(foo, " ", bar).


          1. mayorovp
            17.01.2022 10:32

            Ну вот вам такое в компилируемом языке:


            https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation


        1. cpud36
          15.01.2022 06:43

          Там дело в том, что format обходится без преобразования в строку. Он только берёт ссылки на переменные(dyn Display и прочие), а потому возникают вопросы из серии: где разместить результаты, чтобы оно не рассыпалось в любой нетривиальной ситуации.

          Хотя, конечно, по большей части, там пока недоговорились о конкретном синтаксисе таких захватов(а-ля "а можно ли использовать строковые литералы и макросы?")


  1. mayorovp
    17.01.2022 10:29

    Библиотечное предложение №35 было одобрено для проверки в октябре 2021 года и расширяет применение #[must_use] в стандартной библиотеке. Оно покрывает больше функций, основной эффект которых — возвращение значения. Похоже на идею чистоты функций, но более слабо, чем настоящая языковая черта.

    Нет, ничуть не похоже на идею чистоты функций.


    На самом деле это похоже на линейные типы, особенно есть ещё и трейт Copy не добавлять.


    1. DarkEld3r
      17.01.2022 12:42
      +1

      Предположу, что логика такая: если на результате функции висит #[must_use], то она (скорее всего) чистая. Но да, сравнение слегка странное.