- PVSM.RU - https://www.pvsm.ru -

Rust 1.45.0: стабилизация функциональных процедурных макросов, исправление дефектов преобразования

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

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

rustup update stable

Если у вас ещё не установлен rustup, вы можете установить его [1] с соответствующей страницы нашего веб-сайта, а также посмотреть на GitHub [2].

Что вошло в стабильную версию 1.45.0

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

Исправление дефектов в преобразованиях

Изначально Issue 10184 [3] была открыта в октябре 2013 года, за полтора года до выпуска Rust 1.0. Так как rustc использует LLVM [4] в качестве backend-компилятора, когда вы пишете подобный код:

pub fn cast(x: f32) -> u8 {
    x as u8
}

компилятор Rust в версиях 1.44.0 и раньше генерировал следующее LLVM-IR:

define i8 @_ZN10playground4cast17h1bdf307357423fcfE(float %x) unnamed_addr #0 {
start:
  %0 = fptoui float %x to i8
  ret i8 %0
}

fptoui реализует преобразование и является сокращением от "floating point to unsigned integer".

Но здесь есть проблема, описанная в документации [5]:

Инструкция ‘fptoui’ преобразовывает операнд с плавающей точкой в ближайшее (округляя до нуля) беззнаковое целое значение. Если значение не помещается в ty2, то результирующее значение будет испорченным.

Оригинал

The ‘fptoui’ instruction converts its floating-point operand into the nearest (rounding towards zero) unsigned integer value. If the value cannot fit in ty2, the result is a poison value.

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

Это означает что, например, поведение следующего кода не определено:

fn cast(x: f32) -> u8 {
    x as u8
}

fn main() {
    let f = 300.0;

    let x = cast(f);

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

На моём компьютере с Rust 1.44.0 этот код печатает "x: 0", но т.к. его поведение не определено, напечатать он может всё что угодно. Это мы называем ошибкой «корректности» (ведь unsafe кода тут нет) — то есть ошибка, когда компилятор делает неправильные вещи. Мы отмечаем их в нашем трекере как I-unsound [6], и относимся к ним очень серьёзно.

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

В итоге было принято решение сделать так:

  • as будет выполнять "насыщающее приведение" (saturating cast),
  • будет добавлено новое unsafe приведение, если вы хотите пропустить проверки.

Это очень похоже на доступ к массиву, например:

  • array[i] проверит, чтобы убедиться, что array содержит по крайней мере i + 1 элемент,
  • можно использовать unsafe { array.get_unchecked(i) }, чтобы пропустить проверку.

Итак, что такое насыщающее приведение? Давайте посмотрим на слегка изменённый пример:

fn cast(x: f32) -> u8 {
    x as u8
}

fn main() {
    let too_big = 300.0;
    let too_small = -100.0;
    let nan = f32::NAN;

    println!("too_big_casted = {}", cast(too_big));
    println!("too_small_casted = {}", cast(too_small));
    println!("not_a_number_casted = {}", cast(nan));
}

Выведет:

too_big_casted = 255
too_small_casted = 0
not_a_number_casted = 0

То есть слишком большие числа превращаются в максимально возможное значение. Слишком малые числа дают наименьшее возможное значение (равное нулю). NaN выдаёт ноль.

А это новый API для небезопасного приведения:

let x: f32 = 1.0;
let y: u8 = unsafe { x.to_int_unchecked() };

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

Стабилизация функциональных процедурных макросов в выражениях, шаблонах и стейтментах

В Rust 1.30.0 [7] мы стабилизировали «функциональные процедурные макросы в позиции элемента». Например, крейт gnome-class [8]:

Gnome-класс — это процедурный макрос для Rust. Внутри макроса мы определяем Rust-о подобный мини-язык, имеющий расширения, позволяющие вам определять подклассы GObject, их свойства, сигналы, реализации интерфейса и остальные функции GObject. Цель состоит в том, чтобы не требовать небезопасного кода с вашей стороны.

Это выглядит так:

gobject_gen! {
    class MyClass: GObject {
        foo: Cell<i32>,
        bar: RefCell<String>,
    }

    impl MyClass {
        virtual fn my_virtual_method(&self, x: i32) {
            ... do something with x ...
        }
    }
}

В "позиции элемента" — это некий жаргон, но в основном это означает, что вы можете вызывать только gobject_gen! в определённых местах в вашего кода.

Rust 1.45.0 добавляет возможность вызывать процедурные макросы в трёх новых местах:

// представим, что мы имеем процедурный макрос "mac"

mac!(); // позиция элемента, то, что было стабилизировано ранее

// но здесь представлены 3 новых:
fn main() {
  let expr = mac!(); // в выражении

  match expr {
      mac!() => {} // в шаблоне
  }

  mac!(); // в стейтменте
}

Возможность использовать макросы в большем количестве мест интересна, но есть ещё одна причина, по которой многие разработчики давно ждали эту функцию: Rocket [9]. Популярный веб-фреймворк Rocket, первоначально выпущенный в декабре 2016 года, часто называют одной из лучших вещей, которую может предложить экосистема Rust. Вот пример "Привет, мир" из его предстоящего релиза:

#[macro_use] extern crate rocket;

#[get("/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

#[launch]
fn rocket() -> rocket::Rocket {
    rocket::ignite().mount("/hello", routes![hello])
}

До этого дня Rocket зависела от функциональности из ночной версии компилятора для предоставления своей гибкости и эргономики. По факту, как можно видеть на домашней странице проекта [10], тот же пример что выше в текущей версии Rocket требует наличия свойства proc_macro_hygiene для компиляции. Тем не менее, как вы можете догадаться из названия свойства, сегодня оно попадёт стабильный выпуск! Данная проблема [11] для отслеживания истории ночных функций в Rocket. Теперь они все проверены и готовы к использованию!

Следующая версия Rocket всё ещё находится в разработке, но когда она выйдет, многие будут очень довольны :)

Изменения в стандартной библиотеке

В Rust 1.45.0 были стабилизированы следующие функции:

Также теперь можно использовать char с диапазонами [28] для итерации по символам:

for ch in 'a'..='z' {
    print!("{}", ch);
}
println!();
// Выведет "abcdefghijklmnopqrstuvwxyz"

Полный список изменений вы можете увидеть в детальных примечаниях к выпуску [2].

Другие изменения

Синтаксис [2], пакетный менеджер Cargo [29] и анализатор Clippy [30] также претерпели некоторые изменения.

Участники 1.45.0

Множество людей собрались вместе, чтобы создать Rust 1.45.0. Мы не смогли бы сделать это без всех вас, спасибо [31]!

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

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

Данную статью совместными усилиями перевели nlinker [35], funkill [36], Hirrolot [37] и blandger [38].

Автор: RustLangRu

Источник [39]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/355136

Ссылки в тексте:

[1] установить его: https://www.rust-lang.org/install.html

[2] на GitHub: https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1450-2020-07-16

[3] Issue 10184: https://github.com/rust-lang/rust/issues/10184

[4] LLVM: http://llvm.org/

[5] описанная в документации: https://llvm.org/docs/LangRef.html#fptoui-to-instruction

[6] I-unsound: https://github.com/rust-lang/rust/issues?q=is%3Aissue+is%3Aopen+label%3A%22I-unsound+%F0%9F%92%A5%22

[7] Rust 1.30.0: https://blog.rust-lang.org/2018/10/25/Rust-1.30.0.html

[8] крейт gnome-class: https://gitlab.gnome.org/federico/gnome-class

[9] Rocket: https://rocket.rs

[10] домашней странице проекта: https://rocket.rs/v0.4

[11] Данная проблема: https://github.com/SergioBenitez/Rocket/issues/19

[12] Arc::as_ptr: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.as_ptr

[13] BTreeMap::remove_entry: https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html#method.remove_entry

[14] Rc::as_ptr: https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.as_ptr

[15] rc::Weak::as_ptr: https://doc.rust-lang.org/stable/std/rc/struct.Weak.html#method.as_ptr

[16] rc::Weak::from_raw: https://doc.rust-lang.org/stable/std/rc/struct.Weak.html#method.from_raw

[17] rc::Weak::into_raw: https://doc.rust-lang.org/stable/std/rc/struct.Weak.html#method.into_raw

[18] sync::Weak::as_ptr: https://doc.rust-lang.org/stable/std/sync/struct.Weak.html#method.as_ptr

[19] sync::Weak::from_raw: https://doc.rust-lang.org/stable/std/sync/struct.Weak.html#method.from_raw

[20] sync::Weak::into_raw: https://doc.rust-lang.org/stable/std/sync/struct.Weak.html#method.into_raw

[21] str::strip_prefix: https://doc.rust-lang.org/stable/std/primitive.str.html#method.strip_prefix

[22] str::strip_suffix: https://doc.rust-lang.org/stable/std/primitive.str.html#method.strip_suffix

[23] char::UNICODE_VERSION: https://doc.rust-lang.org/stable/std/char/constant.UNICODE_VERSION.html

[24] Span::resolved_at: https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.resolved_at

[25] Span::located_at: https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.located_at

[26] Span::mixed_site: https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.mixed_site

[27] unix::process::CommandExt::arg0: https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#tymethod.arg0

[28] использовать char с диапазонами: https://github.com/rust-lang/rust/pull/72413/

[29] пакетный менеджер Cargo: https://github.com/rust-lang/cargo/blob/master/CHANGELOG.md#cargo-145-2020-07-16

[30] анализатор Clippy: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md#rust-145

[31] спасибо: https://thanks.rust-lang.org/rust/1.45.0/

[32] русскоязычном Телеграм-чате: https://t.me/rustlang_ru

[33] чате для новичковых вопросов: https://t.me/rust_beginners_ru

[34] чат переводчиков: https://t.me/rustlang_ru_translations

[35] nlinker: https://habr.com/ru/users/nlinker/

[36] funkill: https://habr.com/ru/users/funkill/

[37] Hirrolot: https://habr.com/ru/users/hirrolot/

[38] blandger: https://habr.com/ru/users/blandger/

[39] Источник: https://habr.com/ru/post/511546/?utm_source=habrahabr&utm_medium=rss&utm_campaign=511546