Представляю Вашему вниманию перевод публикации о новой версии всеми любимого языка программирования Rust.


Введение


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


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


$ rustup update stable

Если у Вас всё ещё нет rustup, Вы можете получить его с соответствующей страницы на нашем сайте. Детальный обзор данного релиза доступен на GitHub.


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


Главными новшествами этого релиза можно назвать реализации трейтов FnOnce, FnMut и Fn на структурах Box<dyn FnOnce>, Box<dyn FnMut> и Box<dyn Fn> соответственно.


А также встраиваемые функции (closures) могут быть сконвертированы в небезопасные указатели на функции. Макрос dbg!, введённый в Rust 1.32.0, теперь может быть вызван без указания аргументов.


Более того, данный релиз внёс множество стабилизаций стандартной библиотеки. Ниже представлены наиболее существенные, но доступен и детальный разбор оных.


Fn* трейты реализованы на Box<dyn Fn*>


В Rust 1.35.0, трейты FnOnce, FnMut и Fn реализованы на Box<dyn FnOnce>, Box<dyn FnMut> и Box<dyn Fn> соответственно.


В прошлом, если Вы желали вызвать функцию, инкапсулированную в Box, Вам необходимо было воспользоваться FnBox, так как объекты Box<dyn FnOnce> и подобные не реализовывали соответствующие трейты Fn*. Это также мешало передаче инкапсулированных в Box функций коду, ожидавшему реализатор трейта Fn (предлагалось создавать временные встраиваемые функции).


Это было вызвано неспобностью компилятора обнаруживать подобные реализации. Этот недостаток был устранён с введением unsized_locals.


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


fn foo(x: Box<dyn Fn(u8) -> u8>) -> Vec<u8> {
    vec![1, 2, 3, 4].into_iter().map(x).collect()
}

Объекты Box<dyn FnOnce> могут быть вызваны без лишней возни:


fn foo(x: Box<dyn FnOnce()>) {
    x()
}

Конвертация в небезопасные указатели


Со времен Rust 1.19.0 стало возможным конвертирование встраиваемых функций, не захватывавших среду в указатели на функции. Например, вы могли написать:


fn twice(x: u8, f: fn(u8) -> u8) -> u8 {
    f(f(x))
}

fn main() {
    assert_eq!(42, twice(0, |x| x + 21));
}

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


/// Безопасные инварианты, переданные в `unsafe fn`.
unsafe fn call_unsafe_fn_ptr(f: unsafe fn()) {
    f()
}

fn main() {
    // БЕЗОПАСНОСТЬ: тут нет никаких инвариантов
    // Данная функция статически предотвращена от небезопасных
    // операций
    unsafe {
        call_unsafe_fn_ptr(|| {
            dbg!();
        });
    }
}

Вызов dbg!() без аргументов


Вследствие обилия вызовов println! в качестве колхозных дебаггеров, в Rust 1.32.0 был представлен макрос dbg!. Напомним, что данный макрос позволяет быстро запечатлить результат некого выражения с контекстом:


fn main() {
    let mut x = 0;

    if dbg!(x == 1) {
        x += 1;
    }

    dbg!(x);
}

Приведённые выше строки кода напечатают в терминал результат выражения x == 1 и x соответственно:


[src/main.rs:4] x == 1 = false
[src/main.rs:8] x = 0

Как было сказано в предыдущей секции, где может быть вызвана функция высшего порядка call_unsafe_fn_ptr, dbg!() также подлежит вызову без указания аргументов. Это может быть крайне полезно для обнаружения выбранных программных веток:


fn main() {
    let condition = true;

    if condition {
        dbg!(); // [src/main.rs:5]
    }
}

Стабилизации стандартной библиотеки


В Rust 1.35.0, множество компонентов стандартной библиотеки были стабилизированы. В дополнение к этому, были внесены некоторые реализации, о которых Вы можете прочесть тут.


Копирование знака числа с плавающей точкой в другое число


С данным релизом, новые методы copysign были добавлены в примитивы с плавающей точкой (конкретнее, f32 и f64):



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


fn main() {
    assert_eq!(3.5_f32.copysign(-0.42), -3.5);
}

Проверка того, содержит ли Range определённое значение


Rust 1.35.0 приобрёл парочку новых методов на структурах Range*:



С помощью данных методов, Вы можете с лёгкостью проверить, находится ли определённое значение в диапазоне. Например, Вы можете написать:


fn main() {
    if (0..=10).contains(&5) {
        println!("5 находится в диапазоне [0; 10].");
    }
}

Перевести (map) и разбить (split) заимствованный RefCell


С приходом Rust 1.35.0, Вы можете перевести и разбить заимствованное значение RefCell на множество заимствованных значений на разные компоненты заимствованных данных:



Переставить значение RefCell через встраиваемую функцию


Этот релиз представляет удобный метод replace_with, объявленный на структуре RefCell:



Хешировать указатель или ссылку по адресу


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


Копирование контента Option<&T>


С началом Rust 1.0.0, методы Option::cloned на Option<&T> и Option<&mut T> позволяли клонировать содержание в случае его присутствия (Some(_)). Однако клонирование иногда может быть дорогостоящей операцией, а методы opt.cloned() не описывали никаких подсказок.


Этот релиз внёс:



Функциональность opt.copied() такая же, как и opt.cloned(). Однако описанный выше метод запрашивает условия T: Copy, невыполнение которого вызвет ошибку компиляции.


Изменения в Clippy


Clippy, инструмент, отлавливающий часто встречаемые несовершенности для повышения качества кода, обзавёлся drop_bounds. Оно срабатывает в тех случаях, когда обобщённая функция запрашивает выполнения условия T: Drop:


fn foo<T: Drop>(x: T) {}

Это часто является ошибкой, так как примитивы не реализуют Drop. Более того, T: Drop не покрывает типы, подобные String, не имеющие наглядного поведения деструктора, а скорее результат встраиваемых типов (как Vec<u8>).


В дополнении к drop_bounds, данный релиз разделяет redundant_closure в redundant_closure и redundant_closure_for_method_calls.


Прочесть детальный релиз Clippy можно тут.


Изменения в Cargo


Детальное описание изменений Cargo доступно здесь.


Участники 1.35.0


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


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


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

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


  1. ozkriff
    24.05.2019 15:20
    +2

    Выпуск полезный, не сильно значительный — в основном мелкие эргономические улучшения. Ждем 1.36 с hashbrown и 1.37 с async'ом (какой бы там синтаксис не выбрали в итоге). :)


    Тем временем, я пару недель назад таки срезал v0.5 версию своей пошаговой ржавой игры Zemeroth и написал огромную новость об этом в девлоге: "Zemeroth v0.5: ggez, WASM, itch.io, visuals, AI, campaign, tests".
    Неприятные вопросы переезда между движками и работы в вебе хотя бы на время закрыты, роадмап написан, теперь планомерно движусь к выпуску v0.6.


    1. humbug
      24.05.2019 16:32
      +1

      А есть даты по parking_lot?


      1. ozkriff
        24.05.2019 16:55
        +1

        Там же еще даже PR не влит — https://github.com/rust-lang/rust/pull/56410 — раньше 1.37 точно не доберется.


    1. Propp
      25.05.2019 07:34
      +1

      Вроде как выбрали .await и даже слили PR.


      1. Halt
        25.05.2019 08:52
        +1

        Они сами писали что это еще не окончательное решение. То есть они теоретически готовы к дискуссии, если в последний момент появится какая-то радикальная альтренатива, но вероятность этого довольно мала.


  1. blandger
    24.05.2019 16:55
    +2

    Наверное зря не сделали ссылку на русскую ТГ группу ??


    1. Gymmasssorla Автор
      24.05.2019 17:18
      +1

      Я не против, но зачем? Это всего-лишь перевод)


      1. vr19860507
        24.05.2019 22:37
        +2

        Для лишней популяризации языка.


    1. Gymmasssorla Автор
      25.05.2019 07:39
      +1

      Добавил.


  1. orcy
    24.05.2019 20:19
    +2

    fn foo(x: Box<dyn FnOnce()>) {
        x()
    }


    А раньше как надо было? (*x)()?


    1. Gymmasssorla Автор
      25.05.2019 07:38
      +1

      (*x)() — это сишный синтаксис. Раньше надо было FnBox пользоваться, или же помещать функциональный трейт в Box (что не очень удобно).


      1. orcy
        25.05.2019 20:04
        +1

        Вроде *x для того чтобы достать что-то из бокса — это и растовый синтаксис.


        То есть раньше если у вас оказался Box<dyn FnOnce()> то не было способа вызвать функцию? Это как-то непонятно.


        1. AntonZelenin
          26.05.2019 15:24

          Если я не ошибаюсь, то было так:
          To call a FnOnce closure that is stored in a Box, the closure needs to move itself out of the Box because the closure takes ownership of self when we call it. Rust doesn’t allow us to move a value out of a Box because Rust doesn’t know how big the value inside the Box will be.

          Поэтому нужно было имплементить трейт

          trait FnBox {
              fn call_box(self: Box<Self>);
          }

          который «is allowed to take ownership of a Self value stored in a Box» и вызывать замыкание так
          x.call_box()

          Подробнее можно почитать здесь: doc.rust-lang.org/book/ch20-02-multithreaded.html#implementing-the-execute-method

          А теперь можно просто вызывать x() без всяких заморочек