LINUX.ORG.RU

Rust 1.49

 


2

6

Опубликован релиз 1.49 языка программирования Rust.

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

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

  • Уровень 3. Система поддерживается компилятором, но не предоставляются готовые сборки компилятора и не прогоняются тесты.

  • Уровень 2. Предоставляются готовые сборки компилятора, но не прогоняются тесты

  • Уровень 1. Предоставляются готовые сборки компилятора и проходят все тесты.

Список платформ и уровней поддержки: https://doc.rust-lang.org/stable/rustc/platform-support.html

Новое в релизе 1.49

  • Поддержка 64-bit ARM Linux переведена на уровень 1 (первая система, отличная от систем на x86, получившая поддержку уровня 1)

  • Поддержка 64-bit ARM macOS переведена на уровень 2.

  • Поддержка 64-bit ARM Windows переведена на уровень 2.

  • Добавлена поддержка MIPS32r2 на уровне 3. (используется для микроконтроллеров PIC32)

  • Встроенный тестовый фреймворк теперь выводит консольный вывод, сделанный в другом потоке.

  • Перенесены из Nightly в Stable три функции стандартной библиотеки:

  • Две функции теперь помечены const (доступны на этапе компиляции):

  • Повышены требования к минимальной версии LLVM, теперь это LLVM9 (было LLVM8)

>>> Подробности

★★★★★

Проверено: Shaman007 ()
Последнее исправление: atsym (всего исправлений: 8)

Ответ на: комментарий от hbee

А я люблю рассказывать, что perl это практичный язык (даже в названии указано), пишется быстро, читается легко. И тоже код не показываю.

bread
()
Ответ на: комментарий от vertexua

foo()?;

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

bread
()
Ответ на: комментарий от Im_not_a_robot

высокоуровневые криптографические библиотеки и веб-фреймворки, обертки над api всяких спотифаев, плагины для neovim, куча консольных утилит. Да много всякого разного, надеюсь мне не надо все перечислять?

Вон например, kitty и alacritty, два эмулятора терминала с очень похожими целями. Первый начал пилиться в 2014 году на питоне, второй - через 2 года уже на расте, как раз когда 1.0 вышел.

Lrrr ★★★★★
()
Ответ на: комментарий от hbee

Одно это оправдывает применение чего угодно кроме них

Починил, не благодари.

intelfx ★★★★★
()
Ответ на: комментарий от bonta

для не знающего Раст это выглядит еще более странно чем в Питоне, потому что видя код из листинга ожидаешь от let - что-то вроде объявления объекта. А по факту этот оператор не только объявляет но и не объявляет объекты…

Не уверен, что правильно понял мысль, но let (d,e) вполне можно воспринимать как объявление. В конце-концов ведь тут объявляется две новых переменных.

DarkEld3r ★★★★★
()
Ответ на: комментарий от Siborgium

В расте нельзя описать функцию от произвольного числа переменных – не нужно

Ну это неправда: есть RFC, может когда-то сделают.

в расте ломается RAII из-за отсутствия внятных исключений

Можешь раскрыть мысль?

и конструкторов как таковых

А чем статик функции хуже конструкторов?

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

есть RFC, может когда-то сделают

Не сделают. Либо сделают, сломав имеющуюся модель, как это было с async-ами.

Можешь раскрыть мысль?

А чем статик функции хуже конструкторов?

Статик функции хуже конструкторов потому что они не полиморфны. В С++ я мог бы написать

template <typename T>
auto foo(auto&& ... xs) {
    T a{ xs... };
    // или
    std::vector<T> ts;
    ts.emplace_back(xs...);
}

Но если у меня нет конструктора, мне нужно знать имя конкретной функции, которой нужно передать аргументы. В Расте этот минус (а его уже было бы достаточно) осложняется еще больше. Так, эта функция не может быть перегружена. Более того, я не могу вызвать функцию для произвольного T – мне придется потребовать для этого T: SomeTrait и реализовывать SomeTrait для моих типов. При этом в Расте нет полиморфизма ссылок и функций от произвольного числа аргументов, что еще больше осложняет проблему, я не смогу создать обобщенную функцию SomeTrait::foo(...), что приводит к невозможности воплощения (обобщенных) conditional move и in-place construction.

В случае ошибки конструктор выбрасывает исключение. В расте нет исключений, а вот ошибки инициализации есть. С этим нужно что-то делать – и появились статик функции, возвращающие Result<Self, E>. Но, как видно из написанного выше, статик функции уступают конструкторам практически во всем. Единственный плюс – иногда чуть проще читать код.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Но если у меня нет конструктора, мне нужно знать имя конкретной функции, которой нужно передать аргументы

В подавляющем числе случаев new, иногда билдеры. Да и зачем документация?

Так, эта функция не может быть перегружена

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

Более того, я не могу вызвать функцию для произвольного T – мне придется потребовать для этого T: SomeTrait и реализовывать SomeTrait для моих типов

Можешь, если сам не поставишь констрейнт. Ты не сможешь с ним сделать ничего кроме как переместить, но кроме этого, как правило, в конструкторе ничего и не нужно. А если нужно, то в С++ ты точно также не сможешь сделать с типом того, что для него не реализовано. Так почему бы и не задать необходимые интерфейсы заранее? Опять, иногда это бесит и хочется сказать что-то типа «хрен с тобой, дай мне утиную типизацию», но преимущество в виде человеческих ошибок компиляции тоже есть.

что приводит к невозможности воплощения (обобщенных) conditional move и in-place construction.

Реализуется через замыкания: Foo::with(|| Bar { .. }). На уровне языка, к сожалению, пока еще не гарантируется in-place, но на практике любые копирования выпиливаются в релизе.

С этим нужно что-то делать – и появились статик функции, возвращающие Result<Self, E>.

Снова вкусовщина. Почему это хуже исключений?

Mmmlmlm
()
Ответ на: комментарий от Mmmlmlm

В подавляющем числе случаев new, иногда билдеры. Да и зачем документация?

Можешь, если сам не поставишь констрейнт.

Нет, не могу.

fn foo<T>() -> T {
    T::new()
}
error[E0599]: no function or associated item named `new` found for type parameter `T` in the current scope
 --> main.rs:4:8
  |
4 |     T::new()
  |        ^^^ function or associated item not found in `T`

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

А если нужно, то в С++ ты точно также не сможешь сделать с типом того, что для него не реализовано

Пусть у меня есть структурка вида

struct S { }

impl S {
    pub fn new() -> Self { S{} }
}

Для нее new реализован. Функция выше все еще не компилируется, даже если я напишу где-нибудь в main

let s: S = foo();

Реализуется через замыкания: Foo::with(|| Bar { .. }).

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

Почему это хуже исключений?

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

Я говорю о том, почему статические функции хуже конструктора, и почему это многократно усугубляется отсутствием исключений в Расте.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Mmmlmlm

Реализуется через замыкания: Foo::with(|| Bar { .. }). На уровне языка, к сожалению, пока еще не гарантируется in-place, но на практике любые копирования выпиливаются в релизе.

И каким образом результат замыкания засунуть в вектор без копирования?

technic93
()
Ответ на: комментарий от Siborgium

что приводит к невозможности воплощения (обобщенных) conditional move и in-place construction.

С инплейс конструктором хороший пример, что такое кондишинал мув не очень понятно. Есть еще примеры инетересные? С другой стороны посколько в расте мув семантика по умолчанию компилятор может сделать copy elision аналогично плюсам в случае с возвратом из функции. Возможно он уже так делает, но логика по идее такая же.

technic93
()
Ответ на: комментарий от technic93

С вектором это просто. Если оформить в виде трейта, то вообще будет выглядеть как родной метод.

https://godbolt.org/z/vK84Tq

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

Mmmlmlm
()
Ответ на: комментарий от Mmmlmlm

С вектором это просто.

Ну это не inplace по сути. Я вообще не вижу отличие между таким подходом и push. Этот пример нужен только если объект создается опционально, типа vec.push_if_empty(|| Bar::new()).

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

technic93
()
Последнее исправление: technic93 (всего исправлений: 2)
Ответ на: комментарий от Siborgium

Статик функции хуже конструкторов потому что они не полиморфны. В С++ я мог бы написать

В С++ ты мог бы сделать и полиморфную статик функцию. Если договорится что это теперь везде используется вместо конструкторов.

technic93
()
Ответ на: комментарий от technic93

Возможно он уже так делает

Да. Это называется return value optimisation (RVO).

Есть еще примеры инетересные?

Пусть есть вектор. Для него можно реализовать следующую функцию:

auto try_emplace_noalloc(auto& vec, auto&& ... xs) {
    if (vec.capacity() > vec.size()) {
        vec.emplace_back(std::forward<decltype(xs)>(xs)...);
        return true;
    }
    return false;
}

ее можно вызывать как угодно (пока определены соответствующие конструкторы); она будет конструировать объект in-place:

T x;
// копирование
try_emplace_noalloc(vec, x);
// default-конструктор
try_emplace_noalloc(vec);
// move-конструктор
try_emplace_noalloc(vec, T{});
// конструктор от каких-то трех аргументов
try_emplace_noalloc(vec, foo(), bar(), baz);
// с проверкой
if (!try_emplace_noalloc(vec, std::move(x))) {
    // конструктор не был вызван, владение не было передано в вектор
    std::cerr << "failed to push x = { " << x << " }, vector is full\n";
}
Siborgium ★★★★★
()
Ответ на: комментарий от technic93

В С++ ты мог бы сделать и полиморфную статик функцию.

Да, но ей все равно придется форвардить аргументы конструктору – или конструировать по умолчанию, а затем вручную конструировать отдельные поля из аргументов. Уже тут начнутся проблемы – не все, что MoveConstructible, является MoveAssignable, и так далее.

Проблема возврата Result все равно остается и в общем случае не решается, придется либо затирать типы, либо использовать развесистые variant’ы с ошибками.

Наконец, избегать исключений ради избегания исключений смысла нет.

Если договорится что это теперь везде используется вместо конструкторов.

В Gtkmm так и делают (не везде, впрочем), и лазить по докам в поисках нужной статической функции довольно неприятно. Справедливости ради, полиморфизмом там и не пахнет.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 4)
Ответ на: комментарий от Siborgium

Да. Это называется return value optimisation (RVO).

Я в курсе. Я чуть другое имел ввиду, что мешает сделать автоматически оптимизацию аналогичную в RVO для метода когда мы кладем в вектор? Т.е. любой push_back(T{a,b,c}) как бы преобразовать в emplace_back(a,b,c). Если мы знаем в расте что все по умолчанию мувается, то компилятор в простых случаях может найти источник где создалось T которое ляжет в вектор и аллоцировать его сразу в векторе. Правда это не сработает когда есть бранчинг и с ABI это не понятно как подружить. Тогда надо упарываться с трейтом типа ConstructableFrom<A,B,C> что действительно не удобно, особенно без вариадик темплейтов.

technic93
()
Ответ на: комментарий от Siborgium

Наконец, избегать исключений ради избегания исключений смысла нет.

Ага, а если захочется использовать fallible конструктор в nothrow контексте, то достаточно дописать создание зомби-объекта.

red75prim ★★★
()
Ответ на: комментарий от red75prim

fallible конструктор

в nothrow контексте

Вам ехать или шашечки?

Пишите noexcept(noexcept(T{})), и ваши волосы будут мягкими и шелковистыми.


Ага, а если захочется использовать fallible конструктор в nothrow контексте

То достаточно

auto quax() noexcept {
    struct ThrownException{};
    struct Throws {
        Throws() { throw ThrownException{}; }
    };
    try {
        Throws throws;
    } catch (const ThrownException&) {
        std::cerr << "look ma, nothrow!\n";
    }
}
Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

fallible конструктор в nothrow контексте

Вам ехать или шашечки?

Да, давно плюсы не вспоминал. Смешал noexcept и -fno-exceptions. Компиляция С++ в режиме без поддержки исключений сейчас, наверно, исключение?

В Расте этот минус (а его уже было бы достаточно) осложняется еще больше. Так, эта функция не может быть перегружена.

Это просто попытка использовать подходы C++ в расте, который предлагает другие средства для решения таких задач. Перегрузка (то есть ad-hoc полиморфизм) - это в данном случае сахар для работы с разными функциями, названными одним именем. Да, придётся назвать разные функции по разному.

Более того, я не могу вызвать функцию для произвольного T

Вообще-то можно. Нужно только назвать вещи своими именами: конструктор с параметрами - это замыкание, которое возвращает Result<T, E>.

noexcept конструктор, соответственно возвращает Result<T, !> (! - это never или bottom тип)

fn foo<T, E, F>(create: F) -> Result<(), E>
where
    F: FnOnce() -> Result<T, E>
{
    let a = create()?;
    // или
    let mut ts = vec![];
    ts.push(create()?);
    Ok(())
}

Реализуется через замыкания: Foo::with(|| Bar { .. }).

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

Реализуется. Перемещать или нет аргументы конструктора в замыкание решает тот, кто пишет замыкание.

|| Bar::new(&a) - а не перемещается

move || Bar::new(a) - перемещается

|| Bar::new(a.clone()) - клонируется

Перемещается, если конструктор был вызван:

let mut a = Some(a);
|| Bar::new(a.take().expect("bug"))
red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 2)
Ответ на: комментарий от red75prim

Да, придётся назвать разные функции по разному.

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

который предлагает другие средства для решения таких задач

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

конструктор с параметрами - это замыкание

Нет конечно. Без исключений нет никакой возможности вернуть ошибку из конструктора, а со статическими функциями вместо конструктора у вас не будет полиморфизма. Точнее, его в принципе не будет без перегрузки, вам придется все сводить к интерфейсам (трейтам). Вы просто предлагаете перенести задачу конструирования валидного T на плечи вызывающего кода, вызываемому же коду придется осуществлять дополнительную проверку на ошибки – она уже была осуществлена в замыкании, либо в теле конструктора. С тем же успехом можно переписать все это дело в

if v.capacity() > v.len() {
    v.push(Bar::new(a));
    // ...
} else {
    // ...
}

Реализуется. Перемещать или нет аргументы конструктора в замыкание решает тот, кто пишет замыкание.

Скажем так, я признаю, что с приседаниями в расте все же можно эмулировать conditional move, но считаю это малоосмысленным. Конструировать просто Bar неинтересно – задача же конструирования произвольного T все еще не решена. Тем более in-place. move по умолчанию это все еще move, пусть и дешевый. Когда в дело включается Pin, все становится еще интереснее.

Перемещать или нет аргументы конструктора в замыкание решает тот, кто пишет замыкание.

Вы привели три варианта, но из них только 1 осуществляет передачу владения куда-то. Кстати, реализовать все сразу я не смогу – Bar::new мономорфен по ссылке, и мне придется использовать, например, Bar::new для move/clone и Bar::from_ref для ссылки.

Дополню про exceptions: в расте очень много где используется Result<T, Box<dyn Error>>. На этом моменте любая выгода Result вместо исключения пропадает, а вот многочисленные проблемы (необходимость раскручивать стек руками, unwrap’ы, «грязный» интерфейс) остаются.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Нет конечно. Без исключений нет никакой возможности вернуть ошибку из конструктора

Без исключений нет возможности вернуть ошибку из конструкторов C++. Вернуть ошибку из замыкания - без проблем.

Нет, он не предлагает никаких средств для решения такой задачи.

Какой именно задачи? Сделать перегрузку как в С++? Это не задача, а способ решения некоторых задач, выбранный в C++.

вызываемому же коду придется осуществлять дополнительную проверку на ошибки

Если замыкание возвращает Result<T, !>, то обработка ошибок будет выброшена. Да и «дополнительная проверка на ошибки» вполне может быть оптимизирована так, как это делается для исключений - с использованием side tables. Технически это сложнее из-за того, что обработку ошибок контролирует программист, но гарантировать генерацию side tables при использовании стандартного способа обработки с помощью ? вполне возможно.

задача же конструирования произвольного T все еще не решена. Тем более in-place.

Что значит не решена? Замыкание, реализующее Fn()->T, именно это и делает. in-place конструирование предполагается решить с помощью гарантированного copy-elision. Это проще чем в C++ из-за move семантики без сайд-эффектов.

Когда в дело включается Pin, все становится еще интереснее.

Не становится. Сам тип Pin<PtrT> - перемещаемый. Он просто предоставляет интерфейс для доступа к неперемещаемым значениям типа T, лежащим за указателем PtrT.

Кстати, реализовать все сразу я не смогу – Bar::new мономорфен по ссылке.

Зависит от сигнатуры. fn new<T: Into<Foo>>(_: T) -> Bar может принимать и ссылки (если реализовать From<&Foo> for Foo)

в расте очень много где используется Result<T, Box<dyn Error>>

В библиотеках обычно используются конкретные типы ошибок. Ошибки со стёртым типом чаще встречаются в приложениях, когда выяснили, что сделать уже ничего не можем и остаётся пробросить ошибку выше (для логирования и т.п.). В этом случае «ручная раскрутка стека» сводится к добавлению ?.

unwrap’ы - это не такой уж и частый случай за пределами hello world’ов. Да, бывает, что выразить что-то на уровне типов нельзя (как в моём примере с conditional-move выше) или сложно. Например, у меня около 250 unwrap’ов на 60000 строк кода и из них штук 100 в тестах.

«грязный» интерфейс

For whom how. Видеть контракт функции в сигнатуре иногда полезнее, чем в документации.

red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 1)
Ответ на: комментарий от red75prim

Без исключений нет возможности вернуть ошибку из конструкторов C++. Вернуть ошибку из замыкания - без проблем.

Что значит не решена?

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

Какой именно задачи? Сделать перегрузку как в С++?

С++ не идеален. Получится лучше – буду рад посмотреть. Для упрощения задачи можно начать со static if.

Зависит от сигнатуры. fn new<T: Into>(_: T) -> Bar может принимать и ссылки (если реализовать From<&Foo> for Foo)

Нет, семантически это мономорфный код, принимающий void* и vtable над этим void*. Ссылка там или объект – неважно, вы об этом никогда не узнаете, для вас это абсолютно непрозрачно. Да, vtable уйдет во время оптимизации, но тип затерт уже сейчас.

В этом случае «ручная раскрутка стека» сводится к добавлению ?.

Абсолютно неважно, к добавлению чего она сводится для юзера. Каждый ? это бранч с возвратом значения. В каждом бранче ошибка либо возвращается как есть, либо заворачивается в еще один слой. Это буквально ручная раскрутка стека.

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

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

Видеть контракт функции в сигнатуре иногда полезнее, чем в документации.

Никакого контракта в сигнатуре нет. Вон сигнатура foo<T, E, F>(create: F) -> Result<(), E> where F: FnOnce() -> Result<T, E>. Где в ней выбрасываемое Vec::push исключение, которое в Расте упорно называют «panic»? Его нет. Ни в сигнатуре Vec::push, ни в сигнатуре foo. Вы можете написать catch (aka «panic_handler») для него. О каком контракте идет речь?

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Никакого контракта в сигнатуре нет. Вон сигнатура foo<T, E, F>(create: F) -> Result<(), E> where F: FnOnce() -> Result<T, E>. Где в ней выбрасываемое Vec::push исключение, которое в Расте упорно называют «panic»? Его нет. Ни в сигнатуре Vec::push, ни в сигнатуре foo. Вы можете написать catch (aka «panic_handler») для него. О каком контракте идет речь?

Ну вот даже Haskell падает с Out of memory совершенно в чистых сигнатурах. Если учитывать такое, то получается абсолютно каждая функция где происходит аллокация должна возвращать Result. Да, это было бы 100% корректно, но ради эргономики решили этого не делать.

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

vertexua ★★★★★
()
Последнее исправление: vertexua (всего исправлений: 1)
Ответ на: комментарий от red75prim

Если замыкание возвращает Result<T, !>, то обработка ошибок будет выброшена

#![feature(never_type)]   // Фууууу, nightly, experimental feature

fn f() -> Result<i32, !> {
    Err(1)
}

fn main() {
    println!("{:?}", f());
}
error[E0308]: mismatched types
 --> src/main.rs:4:9
  |
4 |     Err(1)
  |         ^ expected `!`, found integer
  |
  = note: expected type `!`
             found type `{integer}`

error: aborting due to previous error

Ошибка на уровне nightly, что я могу сказать

vertexua ★★★★★
()
Ответ на: комментарий от Siborgium

Замыкание выше не подходит, в нем объект конструируете вы, а не функция.

Э? В C++ объект конструируется конструктором, а не функцией, которая вызывает конструктор. В расте пишем явно каким образом хотим сконструировать, в С++ выбираем через overload resolution. Это опять «хочу как в C++».

Нет, семантически это мономорфный код

Семантически - это параметрический полиморфизм. Никаких vtable там нет. vtable появляется только в trait-objects (dyn Trait).

но тип затерт уже сейчас

Параметрический полиморфизм и затирание типов - это совершенно разные вещи.

fn foo<T>(val: T) -> T {
   val
}

let type_is_not_erased = foo(1u32);

Тип у type_is_not_erased будет u32. Никуда он не делся.

О каком контракте идет речь?

Если create вернёт ошибку, то foo возвратит ошибку. Единственный способ, которым мы можем получить значение типа E внутри foo, - это взять его из результата выполнения create.

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

red75prim ★★★
()
Ответ на: комментарий от vertexua

// Фууууу, nightly, experimental feature

В стейбле это std::convert::Infallible. Я писал ! для краткости. Ошибку можно было бы и лучше написать, но и так понятно. Err(loop{}), кстати, скомпилируется.

red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Каждый ? это бранч с возвратом значения. В каждом бранче ошибка либо возвращается как есть, либо заворачивается в еще один слой. Это буквально ручная раскрутка стека.

Я думаю можно скомпилировать это по разному. Если функция просто пробрасывает все наверх через ?, то ничего не мешает вынести бранчинг за скобки и сделать его один раз в точки вызова этой функции. Есть обсуждение про это дело в трекере. Главное что оно отделено семантически, т.е. у нас не просто возврат int result=-1 как в си, а Result<T,E>. Значит у компилятора даже больше информации чем в С++. Если я все match по Result заменю на try/catch то поидее программа будет работать аналогично исключениям из других языков. Значит ее можно скомпилировать так чтобы error-free сценарий был быстрый.

technic93
()
Ответ на: комментарий от red75prim

Э?

Выражусь иначе, пусть foo вызывает конструктор T, а не переданное ей замыкание.

Никаких vtable там нет. vtable появляется только в trait-objects (dyn Trait).

Семантически там именно vtable. На сишке я могу реализовать все то же самое, разве копипасты потребуется чуть больше.

Тип у type_is_not_erased будет u32. Никуда он не делся.

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

Раз тип никуда не девается, вы, наверное, можете мне вывести его куда-то? Напишите мне аналог std::is_same. Чтобы если T = u32, то функция возвращала val + 1, иначе val. Или, раз тип никуда не девается, то вы для произвольного T можете вызвать T::new(), или, по крайней мере, узнать, есть ли такая статическая функция у этого T?

Паники - это нарушения контрактов не описанных в сигнатуре.

Нет, паники – это банальные исключения, которые появляются тогда, когда в Расте что-то не получается реализовать нормально.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 2)
Ответ на: комментарий от Siborgium

Посмотрите, всё-таки, что такое типобезопасный параметрический полиморфизм и чем он отличается от стирания типов.

red75prim ★★★
()
Ответ на: комментарий от technic93

Значит у компилятора даже больше информации чем в С++

Нет, у него не больше информации, чем в С++, потому что ничто не мешает написать Result для С++. Более того, есть несколько пропозалов на эту тему (std::expected) и Boost.Outcome.

Проблема вообще не в этом, проблема в том, что эту раскрутку стека программист пишет руками и превращает любую функцию в слабочитаемое месиво. С исключениями это происходит автоматически, и, что самое важное, они действительно зерокост для error-free сценария.

Их проблема в их текущем виде заключается в том, что они используют кучу и rtti. Хотя обе эти проблемы обходятся через некоторые приседания, именно эти проблемы приводят к их малой популярности, а не какие-то там контракты/сигнатуры. Решение этих проблем предлагает Саттер в своем пропозале.

Siborgium ★★★★★
()
Ответ на: комментарий от red75prim

Я отлично понимаю, что это. Я сам немало писал на расте и хаскеле, тыкал идрис 1/2. Я просто рекомендую вам сесть и написать is_same. Если не получится, спросите себя о том, почему это не получилось сделать. Если вам известен тип – почему вы не можете воспользоваться информацией о нем?

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Потому что параметрический полиморфизм не позволяет иметь разные реализации для разных типов. Bounded polymorphism в какой-то мере позволяет.

fn foo<T: Any>(val: T) -> T {
  if TypeId::of::<u32>() == val.type_id() {
    // Safe. We know than T==u32. It's ok to copy bits
    unsafe {
        let val = transmute_copy::<T, u32>(&val) + 1;
        transmute_copy(&val)
    }
  } else {
    val
  }
}

После мономорфизации условие и transmute_copy будут удалены оптимизатором без всяких constraint propagation, которые потребуются для оптимизации версии с vtable. Условие в мономорфизированной версии будет константным true или false, transmute_copy::<u32, u32>() - тривиальным копированием.

Bounded polymorphism со специализацией позволит это сделать без unsafe.

red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 2)
Ответ на: комментарий от Siborgium

Нужен более умный паттерн матчинг. В плюсах/хаскеле он в каком то смысле лучше. Первый запрос в гугле выдал такое в найтли.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=15ad676b8935eac7c95a809d4bcbc7be

Я и сам когда-то игрался с таким.

technic93
()

Есть ещё такое https://github.com/willcrichton/tyrade

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

technic93
()
Ответ на: комментарий от red75prim

Bounded polymorphism со специализацией позволит это сделать без unsafe.

Это просто попытка использовать подходы C++ в расте, который предлагает другие средства для решения таких задач. Перегрузка (то есть ad-hoc полиморфизм)

Вы буквально говорите, что перегрузка позволит делать такие вещи без unsafe. Чуть выше вы говорите, что перегрузка – это С++-way, и раст это делает иначе.

После мономорфизации условие и transmute_copy будут удалены оптимизатором

…О чем я и писал выше.

которые потребуются для оптимизации версии с vtable.

«Семантически» – знаете значение слова?

А теперь скажите, как мне получить это значение в компайл-тайме? Почему мне нужно использовать RTTI, чтобы сказать, когда два типа, известных в компайл-тайме, одинаковы?

Siborgium ★★★★★
()
Ответ на: комментарий от technic93

#feature(specialization)

Впрочем, это уже больше похоже на нормальный заход. По крайней мере, тут смогли в компайл-тайм. Обмазавшись перегрузкой по трейту, которая же «С++-way», как чуть выше утверждалось.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Вы буквально говорите, что перегрузка позволит делать такие вещи без unsafe. Чуть выше вы говорите, что перегрузка – это С++-way, и раст это делает иначе.

C++-way - это ad hoc полиморфизм, сначала подстановка конкретного типа в темплейт с выбором подходящей реализации с помощью sfinae (про концепты пока не говорим), потом проверка корректности полученного кода и кодогенерация.

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

Знаете, что у слова «семантика» есть несколько значений? Операционная семантика может совпадать, а денотационная семантика сильно различаться.

Почему мне нужно использовать RTTI

Facepalm. foo<T: Any>() - это не RunTime Type Information. Я могу написать TypeId::of::<T>() вместо val.type_id(), второе просто короче. Операционную семантику это не изменит: никаких объектов типа «тип» в рантайме в этом случае не создаётся. Не потому что они были удалены оптимизатором, а потому что они не существовали в природе. TypeId::of::<T>() и <T as Any>::type_id(&self) мономорфизируются в набор разных функций с телами вида {return TYPE_ID_U32;}, {return TYPE_ID_STRING;}.

foo(&dyn Any) - вот это RTTI, здесь используется рантаймовый объект, содержащий информацию о своём стёртом типе.

red75prim ★★★
()
Ответ на: комментарий от red75prim

с помощью sfinae (про концепты пока не говорим)

Только про концепты и нужно говорить.

со специализацией

Скажите, а что происходит, когда что-то удовлетворяет нескольким специализациям сразу?

это не RunTime Type Information

Это как раз RTTI, который для статически известных типов вычисляется во время оптимизации.

Операционную семантику это не изменит

Я ничего про нее и не говорил.

Давайте понагляднее пример. Я хочу статический массив типа T и длины 4 для всех T, кроме u32, и длины 8 для u32. Как мне такое сделать через type_id?

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Скажите, а что происходит, когда что-то удовлетворяет нескольким специализациям сразу?

Берется более «узкая».

technic93
()
Ответ на: комментарий от Siborgium

Скажите, а что происходит, когда что-то удовлетворяет нескольким специализациям сразу?

Используется самая «специализированная» специализация. Например: сначала специализация для Vec<u32> потом Vec<T: Clone + Copy> потом Vec<T: Clone> потом Vec<T> потом T. При наличии несравнимых специализаций будет ошибка компиляции.

Это как раз RTTI, который для статически известных типов вычисляется во время оптимизации.

О как. А разработчики собираются сделать эти функции константными. Будет compile-time runtime type information. Хех.

Я хочу статический массив типа T и длины 4 для всех T, кроме u32, и длины 8 для u32. Как мне такое сделать через type_id?

После реализации константного TypeId::of() так:

const fn foo_size<T: Any>() -> usize {
    if TypeId::of::<T>() == TypeId::of::<u32>() {
        8
    } else {
        4
    }
}

fn bar<T: Any + Copy>(v: T) -> [T; foo_size::<T>()] {
    [v; foo_size::<T>()]
}

Сейчас в найтли можно сделать так: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=2775f0d1005fc71a680a2a8b33689a2b

red75prim ★★★
()
Ответ на: комментарий от red75prim

А разработчики собираются сделать эти функции константными. Будет compile-time runtime type information. Хех.

Конечно, возьмут фичу из LLVM9, которую делали для плюсов и переиспользуют. Всё правильно :)

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1327r1.html

fsb4000 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.