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)

Ответ на: комментарий от red75prim
                      А это что такое?   ^^^

А это параметр пак, ака вариадик. Задачи итерирования по нему он решает примерно никак, и итератором не является. Даже определение итератора на него со всеми возможными оговорками не натягивается.

Ну и что здесь нельзя скомпилировать так, чтобы отмеченные итерации по типам происходили в рантайме? Компилировать вызов такой функции, естественно, надо с преобразованием списка параметров в список TypeTaggedVar. В качестве тэга можно использовать typeid(T) из RTTI.

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

вы наверно имели в виду

Нет, я не имел в виду.

Надеюсь такая формулировка ясно показывает, что это невозможно?

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

семантически они могут вести себя как полиморфные.

void foo(struct tagged* a) {
    if (a->type == INT_TYPE) { } else if (a->type == STR_TYPE) { }
}

вот это полиморфизм! полиморфный так полиморфный

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

Блин, ну вы хоть один раз можете написать правильно?

Я написал все правильно. Там две сигнатуры функции, обе принимают разные туплы, но в одном случае по вашей логике коэрсии не должно быть, а во втором – должна. Вы как предлагаете в таком случае поступать? Все тем же вашим гениальным ABI? Идеальное решение: для того, чтобы не делать нормально, лучше насрать специальным правилом коэрсии для одного частного случая и ввести ABI (кстати вы так и не рассказали, зачем же он нужен, если все туплы и так автокоэрсятся), а все ради того, чтобы не делать по людски, и это все при том, что вариадики для раста делаются.

Смотри, код то же самое делает

«Дерьмом можно насытиться – так давайте жрать дерьмо вместо еды!». Не делает он то же самое, он позволяет достичь чего-то отдаленно похожего ценой рекурсивной дрисни. Где тут человекочитаемый код? Где тут юзабилити? Там RazrFalcon что-то про поддерживаемость сообщал, где оно все?

олучаем итератор, возвращающий тупл, содержащий эти типы.

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

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

А вы считаете это чем-то хорошим?

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

Ну и вообще тут проблема больше в том, что с одной стороны тянуть нельзя, с другой - нет времени и сил всё сразу сделать. С макросами (которые macro_rules) такая же история была: там тоже хватает костылей и гигиена в некоторых случаях не работает. На тот момент был выбор релизиться без макросов или с такими. Я считаю, что выбор был сделан правильный и на практике макросы и асинки весьма полезны. Когда-нибудь допилят и станет совсем хорошо будут жаловаться на что-нибудь другое.

Нет, не существует такого случая.

Ну если фантазировать, то предположим, что у меня есть необходимость создавать 100500 библиотечных объектов в секунду и в 90% случаев вылетает исключение, обработка которых является узким местом.

Я это несерьёзно, конечно. Но претензии о невозможности устроить conditional move для меня выглядят примерно так. Может чего-то не понимаю.

Где польза?

Ну это будет разговор «шаблоны против дженериков». Вряд ли мы друг другу что-то новое скажем.

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

Есть ли языки с которыми не надо бороться? (:

Когда приходится учитывать кучу нюансов, чтобы написать корректный с точки зрения exception safety код - это борьба с языком или нет? А когда в деструкторе вынужден глотать исключения? Или языки со сборкой мусора, где нет деструкторов и приходится обмазываться try-with-resources? Или хаскель, где ленивость иногда стреляет в ногу?

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

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

В их отсутствие упирается вообще все.

Ну да, ну да. Как же на языках без такой возможности вообще что-то пишут?!

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

Но даже если бы они появились, затирание типов не позволит писать ничего полезного.

Приведённый пример с конструированием произвольных объектов вполне возможно будет сделать.

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

что фьючи боксятся

Они не только боксятся, они еще и Pinned.

Я это несерьёзно, конечно.

Да нет, вы 100% правы. В таких случаях действительно приходится мучаться по старинке. Пропозал Саттера по static exceptions, когда (надеюсь, не если) его примут, исправит эту проблему.

Есть ли языки с которыми не надо бороться? (:

Где-то больше, где-то меньше. В Расте много ограничений, с которыми приходится именно бороться, в этом и проблема.

А в остальном согласен с вами.

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

Как же на языках без такой возможности вообще что-то пишут?!

Да в том-то и дело, что никак не пишут. Я бы с радостью слез с плюсов на какой-то другой язык, но нет ничего. Нигде нет возможности сделать банальный emplace_back. В кресты есть пропозалы на столько хороших и реально полезных вещей, но их не принимают, а тянут, тянут, тянут… Чувствую, придется еще 10 лет ждать.

Ну серьёзно, можешь привести пример, где без этого правда жить нельзя?

Тот самый emplace_back.

Приведённый пример с конструированием произвольных объектов вполне возможно будет сделать.

Ну вот давай разбираться. Пусть у нас есть структурка

struct S<A, B> { ... }

impl<A, B: SomeTrait> S<A, B> {
    pub fn new<C: OtherTrait>(a: A, b: B, c: C) -> Self { ... }
}

и мы теперь хотим вызвать в emplace_back эту функцию, и… у нас ломается модель. Для конструирования структуры необходимо, чтобы переданные args удовлетворяли сигнатуре new, но у нас нет способа описать это в вариадике. Если мы не будем описывать, то у нас ломается одна из базовых идей раста – прописывание всех требуемых трейтов на верхнем уровне.

fn my_push_back<T>(vec: &mut Vec<T> , args...: Args...) {
    vec.push_back(T::new(args...));
}

Вопрос о том, почему в реальности еще и с T::new будут проблемы мы затрагивали ранее, пока просто проигнорируем это.

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

А это параметр пак, ака вариадик. Задачи итерирования по нему он решает примерно никак, и итератором не является. Даже определение итератора на него со всеми возможными оговорками не натягивается.

I find your lack of abstraction disturbing.

Что эта конструкция делает? Итерируется по списку типов и повторяет кусок кода для каждого типа. Итераторы не только в namespace std бывают.

Сильно похоже на примитивную макроподстановку, но ОК. Пусть будет parameter pack, который ничего не итерирует.

Отдельно мне реализуйте отображение из List в список параметров функции – или признайте, что сморозили чушь.

ОК возьмём хрестоматийную сумму произвольного числа аргументов.

double sum(double t) {
    return t/0.0;
}

template <typename T>
T sum(T t) {
  return t;
}

// Обратите внимание на рекурсивную дрисню
template <typename T, typename... Rest>
T sum(T t, Rest... rest) {
  return t + sum(rest...);
}

double foo_double() {
    return sum(0., 1.);
}

int foo_int() {
    return sum(1, 2);
}

Конвертируем в динамику. Псевдокод

// sum_double - замангленное имя. Компилятор знает, что эта функция получает только
// единственный дабл, поэтому проверок нет. Коэрсим первый элемент списка к даблу
TypeTaggedVar sum_double(List<TypeTaggedVar> t) {
  // TypeTaggedVar берёт typeid параметра, сохраняет его в тег и скрывает значение за *void
  return TypeTaggedVar{ t.first().coerce_to_double()/0.0 };
}

// sum_T - замангленное имя
TypeTaggedVar sum_T(List<TypeTaggedVar> t) {
  // Просто возвращаем единственный параметр
  return t.first();
}

TypeTaggedVar sum_T_Rest(List<TypeTaggedVar> t) {
  auto first = t.first();
  auto rest = t.rest();
  // Операция + диспетчится динамически по тегам из first и результата sum(rest)
  return first + sum(rest);
}

TypeTaggedVar sum(List<TypeTaggedVar> t) {
  // здесь компилятор генерирует рантаймовое разрешение оверлоадов
  if t.size() == 1 && t.first().type() == typeid(double) {
     return sum_double(t);
  } else if t.size() == 1 {
     return sum_T(t);
  } else {
    return sum_T_Rest(t);
  }
}

TypeTaggedVar foo_double() {
  return sum(List{TypeTaggedVar{ 0. }, TypeTaggedVar { 1. } });
}

TypeTaggedVar foo_int() {
  return sum(List{TypeTaggedVar{ 1 }, TypeTaggedVar { 2 } });
}
red75prim ★★★
()
Ответ на: комментарий от Siborgium

Они не только боксятся, они еще и Pinned.

И почему это плохо?

В Расте много ограничений, с которыми приходится именно бороться, в этом и проблема.

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

P.S. Предпочёл бы на ты.

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

Тот самый emplace_back.

Уже обсуждали. emplace_back в расте планируется сделать с помощью гарантированного copy elision. Создание произвольных объектов выполняется замыканиями, которые тащат с собой все нужные параметры.

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

Еще и предполагает трогание кучи, что далеко не всегда вариант.

Боксить не обязательно.

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

Я прошу вас реализовать хотя бы какое-то подобие вариадика

Мне казалось единственная причина существования вариадиков - это необходимость запилить убогий std::variant вместо человеческого ADT. Но тут как говорится: когда в руках молоток - всё вокруг похоже на гвоздь.

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

Только из-за одной системы типов это уже сложно.

Дык, система типов - это наоборот преимущество.

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

Тот самый emplace_back.

Но без emplace_back жить можно. (:

К слову, у меня (тоже?) был период активного языко-страдания, когда постоянно смотрел на разные языки и искал там интересные возможности, но ни один не мог сравниться с «великими и ужасными» плюсами. Как я сейчас понимаю, во многом из-за того, что воспринимал как раз через призму С++. Сейчас уже спокойнее к этому отношусь, но по сторонам стараюсь поглядывать.

Ну вот давай разбираться.

Так ведь если в раст добавят вариадики, то никто не мешает добавить заодно трейт вроде такого:

trait Construct<Args...> {
    fn construct(args...) -> Self;
}

И тогда можно будет сделать вот так:

fn my_push_back<T: Construct<Args...>>(vec: &mut Vec<T>, args...: Args...) {
    vec.push_back(T::construct(args...));
}
DarkEld3r ★★★★★
()
Ответ на: комментарий от Siborgium

На каком из компиляторов,

Один из двух, gcc/clang свежий на тот момент (год/полтора назад), уже не помню.

Можно ли в лямбду добавить gnu::always_inline?

Да.

Может я напоролся на баг когда это не работало, а может просто не осилил что то.

При чем тут гарантии на инлайн лямбд?

Там код через лямбды, а мы хотим такое.

auto ia = a.begin();
auto ib = b.begin();
for(; ia != a.end() && ib != b.end(); ++ia, ++ib) {}

Я понимаю что тут на практике сработает отлично, но осадочек остался :)

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

Шаблоны в С++ не типизированы.

Ты здесь видишь полиморфизм? Нет, потому как его нет. Далее вот это f преобразуется в шаблон, в котором уже есть typename и инстанцируется.

Никаких иных возможностей реализовать статический полиморфизм не существует. Только template. Это аксиома. Генерик-говно в говнорасте как было мономорфным, так и осталось мономорфным после компиляции.

По поводу типизации. Типизация нужна потому, что по-сути f<T: X>(x: T) - эквивалентно, полностью, f(x: X). И чтобы вот у этого x был какой-то тип, у которого можно получать свойства - там и нужна типизация, дополнительная. Никаких других причин нет.

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

Зачем ты мне это пишешь? Я где-то писал про полиморфизм? Как это относится к моему сообщению?

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

Что эта конструкция делает? Итерируется по списку типов и повторяет кусок кода для каждого типа. Итераторы не только в namespace std бывают.

Она ни по каким спискам не итерируется, она и есть список типов/значений.

// Обратите внимание на рекурсивную дрисню
template <typename T, typename... Rest>
T sum(T t, Rest... rest) {
  return t + sum(rest...);
}

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

Конвертируем в динамику. Псевдокод

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

auto f(auto ... args); <-- вперед, действуй, пиши мне вызов f от List<TypeTaggedVar>

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

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

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

трейт вроде такого

Собственно, это единственный более-менее рабочий вариант решения этой проблемы. Его либо придется реализовывать на уровне компилятора (наподобие других «магических» трейтов типа Sized/Send), либо прописывать руками раз за разом. Опять же, что делать с ошибками? Придется делать Construct/TryConstruct или же марать код с Result<T, !> – зато код дублировать не придется.

Проблема в том, что применение вариадиков не ограничивается конструированием произвольных объектов, одного такого трейта не хватит. Вариадики особо никому не были нужны до С++17, с ними было очень сложно работать. С С++17 с появлением возможности свертки и применения произвольных трансформаций жить стало гораздо проще. На каждую возможность придется вводить по трейту – ведь, например, для суммирования придется вводить трейт Add<A...>, в котором мучаться с выведением итогового типа попарно.

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

Нет, variant вообще не замена ADT в смысле МЛ-подобных языков и не предназначался ей быть.

struct Animal { virtual void bark() = 0; }
struct AnotherDog: Animal { void bark() override { } };
struct Dog: Animal { void bark() override { } };

struct ArtificialBarker { void bark() { } };

constexpr auto bark(auto&& barkable) {
    return std::visit([](auto&& b) { b.bark(); }, barkable);
}

int main() {
    std::variant<Dog, AnotherDog, ArtificialBarker> variant = // ...
    bark(variant);
}

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

когда в руках молоток - всё вокруг похоже на гвоздь.

Где Zip?

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

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

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

Простейший пример применения.

Ну и зачем переизобретать дин. полиморфизм? Экономия на аллокациях?

Я всё не могу понять как эта страхолюдина попала в std.

Самое забавное, что C++ как язык не так уж и плох. Хуже раста, естественно, но не убогая сишка. Но C++ std - это какой-то стыд и позор. Всё что можно было сделать через одно место - сделали ещё хуже. Всё жду когда кто-то запилит нормальную std на C++20 с модулями и концептами. Аля QtCore. А то мне до сих пор снятся кошмары о GNU C++ std. В то время как в Rust std простой и понятный код. Даже с комментариями, тестами и документацией! И такое бывает!

Где Zip?

Ненужно (с)

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

Ну и зачем переизобретать дин. полиморфизм?

Обратите внимание на третий тип.

Я всё не могу понять как эта страхолюдина попала в std.

Там, на самом деле, два примера. Первый уродливый, но для него не нужен вспомогательный тип. Второй – каноничный. Если выкинуть первый, а хелпер вынести в хэдер с другими хелперами, останется чистое и красивое

    for (auto& v: vec) {
        // 4. another type-matching visitor: a class with 3 overloaded operator()'s
        std::visit(overloaded {
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
        }, v);
    }
Siborgium ★★★★★
()
Ответ на: комментарий от RazrFalcon

В то время как в Rust std простой и понятный код

В Rust std простой и понятный код зачастую за счет вынесения имплементации в другой модуль – как, например, для хэшмапы.

А вообще да, std – древнее говно, потому что нужна совместимость с другим древним говном. Раст такой ноши не испытывает.

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

Простейший пример применения.

В чем проблема накинуть impl для ArtificialBarker? Где это нужно? Есть где нибудь «реальный» пример?

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

Никто так не пишет

Ну вперёд, напиши без библиотечных вызовов вроде common_type.

auto f(auto … args);

Может всё-таки стоит попробовать подумать головой, а не пытаться выиграть спор? Компилятор может вывести типы переменных в compile time. Значит у него есть информация, необходимая для вывода типов, и он эту информацию может перенести в рантайм, и использовать её там.

Да, никто так не делает, так же как никто не использует стирание типов для реализации bounded parametric polymorphism.

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

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

Не пришли. Конструкторы - это не магия, а обычная функция, создающая значение. Приводите код, который нельзя сделать с помощью замыкания. Без emplace_back, как я уже говорил это будет сделано с помощью гарантированного copy elision.

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

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

а хелпер вынести в хэдер с другими хелперами

Зачем мне нужен хелпер чтобы пройтись по варианту? Почему это не в std?

Обратите внимание на третий тип.

Высосанная из пальца проблема.

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

Зачем мне нужен хелпер чтобы пройтись по варианту?

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

Высосанная из пальца проблема.

Ненужно (с) уже в третий раз?

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

Ну вперёд, напиши без библиотечных вызовов вроде common_type.

auto foo(auto ... xs) {
    return (xs + ...);
}

Может всё-таки стоит попробовать подумать головой, а не пытаться выиграть спор? Компилятор может вывести типы переменных в compile time. Значит у него есть информация, необходимая для вывода типов, и он эту информацию может перенести в рантайм, и использовать её там.

К чему это вообще? Для чего ты мне это сообщаешь? Что ты пытаешься этим сказать? Как это относится к якобы эквивалентности List<TypeTaggedVar> и вариадиков?

Да, никто так не делает, так же как никто не использует стирание типов для реализации bounded parametric polymorphism.

Что ты несешь, при чем здесь это? Ты совсем поплыл? Почему ты не можешь ответить за свое же блеяние и реализовать то, что ты наблеял? Зачем ты в рантайм поперся переносить свой лист, для которого еще и переписал все функции в рекурсивный понос?

Ключ в том, чтобы не переписывать ничего, а вынести уже имеющийся синтаксис f(a, b, c) на новый уровень, позволив вот эти a, b, c выдирать оттуда и использовать отдельно и самостоятельно.

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

В чем проблема накинуть impl

Зачем накидывать какой-то impl для типа, если он уже есть? Более того, почему я в принципе должен портить логически корректную иерархию просто ради того, чтобы можно было сделать несколько полиморфных вызовов? Этот код есть, он уже работает, и он уже не требует ничего дописывать.

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

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

  • Автоматическая коэрсия туплов (a, (b, c)) -> (a, b, c)
  • ABI для того, чтобы все это дерьмо работало (при чем тут вообще ABI? почему он нужен, если у тебя уже коэрсия работает и это один и тот же типа?)
  • Вариадики эквивалентны List<TypeTaggedVar> (и простыня рекурсивного поноса)

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

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

Еще раз.

Есть иерархия типов

    Animal                      ArtificialBarker
   /       \
Dog    AnotherDog

Все эти типы реализуют один интерфейс – они обладают методом bark(). Реализация уже есть. Без варианта я могу только добавить в ArtificalBarker наследование от Animal, чтобы вызывать dynamic_cast<Animal&>(barkable).bark(), но это а) логически некорректно и б) в общем случае потребует реализацию дополнительных методов в) стирает тип.

Зачем все это делать, если можно просто взять variant+visit? Почему вы хотите его избежать?

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

Автоматическая коэрсия туплов (a, (b, c)) -> (a, b, c)

Опять неправильно. Про автоматическую коэрсию я нигде не говорил. Для работы с гетерегенными списками в расте сейчас приходится записывать их как (a, (b, (c, ()))). Другое представление использовать, не (a, b, c), а (a, (b, (c, ()))). Это не коэрсия. Это другой способ записи. И правильно сконвертировать список вы так и не смогли.

ABI для того, чтобы все это дерьмо работало

Про ABI я написал в ответ на ваше «Здесь у нас не haskell». Ну вот я и решил, что вероятно вам не нравится что у функций f(a, b, c) и f((a, (b, (c, ())))) могут быть разные ABI. Поэтому я указал, что для их реализации можно использовать одинаковые ABI. От этого ничего не сломается.

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

Вариадики эквивалентны List

В чём проблема вы так и не сказали. return (xs + …); конвертируется в итерацию по этому листу с такой-же легкостью как рекурсивная версия конвертируется в рекурсивную версию с List’ами. Рекурсивную версию я выбрал потому что она приводится в туториалах по вариадикам и в ответах на stackoverflow.

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

ArtificialBarker реализует непонятно что. Когда есть интерфейс то можно все отследить и логично ограничить.

Но это не то что я спрашивал, есть реальные примеры?

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

Ты в очередной раз решил опозориться?

// Обратите внимание на рекурсивную дрисню
template <typename T, typename... Rest>
T sum(T t, Rest... rest) {
  return t + sum(rest...);
}

Какое отношение это бездарное дерьмо имеет к полиморфному sum? Я понимаю, что для тебя, домохозяина, это слишком сложно, но всё же?

Это дерьмо не работает - пытайся ещё раз.

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

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

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

Это реальный пример. Я лишь заменил имена на более наглядные и выкинул не имеющий отношения к теме код.

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

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

Ага. Ну в расте такого нету. В С++ типы выводятся автоматически на основе тела функции. В расте надо отдельно написать функции на типы, а потом уже что то делать в рантайме. Т.е. код будет примерно такой

fn add(t: T, rest: Rest) -> <T as Add<Rest>>::Output { t + rest }

Т.е. в с++ можно написать функции, а потом если надо проставить везде decltype() и получить функции на уровне типов. Тоже самое с интеграл темплейтами, можно поднять constexpr значения / функции на уровень типов.

Но чтобы написать более менее полезные функции нужен нормальный паттерн матчинг по типам, или другими словами оверлоад или специализация. Например с тем же zip(A, B). Если мы знаем что A, B это рэндом аксеесс итераторы, то нам не нужно два счётчика, и два сравнения. Достаточно сделать len = min(A.size(), B.size()) и потом уже бежать одним счётчиком. Собственно в с++ ренжах либе есть всякие оптимизации подобного рода. В раст такое для zip() добавили не так давно, т.к. в стд либе они используют unstable фичи.

В расте этот язык для типов <T as Add<Rest>>::Output выглядит более вербозно. И по причинам выше его не хватает. Например надо специализировать какой-то f(t: T) в зависимости от sizeof(T). Т.е. поднять constexpr на уровень типов. Естественно примитивно это можно сделать в теле функции if (sizeof<T>() == 4) { f1() } else { f2() }, как там выше предлагали с typeid и компилятор выкинет if. Но на уровне типов это информация теряется. Т.е. f1() и f2() уже обязаны вернуть один и тот же тип.

Поэтому все эти упражнения с тьюринг полными дженериками раста имеет мало смысла, пока на практике они оторваны от информации о коде. Как я привёл выше два примера.

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

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

Мне не нравится, что вы вводите какую-то магическую операцию деконструкции типов и для работы с получившимся франкенштейном предлагаете еще и ABI (кстати все еще не имеющего отношения к теме) ввести.

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

Вместо реализации Zip от произвольного числа элементов вы написали Zip от двух, где второй – интерфейс. Вы утверждаете, что это эквивалентные операции. Я говорю: нет, потому что вы не можете вызвать f: (a, b, c) от результата этой операции, так как не можете отобразить (a, (b, (c, ()))) в (a, b, c) кроме как руками выполнять паттерн-матчинг.

Вы говорите: я могу, дайте мне только магический лифт, поэтому эквивалентны. Ну так вот, у вас этого лифта нет.

Если бы у вас были вариадики, вы могли бы написать тот самый лифт. Это было бы тривиально. Но и их у вас нет. Все то же самое про List. Если бы у вас были вариадики, вы могли бы легко преобразовывать List<TypeTaggedVar> в вариадик и наоборот – но вариадиков у вас нет. Про какую эквивалентность вы говорите? Приведенные примеры показывают, что вариадики – более мощный концепт, который нельзя реализовать имеющимися в расте средствами.

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

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

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

Чтобы не быть голословным, набросал вам вашу коэрсию. Здесь много что можно улучшить, но это не так важно. Можете ли вы сделать что-то подобное?

https://godbolt.org/z/Ese6dv

#include <iostream>
#include <tuple>
#include <utility>

template <typename T>      inline constexpr bool is_tuple_v = false;
template <typename ... Ts> inline constexpr bool is_tuple_v<std::tuple<Ts...>> = true;

template <typename T>
concept tuple_like = is_tuple_v<T>;

constexpr auto as_tuple(auto a) {
    if constexpr (tuple_like<decltype(a)>) {
        return a;
    } else {
        return std::tuple{ a };
    }
}

constexpr auto concat_(auto a) {
    if constexpr (tuple_like<decltype(a)>) {
        return a;
    }
    return std::tuple{ a };
}

constexpr auto concat_(tuple_like auto a, tuple_like auto b) {
    auto as = std::make_index_sequence<std::tuple_size_v<decltype(a)>>{};
    auto bs = std::make_index_sequence<std::tuple_size_v<decltype(b)>>{};
    return [&]<auto ... As, auto ... Bs>(std::index_sequence<As...>, std::index_sequence<Bs...>) {
        return std::tuple{ std::get<As>(a)...,
                           std::get<Bs>(b)... };
    }(as, bs);
}

constexpr auto concat_() { return std::tuple{}; }

constexpr auto concat_(auto a, auto b, auto ... rest) {
    return concat_(as_tuple(a), concat_(as_tuple(b), as_tuple(rest)...));
}

constexpr auto coerce_(auto t) {
    if constexpr (tuple_like<decltype(t)>) {
        return std::apply([=](auto ... ts){
            return concat_(coerce_(ts)...);
        }, t);
    } else {
        return t;
    }
}

int main(){
    std::tuple a{ 1, '%', 4.0 };
    std::tuple b{ "string", std::tuple{}, true };
    std::tuple c{ std::tuple{ 0.256f } };
    auto _ = coerce_(concat_(a, b, c));
    std::cout << typeid(c).name() << '\n';
    std::apply([&](auto ... c){ ((std::cout << c << ' '), ...); }, _); // 1 % 4 string 1 0.256 
}
Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

набросал вам вашу коэрсию.

Это ты загнул. Речь шла про

(A, (B, (C, ()))) <-> (A, B, C)

А не про произвольные комбинации. Очевидно что в расте тебе могут показать макрос который сгенерирует такое для списков ограниченной длины. Всё.

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

Макрос - не язык. С таким же успехом любой недоязычок может всё. Прикрутил внешний кодоген на скриптухе и уже смог. Зачем вообще нужен языка?

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

Тогда назвать язык скриптухо генератор - раст - лввм. Типа как ним-си. Если макрос тривиальный то это все ещё раст. Но не любая проблема решается тривиальным макросом.

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

Не, не. Есть llvm, это по-сути тоже самое, что генерация в си. Это такая специальная сишка, куда больше подходящая в качестве ir. Там такое много где практиковалось. Там у хаскеля свой диалект сишки, у гцц. Везде. Но смысл остался тот же.

Вот, любая скриптуха поверх llvm имеет доступ к его фичам и по-сути к сишка-вставкам. Все эти атрибуты ворованные, интринсики - это всё ручки управления llvm. К недоязычку самому отношения не имеют.

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

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

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

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

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

А не про произвольные комбинации.

«Не произвольные комбинации» растут из рекурсивного построения списка. Тем не менее, даже для них написать это нельзя.

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

Нет, не могут – туда руками придется передавать длину списка, причем она должна будет быть известна на этапе генерации кода из макросов, т.е. туда нельзя будет передать const N = 5; expand!(..., N);.

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

Я не говорил, что в расте будет удобно писать. Или что можно сделать совсем-совсем всё на текущем этапе реализации раста. Здесь для нормального вывода тупла нужна специализация например.

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

Ссылка на плейграунд

#![feature(specialization)]
#![allow(incomplete_features)]
use std::fmt::Debug;

macro_rules! tuple {
    ($e:expr, $( $rest:expr ),+) => { ($e, tuple!($( $rest ),+)) };
    ($e:expr) => { ($e, ()) };
    ($e:expr,) => { ($e, ()) };
    () => { () };
}

fn main() {
    let a = tuple!(1, '%', 4);
    let b = tuple!("string", tuple!(), true);
    let c = tuple!(tuple!(0.256));
    let d = tuple!(a, b, c).concat_all();
    d.start_iter(); // (1 '%' 4 "string" () true (0.256 ))
}

trait Concat<V> {
    type Out;
    fn concat(self, other: V) -> Self::Out;
}

impl<V, T, U: Concat<V>> Concat<V> for (T, U) {
    type Out = (T, U::Out);
    fn concat(self, other: V) -> Self::Out {
        (self.0, self.1.concat(other))
    }
}

impl<V> Concat<V> for () {
    type Out = V;
    fn concat(self, other: V) -> Self::Out {
        other
    }
}

trait ConcatAll {
    type Out;
    fn concat_all(self) -> Self::Out;
}

impl<T: Concat<U::Out>, U: ConcatAll> ConcatAll for (T, U) {
    type Out = T::Out;
    fn concat_all(self) -> Self::Out {
        self.0.concat(self.1.concat_all())
    }
}

impl ConcatAll for () {
    type Out = ();
    fn concat_all(self) -> () {
        ()
    }
}

trait IterTuple {
    fn start_iter(&self);
    fn iter(&self);
}

impl<T: Debug> IterTuple for T {
    default fn start_iter(&self) {
        print!("{:?} ", self)
    }
    default fn iter(&self) {
        print!("{:?} ", self)
    }
}

impl<T: IterTuple + Debug, U: IterTuple + Debug> IterTuple for (T, U) {
    fn start_iter(&self) {
        print!("(");
        self.0.start_iter();
        self.1.iter();
        print!(")");
    }
    fn iter(&self) {
        self.0.start_iter();
        self.1.iter();
    }
}

impl IterTuple for () {
    fn start_iter(&self) {
        print!("() ");
    }
    fn iter(&self) {
    }
}

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

Ты сделал не коэрсию, ты сделал итерацию по hlist’у.

fn foo<A: Display, B: Display, C: Display>(t: (A, B, C)) {
    let (a, b, c) = t;
    println!("{} {} {}", a, b, c);
}

математически эквиваленты

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

Чтобы как-то пользоваться твоим hlist’ом, нужно переписывать все функции с обычных туплов на hlist’ы

Нельзя придумать пример, который я не смогу написать на расте. Я не говорил, что в […] можно сделать совсем-совсем всё на текущем этапе реализации раста.

Ты сам себе противоречишь. Зачем ты это делаешь? Для чего? Я написал код. Вперед, пиши преобразование. Где оно? Нельзя написать? Так получается, есть такой пример?

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