Rust: проблема отслеживания const fn (RFC 911)

Созданный на 6 апр. 2015  ·  274Комментарии  ·  Источник: rust-lang/rust

https://github.com/rust-lang/rust/issues/57563 | новая проблема с метатрекингом

Старый контент

Проблема с отслеживанием для rust-lang/rfcs#911.

Эта проблема была закрыта в пользу более целенаправленных проблем:

Что нужно сделать перед стабилизацией:

CTFE = https://en.wikipedia.org/wiki/Compile_time_function_execution

A-const-eval A-const-fn B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue T-lang

Самый полезный комментарий

Пожалуйста, игнорируйте это, если я совсем не в теме.

Проблема, которую я вижу с этим RFC, заключается в том, что как пользователь вы должны пометить как можно больше функций const fn , потому что это, вероятно, будет лучшей практикой. То же самое сейчас происходит в C++ с contexpr. Думаю, это просто лишнее многословие.

В D нет const fn , но он позволяет вызывать любую функцию во время компиляции (за некоторыми исключениями).

Например

// Standalone example.
struct Point { x: i32, y: i32 }

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y }
    }

    fn add(self, other: Point) -> Point {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

const ORIGIN: Point = Point::new(0, 0); // works because 0, 0 are both known at compile time 
const ORIGIN2: Point = Point::new(0, 0); // ditto

const ANOTHER: Point = ORIGIN.add(ORIGIN2); // works because ORIGIN and ORIGIN2 are both const.
{
    let x: i32 = 42;
    let y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Error: x and y are not known at compile time
}
{
    const x: i32 = 42;
    const y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Works x and y are both known at compile time.
}

Заметьте, я на самом деле не пользователь Rust и прочитал RFC всего несколько минут назад, так что вполне возможно, что я что-то неправильно понял.

Все 274 Комментарий

Это закрыто по номеру 25609?

@Munksgaard Это просто добавляет поддержку компилятору, насколько мне известно. В stdlib есть много функций, которые нужно изменить на const fn и протестировать на предмет поломки. Я не знаю, каков прогресс в этом.

Я надеюсь, что это будет реализовано на std::ptr::null() и null_mut() , чтобы мы могли использовать их для инициализации static mut *MyTypeWithDrop , не прибегая к 0usize as *mut _

РЕДАКТИРОВАТЬ: Удалено, так как это не по теме

Чтобы было ясно, вопрос здесь в первую очередь не в полезности функции, а в том, как лучше всего ее сформулировать (или в какой структуре ее сформулировать). См. обсуждение RFC.

Теперь это проблема отслеживания для возможной стабилизации.

https://github.com/rust-lang/rust/issues/29107 был закрыт.

Я не согласен с тем, что «Интеграция с шаблонами» или любые изменения в стандартной библиотеке должны блокировать это. Это очень полезно даже без этих изменений, а эти изменения можно сделать позже. В частности, я хотел бы вскоре начать использовать const fn в своем собственном коде.

Соответственно, можно ли переоценить статус стабилизации этого?

Я не сомневаюсь, что const fn даже в его текущей ограниченной форме было бы полезно иметь, но то, что я действительно хотел бы, в идеале, прежде чем идти дальше по этому пути, было бы для тех, кто выступает за « const fn подход», чтобы обдумать и сформулировать свой предпочтительный эндшпиль. Если мы просто продолжим постепенно добавлять кажущуюся полезной функциональность самым очевидным образом, мне кажется весьма вероятным, что в конечном итоге мы в конечном итоге скопируем более или менее полностью дизайн C++ constexpr . Нас это устраивает? Даже если мы скажем «да», я бы предпочел, чтобы мы выбрали этот путь с ясным взглядом, вместо того, чтобы возвращаться к нему маленькими шагами с течением времени, как к пути наименьшего сопротивления, пока он не станет неизбежным.

(Учитывая, что семантика безопасного кода Rust должна быть полностью определяемой, кажется вероятным, что в конечном итоге, по крайней мере, каждая функция, которая (транзитивно) не зависит от unsafe , должна быть помечена как const . И учитывая, что unsafe должен быть деталью реализации, держу пари, люди будут настаивать на том, чтобы как-то ослабить и это ограничение. хорошо интегрированная история для постановки и вычислений на уровне типов.)

@glaebhoerl

Я не сомневаюсь, что const fn даже в его текущей ограниченной форме будет полезной функциональностью, но то, что я действительно хотел бы, в идеале, прежде чем идти дальше по этому пути, было бы для тех, кто выступает за «подход const fn» к подумайте и сформулируйте свой предпочтительный эндшпиль... мне кажется очень вероятным, что в конечном итоге мы в конечном итоге скопируем более или менее полностью дизайн constexpr C++.

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

@eddyb и я недавно сделали несколько набросков схемы, которая могла бы обеспечить постоянную оценку очень широкого спектра кода. В основном опуская все константы в МИР и интерпретируя его (в некоторых случаях,
абстрактная интерпретация, если есть дженерики, которые вы еще не можете оценить, и именно здесь все становится для меня наиболее интересным).

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

Тем не менее, @glaebhoerl , я также хотел бы услышать, как вы сформулируете свой предпочтительный альтернативный эндшпиль. Я думаю, что вы изложили некоторые подобные мысли в RFC const fn , но я думаю, что было бы хорошо услышать это снова и в этом контексте. :)

Проблема с распределением заключается в том, что оно уходит во время выполнения.
Если мы каким-то образом сможем запретить пересечение этого барьера времени компиляции/времени выполнения, то я полагаю, что у нас может быть рабочий liballoc с const fn .
Было бы не сложнее управлять такого рода распределениями, чем иметь дело со значениями с байтовой адресацией в интерпретируемом стеке.

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

Имейте в виду, что даже при полноценном constexpr -подобном вычислении const fn _все равно_ будет чистым: его двойной запуск на 'static данных приведет к точно такому же результату и никаким побочные эффекты.

@nikomatsakis Если бы у меня был такой, я бы упомянул его. :) Я в основном просто вижу известные неизвестные. Вся эта штука с const как частью системы дженериков, конечно же, была частью того, что я понимал под дизайном C++. Что касается связанных общих параметров const и const , учитывая, что у нас уже есть массивы фиксированного размера с const как часть их типа, и мы хотели бы абстрагироваться от их, я был бы удивлен, если бы существовал гораздо лучший — а не просто более общий — способ сделать это. Часть вещей const fn кажется более отделимой и изменчивой. Легко представить, что можно пойти дальше и иметь такие вещи, как const impl s и const Trait в дженериках, но я _уверен_, что существует предшествующий уровень техники для такого рода общих вещей, которые уже выяснили все. и мы должны попытаться найти его.

Из основных вариантов использования языка Rust те, которые в первую очередь нуждаются в низкоуровневом управлении, например, ядра, кажутся уже достаточно хорошо обслуженными, но другая область, в которой у Rust может быть большой потенциал, — это вещи, которые в первую очередь требуют высокой производительности, а в что космическая мощная поддержка (в той или иной форме) для поэтапных вычислений (которые const fn уже являются очень ограниченным примером) кажется, что это может изменить правила игры. (Буквально за последние несколько недель я наткнулся на два отдельных твита людей, решивших перейти с Rust на язык с лучшими возможностями подготовки.) Я не уверен, что какое-либо из существующих решений на «близких нам» языках -- constexpr в C++, специальный CTFE в D, наши процедурные макросы — действительно вдохновляющие и достаточно мощные/полноценные для такого рода вещей. (Процедурные макросы кажутся хорошей вещью, но больше для абстракции и DSL, чем для ориентированной на производительность генерации кода.)

Что касается того, что _было бы_ вдохновляющим и достаточно хорошим... Я еще не видел этого, и я недостаточно знаком со всем пространством, чтобы точно знать, где искать. Конечно, согласно вышеизложенному, мы могли бы хотя бы взглянуть на Julia и Terra, даже если они кажутся совершенно разными языками по сравнению с Rust во многих отношениях. Я знаю, что Олег Киселев проделал много интересной работы в этой области. Работа Тиарка Ромпфа над Lancet и Lightweight Modular Staging для Scala определенно заслуживает внимания. Я помню, как в какой-то момент видел презентацию @kmcallister о том, как может выглядеть Rust с зависимой типизацией (что может быть, по крайней мере, более общим, чем вставлять везде const ), и я также помню, что видел что-то от Олега на этот счет. эти типы сами по себе являются формой постановки (что кажется естественным, учитывая, что разделение фаз между компиляцией и временем выполнения очень похоже на этапы)... множество захватывающих потенциальных связей во многих разных направлениях, поэтому это будет похоже на пропущенный возможность, если бы мы просто согласились на первое решение, которое приходит нам в голову. :)

(Это была просто мешанина, и я почти наверняка неточно охарактеризовал многие вещи.)

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

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

Было бы неплохо иметь возможность делать больше, чем это, но иметь возможность создавать статические экземпляры нетривиальных типов с инвариантами, навязанными компилятором, очень важно. Подход C++ constexpr чрезвычайно ограничен, но это больше, чем то, что мне нужно для моих вариантов использования: мне нужно предоставить функцию, которая может создать экземпляр типа T с параметрами x , y и z таким образом, что функция гарантирует, что x , y и z допустимы (например, x < y && z > 0 ), так что результатом может быть переменная static без использования кода инициализации, который запускается при запуске.

@briansmith FWIW Другой подход, который имеет шанс решить те же варианты использования, был бы, если бы макросы имели гигиену конфиденциальности , которую я считаю (надеюсь), которую мы планируем сделать.

@briansmith FWIW Другой подход, который имеет шанс решить те же самые варианты использования, был бы, если бы макросы имели гигиену конфиденциальности, которую, я полагаю (надеюсь), мы планируем сделать так, чтобы они были.

Я думаю, если вы используете процедурные макросы, вы можете оценить x < y && z > 0 во время компиляции. Но, похоже, пройдет много-много месяцев, прежде чем процедурные макросы можно будет использовать в стабильном Rust, если они вообще будут. const fn интересен тем, что его можно включить для стабильного Rust _сейчас_, насколько я понимаю положение вещей.

@glaebhoerl Я бы не стал задерживать дыхание из-за строгой гигиены, вполне возможно, что у нас будет механизм выхода (как и в настоящих LISP), так что вам это может не понадобиться из соображений безопасности.

@glaebhoerl также есть https://anydsl.github.io/, который даже использует
Синтаксис похож на Rust ;) они в основном нацелены на поэтапное вычисление и в
конкретная частичная оценка.

В субботу, 16 января 2016 г., в 18:29, Эдуард-Михай Буртеску <
уведомления@github.com> написал:

@glaebhoerl https://github.com/glaebhoerl Я бы не задержал дыхание
строгая гигиена, вполне возможно, что у нас будет механизм побега
(точно так же, как настоящие LISP), поэтому вам может не понадобиться это для какой-либо безопасности
целей.


Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-172271960 .

29525 должен быть исправлен до стабилизации

Учитывая, что семантика безопасного кода Rust должна быть полностью определяемой, кажется вероятным, что в конечном итоге, по крайней мере, каждая функция, которая (транзитивно) не зависит от unsafe , должна быть помечена как const . И учитывая, что unsafe предполагается как деталь реализации, держу пари, люди будут настаивать на том, чтобы как-то ослабить и это ограничение.

Просто мысль: если мы когда-нибудь формально определим модель памяти Rust , то даже код unsafe потенциально может быть безопасно и разумно оценен во время компиляции, интерпретируя его абстрактно/символически, то есть использование необработанных указателей не будет t превращаются в прямой доступ к памяти, как во время выполнения, а скорее в нечто (просто в качестве примера для иллюстрации), например поиск в хэш-карте выделенных адресов вместе с их типами и значениями или подобным, с проверкой правильности каждого шага - так что любое выполнение, поведение которого не определено, будет _строго_ ошибкой, о которой сообщает компилятор, а не уязвимостью безопасности в rustc . (Это также может быть связано с обработкой isize и usize во время компиляции символически или в зависимости от платформы.)

Я не уверен, где это оставляет нас в отношении const fn . С одной стороны, это, вероятно, откроет гораздо более полезный код, который будет доступен во время компиляции — Box , Vec , Rc , все, что использует unsafe для оптимизации производительности — это хорошо. На одно произвольное ограничение меньше. С другой стороны, граница для «того, что может быть const fn » теперь, по существу, будет сдвинута наружу к _всему, что не связано с FFI_. Итак, что мы _фактически_ отслеживаем в системе типов под видом const ности, так это то, что (транзитивно) зависит от FFI, а что нет. И независимо от того, использует ли что-то FFI, _до сих пор_ люди справедливо считают, что это внутренняя деталь реализации, и это ограничение (в отличие от unsafe ) _на самом деле_ не представляется возможным снять. И в этом сценарии у вас, вероятно, будет гораздо больше fn , имеющих право на получение const , чем тех, которые этого не сделают.

Таким образом, у вас по-прежнему будет const ness, вращающийся вокруг произвольного ограничения реализации, и вам также придется писать const почти везде. Это тоже звучит не слишком привлекательно...

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

@glaebhoerl Ну, это в значительной степени модель, которую я описал , и которую @tsion s miri реализует.

Я думаю, что различие FFI очень важно из-за чистоты, которая необходима для согласованности.
Вы _не могли_ даже использовать GHC для Rust const fn s, потому что у него есть unsafePerformIO .

Мне самому не очень нравится ключевое слово const , поэтому я согласен с const fn foo<T: Trait> вместо const fn foo<T: const Trait> (за требование const impl Trait for T ).

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

@eddyb Я думаю, вы имели в виду ссылку на https://internals.rust-lang.org/t/mir-constant-evaluation/3143/31 (комментарий 31, а не 11).

@tion Исправлено, спасибо!

Пожалуйста, игнорируйте это, если я совсем не в теме.

Проблема, которую я вижу с этим RFC, заключается в том, что как пользователь вы должны пометить как можно больше функций const fn , потому что это, вероятно, будет лучшей практикой. То же самое сейчас происходит в C++ с contexpr. Думаю, это просто лишнее многословие.

В D нет const fn , но он позволяет вызывать любую функцию во время компиляции (за некоторыми исключениями).

Например

// Standalone example.
struct Point { x: i32, y: i32 }

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y }
    }

    fn add(self, other: Point) -> Point {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

const ORIGIN: Point = Point::new(0, 0); // works because 0, 0 are both known at compile time 
const ORIGIN2: Point = Point::new(0, 0); // ditto

const ANOTHER: Point = ORIGIN.add(ORIGIN2); // works because ORIGIN and ORIGIN2 are both const.
{
    let x: i32 = 42;
    let y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Error: x and y are not known at compile time
}
{
    const x: i32 = 42;
    const y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Works x and y are both known at compile time.
}

Заметьте, я на самом деле не пользователь Rust и прочитал RFC всего несколько минут назад, так что вполне возможно, что я что-то неправильно понял.

@MaikKlein было много дискуссий о CTFE в обсуждении RFC .

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

Это используется Rocket: https://github.com/SergioBenitez/Rocket/issues/19#issuecomment -269052006.

См. https://github.com/rust-lang/rust/issues/29646#issuecomment-271759986 . Также нам нужно пересмотреть нашу позицию по поводу явности, поскольку мири раздвигает границы «глобальных побочных эффектов» ( @solson и @nikomatsakis только что говорили об этом в IRC).

Проблема, которую я вижу с этим RFC, заключается в том, что как пользователь вы должны пометить как можно больше функций const fn, потому что это, вероятно, будет лучшей практикой.

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

Насчет линта согласен. Это похоже на существующие встроенные линты missing_docs , missing_debug_implementations и missing_copy_implementations .

Однако есть проблема с включенным lint по умолчанию... он будет предупреждать о функциях, которые вы явно не хотите использовать const , скажем, потому что вы планируете позже изменить функцию таким образом, чтобы она не может быть const и не хочет зафиксировать свой интерфейс в const (удаление const является критическим изменением).

Я предполагаю, что #[allow(missing_const)] fn foo() {} может работать в этих случаях?

@eddyb @nikomatsakis Мой пункт «удаление const является критическим изменением» предполагает, что мы все-таки захотим иметь ключевое слово, поскольку это обещание нижестоящим, что fn _остается_ const до следующей основной версии.

Будет обидно, сколько const нужно будет раскидать через std и другие библиотеки, но я не вижу, как можно этого избежать, если только это не требовалось только на общедоступных ресурсах. лицом к предметам, и это кажется запутанным правилом.

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

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

Технически было бы лучше аннотировать функции как notconst , потому что я ожидаю, что const fn будет намного больше, чем наоборот.

notconst также больше соответствовало бы философии дизайна Rust. (т.е. " mut , а не const ")

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

Мне нравится этот... Я не думаю, что это сбивает с толку.

Я падаю духом от этой идеи. У этого есть свои преимущества (думайте только о const fn при принятии решений об общедоступном интерфейсе), но я подумал о другом способе, которым это может сбивать с толку:

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

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


Я ожидаю, что будет намного больше const fn , чем наоборот.

Я так думал какое-то время, но это будет верно только для чистых ящиков библиотек Rust. Невозможно сделать fns на основе FFI постоянными (даже если они только транзитивно основаны на FFI, а это много), поэтому сумма const fn может быть не такой уж плохой. как мы с тобой думали.


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

Кроме того, для протокола, notconst было бы критическим изменением.

@solson Очень хороший момент.

Имейте в виду, что ключевое слово становится еще более сложным, если вы пытаетесь использовать его с трейт-методами. Ограничение его определением признака недостаточно полезно, а аннотирование имплементов приводит к несовершенным правилам «const fn parametrim».

Я чувствую, что этот компромисс был довольно тщательно обсужден, когда мы приняли const fn в первую очередь. Я думаю, что анализ @solson также верен. Я предполагаю, что единственное, что изменилось, это то, что, возможно, процент констеблей fns вырос, но я не думаю, что настолько, чтобы изменить фундаментальный компромисс здесь. Будет раздражать постепенное добавление const fn в ваши публичные интерфейсы и так далее, но такова жизнь.

@nikomatsakis Меня беспокоит сочетание этих двух фактов:

  • мы не можем проверить все заранее, код unsafe может быть "динамически непостоянным"
  • что бы мы ни делали для дженериков и трейтов, это будет компромисс между «правильным» и гибким

Учитывая, что «глобальные побочные эффекты» — это главное, что предотвращает const fn кода, не является ли это «системой эффектов», которая раньше была в Rust и была удалена?
Не следует ли говорить о «стабильности эффекта»? Похоже на код, предполагающий, что какая-то библиотека никогда не паникует IMO.

@eddyb абсолютно const - это система эффектов, и да, у нее есть все недостатки, которые заставляли нас избегать их как можно больше ... Вполне вероятно, что если мы собираемся терпеть боль добавления в системе эффектов мы можем захотеть рассмотреть некоторый синтаксис, который мы можем масштабировать для других видов эффектов. Например, мы платим аналогичную цену с unsafe (тоже эффект), хотя я не уверен, что есть смысл думать об их объединении.

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

Как насчет этого:

В общем, я думаю, что const fn следует использовать только для конструкторов ( new ) или там, где это абсолютно необходимо.

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

@torkleyy Вы можете сделать это уже с помощью помощников, которые не экспортируются.

Я не вижу веских аргументов в пользу того, что приватные вспомогательные функции не должны быть неявно const , когда это возможно. Я думаю, что @solson говорил, что явное указание const даже для вспомогательных функций заставляет программиста сделать паузу и подумать, хотят ли они зафиксировать эту функцию как const . Но если программисты уже обязаны думать об этом для публичных функций, разве этого недостаточно? Разве не стоило бы не писать везде const ?

В IRC @eddyb предложил разделить эту функцию, чтобы мы могли стабилизировать вызовы const fns до выяснения деталей их объявления и тела. Это звучит как хорошая идея?

@durka Это звучит великолепно для меня, как для пользователя Rust, который мало что знает о внутренностях компилятора.

Извините за непонимание, но что значит стабилизировать вызов функции const без стабилизации объявления.

Говорим ли мы, что компилятор каким -то образом каким-то образом узнает, что является константой, а что нет, но пока оставляет эту часть открытой для обсуждения/реализации?

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

@nixpulvis Некоторые const fn уже существуют в стандартной библиотеке, например UnsafeCell::new . Это предложение позволит вызывать такие функции в постоянных контекстах, например, инициализатор элемента static .

@nixpulvis Я имел в виду вызовы функций const fn , определенных в нестабильном коде (например, в стандартной библиотеке), из постоянных контекстов, а не обычных функций, определенных в стабильном коде Rust.

копия @rust-lang/lang на https://github.com/rust-lang/rust/issues/24111#issuecomment -310245900

Хотя я полностью поддерживаю стабилизацию вызовов const fn , если это может произойти быстрее, мне не ясно, что блокирует стабилизацию всей функции const fn . Каковы оставшиеся опасения на сегодняшний день? Каким будет путь к их решению?

@SimonSapin Дело в том, что нам не совсем понятен дизайн объявления const fn сегодня хорошо масштабируется, и мы не уверены во взаимодействии между ними и чертами и в том, какая гибкость должна быть.

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

стабилизировать использование const fn.

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

Я отправил PR https://github.com/rust-lang/rust/issues/43017 для стабилизации вызовов, а также список функций, которые необходимо проверить на @petrochenkov.

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

pub trait Zero {
    fn zero () -> Self;
}

Эта черта не требует, чтобы метод zero был const , так как это предотвратило бы его реализацию некоторым типом BigInt , поддерживаемым Vec . Но для машинных скаляров и других простых типов было бы гораздо практичнее использовать метод const .

impl Zero for i32 {
    const fn zero () -> i32 { 0 } // const
}

impl Zero for BigInt {
    fn zero () -> BigInt { ... } // not const
}

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

Почему вы хотите, чтобы разные реализации трейта вели себя по-разному? Вы не можете использовать это в общем контексте. Вы можете просто сделать локальный импл на скаляре с константой fn.

@Daggerbot Это единственный способ, которым я вижу перспективу для const fn в трейтах - если трейт требует, чтобы все импли были const fn , это гораздо реже, чем наличие эффективных « const impl s» .

@jethrogb Вы могли бы, хотя для этого требуется, чтобы константность была свойством реализации.
Я имею в виду, что общий const fn с привязкой, например, к T: Zero , потребует impl из Zero для T s, с которым он вызывается, содержит только методы const fn , когда вызов исходит из самого постоянного контекста (например, другого const fn ).

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

Разве это не решит проблему?

pub trait Zero {
    fn zero() -> Self;
}

pub trait ConstZero: Zero {
    const fn zero() -> Self;
}

impl<T: ConstZero> Zero for T {
    fn zero() -> Self {
        <Self as ConstZero>::zero()
    }
}

Шаблон можно уменьшить с помощью макросов.

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

// Blanket impl
impl<T: ConstZero> Zero for T {
    fn zero () -> Self { T::const_zero() }
}

pub struct Vector2<T> {
    pub x: T,
    pub y: T,
}

impl<T: ConstZero> ConstZero for Vector2<T> {
    const fn const_zero () -> Vector2<T> {
        Vector2 { x: T::const_zero(), y: T::const_zero() }
    }
}

// Error: This now conflicts with the blanket impl above because Vector2<T> implements ConstZero and therefore Zero.
impl<T: Zero> Zero for Vector2<T> {
    fn zero () -> Vector2<T> {
        Vector2 { x: T::zero(), y: T::zero() }
    }
}

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

Но если бы мы могли добавить const в реализованный метод там, где это не требуется, мы могли бы избежать этого дублирования, хотя все еще не идеально:

impl<T: Zero> Zero for Vector2<T> {
    const fn zero () -> Vector2<T> {
        Vector2 { x: T::zero(), y: T::zero() }
    }
}

IIRC, C++ позволяет что-то подобное при работе с constexpr . Недостатком здесь является то, что это const будет применимо только в том случае, если <T as Zero>::zero также равно const . Должно ли это быть ошибкой, или компилятор должен игнорировать этот const , когда он неприменим (например, C++)?

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

Изменить: предложение @andersk сделает первый пример возможным без ошибок. Это, вероятно, было бы лучшим/самым простым решением с точки зрения реализации компилятора.

@Daggerbot Это звучит как вариант использования правила «решетки», предложенного в конце RFC 1210 (специализация) . Если вы пишете

impl<T: ConstZero> Zero for T {…}  // 1
impl<T: ConstZero> ConstZero for Vector2<T> {…}  // 2
impl<T: Zero> Zero for Vector2<T> {…}  // 3
impl<T: ConstZero> Zero for Vector2<T> {…}  // 4

тогда, хотя 1 перекрывается с 3, их пересечение покрывается точно 4, так что это было бы разрешено по правилу решетки.

См. также http://smallcultfollowing.com/babysteps/blog/2016/09/24/intersection-impls/.

Это невероятно сложная система, которой мы хотим избежать.

Да, правило решетки было бы необходимо.

@eddyb что ты считаешь сложным?

@Kixunil Дублирование почти каждого признака в стандартной библиотеке вместо того, чтобы «просто» помечать некоторые impl как const fn .

Мы сбились с пути здесь. В настоящее время проблема заключается в стабилизации использования const fn . Разрешенные методы признаков const fn или const impl Trait for Foo ортогональны друг другу и принятым RFC.

@oli-obk Это не новый RFC, а проблема отслеживания для const fn .

Я просто заметил и отредактировал свой комментарий.

@eddyb да, но это проще для компилятора (минус специализация, но мы, вероятно, все равно захотим специализацию) и позволяет людям также ограничиваться ConstTrait .

Во всяком случае, я не против пометить импли как константы. Я также представляю, как компилятор автоматически генерирует ConstTrait: Trait .

@Kixunil Это не намного проще, особенно если вы можете сделать это со специализацией.
Компилятору не нужно будет автоматически генерировать что-то вроде ConstTrait: Trait , и система признаков не должна знать обо всем этом, нужно просто рекурсивно пройти через реализации (либо конкретный impl или ограничение на where ), которые предоставляет система черт, и проверьте их.

Мне интересно, должен ли const fns запрещать доступ к UnsafeCell . Вероятно, необходимо разрешить действительно константное поведение:

const fn dont_change_anything(&self) -> bool {
    let old = self.cell.get();
    self.cell.set(!old);
    old
}

До сих пор я видел, что set не const . Вопрос в том, останется ли это навсегда. Другими словами: может ли код unsafe полагаться на то, что при выполнении одной и той же функции const с неизменяемыми данными всегда будет возвращаться один и тот же результат сегодня и в каждом будущем выпуске языка/библиотеки?

const fn не означает неизменный, это означает, что его можно вызывать во время компиляции.

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

@jethrogb Спасибо за ссылку!

Я заметил, что mem::size_of реализовано как const fn в nightly. Возможно ли это для mem::transmute и других? Встроенные функции Rust работают внутри компилятора, и я недостаточно знаком, чтобы вносить необходимые изменения, позволяющие это сделать. В противном случае, я был бы счастлив реализовать это.

К сожалению, работать со значениями немного сложнее, чем просто волшебным образом их создавать. Для transmute требуется мири. Первый шаг к включению miri в компилятор уже сделан: #43628

Так! Есть ли интерес к константной стабилизации *Cell::new , mem::{size,align}_of , ptr::null{,_mut} , Atomic*::new , Once::new и {integer}::{min,max}_value ? Должны ли мы иметь здесь FCP или создавать отдельные проблемы с отслеживанием?

да.

Я не являюсь частью какой-либо команды, которая имеет право принимать решения по этому вопросу, но мое личное мнение состоит в том, что все они, кроме mem::{size,align}_of , достаточно тривиальны, чтобы их можно было стабилизировать сейчас, не проходя через движения резинового штампа. ФКП.

Как пользователь, я хотел бы использовать mem::{size,align}_of в константных выражениях как можно скорее, но я читал, что @nikomatsakis выражает обеспокоенность по поводу их постоянной константной стабильности, когда они были сделаны const fn s . Я не знаю, есть ли особые опасения или просто общее предостережение, но именно поэтому IIRC были добавлены шлюзы функций для каждой функции. Я полагаю, что проблемы этих двоих будут достаточно схожими, чтобы они могли разделить FCP. Я не знаю, может ли @rustbot обрабатывать отдельные FCP в одном и том же потоке GitHub, поэтому, вероятно, лучше открыть отдельные задачи.

@durka , можете ли вы открыть одну проблему отслеживания для стабилизации константности всех этих функций? Я предложу FCP, как только он будет готов.

Чтобы следовать примеру в обсуждении const fns на alloc::Layout :
Можно ли разрешить панику в const fn и рассматривать ее как ошибку компиляции? Это похоже на то, что делается сейчас с постоянными арифметическими выражениями, не так ли?

Да, это очень тривиальная функция после слияния мири.

Это подходящее место для запроса дополнительных функций std , которые становятся const ? Если это так, Duration:: { new , from_secs , from_millis } должны быть безопасными для получения const .

@remexre Самый простой способ сделать это, вероятно, сделать PR и попросить там просмотреть команду libs.

Пиарится как https://github.com/rust-lang/rust/pull/47300. Я также добавил const в нестабильные конструкторы.

Любые мысли о том, чтобы разрешить объявление дополнительных функций std const ? В частности, mem::uninitialized и mem::zeroed ? Я считаю, что оба они являются подходящими кандидатами на дополнительные функции const . Единственный недостаток, о котором я могу думать, - это тот же недостаток mem::uninitialized , где создание структур, реализующих Drop , создается и перезаписывается без ptr::write .

Я также могу приложить PR, если это звучит уместно.

Какова мотивация для этого? Это кажется бесполезным футганом, позволяющим создавать недопустимые битовые шаблоны, которые затем нельзя перезаписать (потому что они в константе), но, возможно, я упускаю из виду очевидное.

mem::uninitialized — это абсолютный ножной ружье, которое также стреляет сквозь ваши руки, если его неправильно прицелить. Серьезно, я не могу преувеличить, насколько невероятно опасным может быть использование этой функции, несмотря на то, что она помечена как unsafe .

Мотивация объявления этих дополнительных функций const проистекает из природы этих функций, поскольку вызов mem::uninitialized<Vec<u32>> каждый раз будет возвращать один и тот же результат без побочных эффектов. Очевидно, что если оставить его неинициализированным, это ужасная вещь. Следовательно, unsafe все еще присутствует.

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

use std::time::Instant;

pub struct GlobalTimer {
    time: UnsafeCell<Instant>
}

impl TimeManager {
    pub const fn init() -> TimeManager {
        TimeManager {
            time: UnsafeCell::new(Instant::now())
        }
    }
}

Этот код не компилируется из-за того, что Instant::now() не является функцией const . Замена Instant::now() на mem::uninitialized::<Instant>()) устранила бы эту проблему, если бы mem::uninitialized было const fn . В идеале разработчик должен инициализировать эту структуру, как только программа начнет выполнение. И хотя этот код считается неидиоматической ржавчиной (глобальное состояние, как правило, очень плохое), это лишь один из многих случаев, когда глобальные статические структуры полезны.

Я думаю, что этот пост дает хорошую основу для будущего запуска кода Rust во время компиляции. Глобальные статические структуры времени компиляции — это функция с некоторыми важными вариантами использования (на ум также приходят ОС), которые в настоящее время отсутствуют в ржавчине. К этой цели можно сделать небольшие шаги, постепенно добавляя const к библиотечным функциям, которые считаются подходящими, например, mem::uninitialized и mem::zeroed , несмотря на их маркировку unsafe .

Изменить: забыл const в сигнатуре функции TimeManager::init()

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

const fn foo() -> Whatever {
    unsafe { 
        let mut it = mem::uninitialized();
        init_whatever(&mut it);
        it
    }
}

Но const fns в настоящее время настолько ограничены, что вы даже не можете написать это...

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

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

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

С помощью union предыдущий код теперь компилируется . Это вообще ужас 😅.

Также все хорошие моменты. Эти встроенные функции находятся довольно далеко в списке вариантов использования, но они все еще являются подходящими кандидатами на возможную const ность.

Это ужасно удивительно.

Итак... почему именно вы выступаете за объединение mem::uninitialized, как
в отличие, скажем, от Instant::now? :)

Необходимость иметь постоянные инициализаторы для структур с непостоянными
интерьеры реальны (см.: Mutex). Но я не думаю, что это обман
легче это правильный способ получить это!

В четверг, 25 января 2018 г., в 2:21, Стивен Флейшман <
уведомления@github.com> написал:

С помощью союзов предыдущий код теперь компилируется
https://play.rust-lang.org/?gist=be075cf12f63dee3b2e2b65a12a3c854&version=nightly .
Это вообще ужас 😅.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-360382201 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAC3n-HyWD6MUEbfHkUUXonh9ORGPSRoks5tOCtegaJpZM4D66IA
.

Instant::now не может быть константой. Что вернет эта функция? Время составления?

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

Интеграция с шаблонами (например, https://gist.github.com/d0ff1de8b6fc15ef1bb6)

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

Instant::now не может быть константой. Что вернет эта функция? Время составления?

Но может быть Instant::zero() или Instant::min_value() , которые являются константами.

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

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

Можем ли мы сделать интеграцию с шаблонами через rust-lang/rfcs#2272? Паттерны уже болезненны в том виде, в каком они есть сейчас, давайте не будем делать их еще более болезненными.

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

Поправьте меня, если я ошибаюсь, но разве эти проверки не идентичны проверкам того, что в настоящее время разрешено в теле const rvalue? У меня сложилось впечатление, что const FOO: Type = { body }; и const FOO: Type = foo(); const fn foo() -> Type { body } идентичны в том, что они допускают для любого произвольного тела.

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

Вы предлагаете вместо этого const generics (в обоих направлениях)? (например, <const T> + const C<T> , также известный как const C<const T> ?)

Мне бы очень хотелось иметь макрос try_const! , который будет пытаться оценить любое выражение во время компиляции и паниковать, если это невозможно. Этот макрос сможет вызывать неконстантные fns (используя miri?), поэтому нам не нужно ждать, пока каждая функция в std будет помечена как const fn. Однако, как следует из названия, он может выйти из строя в любое время, поэтому, если функция обновлена ​​и теперь не может быть константой, она перестанет компилироваться.

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

Если бы функция изначально была помечена как const fn , то автор ящика заметил бы проблему непосредственно при попытке скомпилировать ящик, и вы можете положиться на аннотацию.

Если бы это работало на игровой площадке... https://play.rust-lang.org/?gist=6c0a46ee8299e36202f959908e8189e6&version=stable

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

Переносимым способом было бы разрешить SystemTime::now() в оценке const.

(Это аргумент для const/compile-time-eval ЛЮБОЙ функции/выражения, независимо от того, const fn это или нет.)

Для меня это звучит как аргумент в пользу запрета абсолютных путей в include_bytes 😃

Если вы разрешите SystemTime::now в const fn, const FOO: [u8; SystemTime::now()] = [42; SystemTime::now()]; выдаст случайную ошибку в зависимости от производительности вашей системы, планировщика и положения Юпитера.

Еще хуже:

const TIME: SystemTime = SystemTime::now();

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

И еще более безумно то, что вы можете испортить foo.clone() очень неразумными способами, потому что вы можете в конечном итоге выбрать клон из массива с длиной 3, но возвращаемый тип может быть массивом с длиной 4.

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

@SoniEx2 Я думаю, это немного не по теме, но вы можете реализовать что-то подобное уже сегодня, используя файл груза build.rs . См. Build Scripts в Cargo Book, в частности раздел, посвященный примерам генерации кода.

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

Пожалуйста, не позволяйте получать текущее время в const fn ; нам не нужно добавлять дополнительные/более простые способы сделать сборку невоспроизводимой.

Мы не можем допустить какой-либо недетерминизм (например, случайные числа, текущее время и т. д.) в const fn — допущение этого приводит к несостоятельности системы типов, поскольку rustc предполагает, что константные выражения всегда дают один и тот же результат. тот же ввод. См. здесь для немного больше пояснений.

Будущий метод обработки случаев, как в https://github.com/rust-lang/rust/issues/24111#issuecomment -376352844, будет заключаться в использовании простого процедурного макроса, который получает текущее время и выдает его как простое число или токен строки. Процедурные макросы — это более или менее полностью неограниченный код, который может получить время любым из обычных переносимых способов, которые может использовать неконстантный код Rust.

@rfcbot fcp слияние

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

@rfcbot fcp merge от имени @oli-obk - кажется стоит подумать о стабилизации и обсудить вопросы

Член команды @nrc предложил объединить это. Следующий шаг — просмотр остальными отмеченными командами:

  • [x] @атурон
  • [х] @cramertj
  • [ ] @эддиб
  • [х] @joshtriplett
  • [ ] @никомацакис
  • [х] @nrc
  • [ ] @pnkfelix
  • [х] @scottmcm
  • [x] @без лодок

Обеспокоенность:

  • дизайн (https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588)
  • параллельные константы, разрешенные https://github.com/rust-lang/rust/issues/24111#issuecomment -377133537
  • приоритет (https://github.com/rust-lang/rust/issues/24111#issuecomment-376652507)
  • адреса указателя времени выполнения (https://github.com/rust-lang/rust/issues/24111#issuecomment-386745312)

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

См. этот документ для получения информации о том, какие команды могут давать мне члены команды, отмеченные тегами.

@rfcbot заботится о приоритете

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

@rfcbot касается всего-константа

Мы оказываемся в некотором роде мира C++, где есть стимул сделать каждую функцию, которую вы можете, const .

Краткое изложение краткого обсуждения с @oli-obk:

  1. В будущем почти каждая функция может быть помечена как const . Например, все на Vec может быть const . В этом мире может иметь смысл вообще избавиться от ключевого слова const : почти все может быть const , и нужно будет приложить все усилия, чтобы изменить функцию с const на non -const, так что опасность обратной совместимости, связанная с предполагаемой константностью, вероятно, не будет очень высокой.

  2. Однако избавиться от const сегодня невозможно. Сегодняшний мири не может все интерпретировать, и он не очень тщательно тестируется в продакшене.

  3. На самом деле обратно совместимо требовать const сегодня, а затем отказаться от этого ключевого слова и переключиться на предполагаемую константу в будущем.

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

Что касается опасностей Fallout: const fn широко используется в nightly, особенно во встроенных. Кроме того, средство проверки const fn — это то же самое средство проверки, которое используется для статических инициализаторов и констант (за исключением некоторых статических проверок и аргументов функций).

Главный недостаток, который я вижу, заключается в том, что мы, по сути, выступаем за обильное распыление const по ящикам (на данный момент см . пост @matklad для будущих идей

@rfcbot относится к параллельным константам

Кажется, что стабилизация этого немедленно приведет к тому, что куча ящиков создаст параллельную иерархию признаков с Const впереди: ConstDefault , ConstFrom , ConstInto , ConstClone , ConstTryFrom , ConstTryInto и т. д. и просят ConstIndex и т. д. Это не страшно — у нас, конечно, есть это немного с Try сегодня, хотя стабилизация TryFrom поможет — но я чувствую, что было бы неплохо иметь хотя бы набросок плана для более приятного решения. (Это https://github.com/rust-lang/rfcs/pull/2237? Я не знаю).

( @nrc : Похоже, бот зарегистрировал только одну из ваших проблем)

Parallel-const-traits имеют тривиальное решение в гипотетической будущей версии const-all-the-things. Они бы просто работали.

В мире const fn вы не столкнетесь с дублированием признаков, пока мы не разрешаем методы типаж const fn (которые мы не разрешаем), просто потому, что вы не можете. Конечно, вы могли бы создать связанные константы (по ночам), что похоже на ситуацию, в которой libstd был год назад, когда у нас была куча констант для инициализации различных типов внутри статики/констант, без раскрытия их закрытых полей. Но это то, что могло уже происходить какое-то время и не происходило.

Чтобы было ясно, ConstDefault уже сегодня возможен без const fn , а остальные примеры ( ConstFrom , ConstInto , ConstClone , ConstTryFrom , ConstTryInto ) будет невозможно даже при стабилизации этой функции, поскольку она не добавляет константные методы черт, как упоминалось @oli-obk.

( ConstDefault возможно при использовании связанной константы, а не связанной константы fn, но, насколько я знаю, она эквивалентна по мощности.)

@scottmcm const fn в определениях черт сегодня невозможно (о , @solson уже упоминал об этом).

@eddyb случайная идея: что, если бы мы сделали возможным const impl черту вместо добавления const fn в определения черты? (Эти два тоже не исключают друг друга.)

@whitequark https://github.com/rust-lang/rfcs/pull/2237 охватывает эту идею за счет комбинации const impl , расширяющейся до const fn на каждом fn в impl и позволяя impl со всеми методами const удовлетворять границе T: const Trait , не помечая ни один из методов const в само определение признака.

Дизайн концерна @rfcbot

Исторически мы делали ставку на стабилизацию любой конкретной системы const fn по нескольким причинам:

  • текущий не поддерживает trait s, которые требуют методов const fn , или trait impl s, которые предоставляют методы const fn (см. https://github. com/rust-lang/rfcs/pull/2237 для некоторых способов сделать это)
  • соответственно, существует проблема запроса метода, используемого через T: Trait , привязанного к const fn без отдельных трейтов, и предпочтительно только при использовании во время компиляции (например, Option::map будет работать так же во время выполнения, но потребует закрытия, вызываемого константой в CTFE)
  • с большим количеством алгоритмов, коллекций и абстракций, потенциально допускающих константную оценку, могут быть целые ящики, которые будут использовать const fn везде (на ум приходит libcore )

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

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

    • плюсы: более чистая кодовая база, постоянная оценка уже может дать сбой в зависимости от задействованных значений

    • минусы: нет документации по поведению на уровне языка и границы semver, вы можете бросить любой другой код в miri и наблюдать за любыми незначительными изменениями, которые они сделали, например, ведение журнала отладки во время выполнения добавляется в версию исправления одной из ваших зависимостей; Кроме того, продвигать rvalue сложнее

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

    • тела этих функций будут (по крайней мере, с точки зрения const fn ) вести себя как макросы

    • плюсы: отсутствие последствий, ограничение объема некоторых анализов

    • минусы: все еще не очень хорошая документация, неясно, какое поведение должно быть затронуто ( просто константная оцениваемость?), любое изменение в теле может считаться semver-breaking

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

@leoschwarz , разве это уже не проблема с автоматическими чертами? Возможно, решение этой проблемы состоит в том, чтобы интегрировать rust-semverver с грузом, чтобы обнаруживать такого рода непреднамеренные поломки.

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

@nrc Я думаю, что «все-const» верно, но это не проблема. Да, в конечном итоге мы пометим огромное количество вещей const .

Просто хочу отметить, что я не уверен, что хочу, чтобы все выведенное было
константа Это решение о том, что важнее: время выполнения или время компиляции.
важный. Иногда мне кажется, что компилятор делает достаточно вычислений при
время компиляции!

В среду, 28 марта 2018 г., в 14:49, Джош Триплетт, [email protected]
написал:

@nrc https://github.com/nrc Я думаю, что «все-const» верно, но не
проблема. Да, мы закончим тем, что пометим огромное количество вещей const.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-376914220 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAC3ny9Wm9JK6p-fXf6gbaEgjFtBpMctks5ti6LigaJpZM4D66IA
.

срок оценки

этот предел скоро исчезнет.

Просто хочу отметить, что я не уверен, что хочу, чтобы все выведенное было константным.

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

@rfcbot разрешил параллельные константы-черты

Спасибо за поправки, друзья!

этот предел скоро исчезнет.

Потрясающий. В этом случае auto-const-fn (в сочетании с некоторой интеграцией rust-semverver или подобным для предоставления информации о поломке) звучит потрясающе, хотя «добавить ведение журнала и вызвать поломку» может быть проблематичным. Хотя вы можете увеличить номер версии, я думаю, это не значит, что они конечны.

Регистрация и печать являются «прекрасными» побочными эффектами в моей модели констант. Мы сможем найти решение, если все согласятся. Мы могли бы даже писать в файлы (не совсем так, но действовать так, как будто мы это сделали, и все выбросить).

Я действительно беспокоюсь о том, чтобы молча избавиться от побочных эффектов.

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

Меня немного беспокоит подход «просто сделайте предупреждение» к предполагаемым
постоянство. Если автор ящика, никогда не задумывавшийся о константности, увидит
"предупреждение: изменение, которое вы только что сделали, делает невозможным вызов foo() в
const контекст, который раньше был возможен», увидят ли они это как
non-sequitur и заставить его замолчать? Ясно, что люди в этом вопросе часто думают
о том, какие функции могут быть константами. И было бы хорошо, если бы больше людей
что (как только const_fn будет стабильным). Но внезапные предупреждения правильные
способ поощрять это?

В четверг, 29 марта 2018 г., в 4:36, Оливер Шнайдер, [email protected]
написал:

Мы можем обсудить это, как только создадим для них RFC. А пока ты просто
не может иметь "побочных эффектов" в константах. Тема ортогональна
стабилизирующий const fn


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-377164275 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAC3n3MtmvrDF42Iy0nhZ2q8xC-QGcvXks5tjJ0ggaJpZM4D66IA
.

Я думаю, что явный const fn может раздражать и загромождать многие API, однако я думаю, что альтернатива неявного предположения имеет слишком много проблем, чтобы быть осуществимой:

  • Отказ от побочных эффектов может привести к тому, что код будет вести себя по-разному (скажем, писать, а затем читать файл), если он называется константным или неконстантным.
  • Вызов внешних функций означает, что всегда могут быть побочные эффекты, поэтому небезопасный код, вероятно, никогда не может быть выведен как const fn.
  • Когда можно вывести const fn для универсального кода? Будет ли это сделано во время мономорфизации?

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

Возможно, такая функция может быть реализована в виде флага конфигурации на уровне ящика, который можно добавить в корень ящика, #![infer_const_fn] или что-то в этом роде, и оставаться включенным навсегда. Если флаг добавляется, const fn будет выводиться, где это возможно, в ящике, а также отражаться в документах (и это потребует, чтобы вызываемые функции также были константными fn), если автор ящика добавляет этот флаг, они как бы обязуются быть осторожными. о версиях и, возможно, rust-semverver можно даже принудительно.

Как насчет того, чтобы сделать это в обратном порядке?

Вместо const fn используйте side fn.

Он по-прежнему явный (вам нужно поставить сторону fn, чтобы вызвать сторону fn, явно нарушая совместимость), и устраняет беспорядок. (Некоторые) встроенные функции и все, что связано с ассемблером, будет побочным fn.

Это не обратно совместимо, хотя, я думаю, его можно добавить в выпуск?

30 марта 2018 г., 2:43:06 GMT+08:00, «Сони Л.» уведомления@github.com написал:

Как насчет того, чтобы сделать это в обратном порядке?

Вместо const fn используйте side fn.

Это все еще явно (вам нужно поставить сторону fn, чтобы вызвать сторону fn,
явным образом нарушая совместимость), и устраняет беспорядок. (Немного)
Внутренности и все, что связано с ассемблером, будет побочной фн.

--
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую или просмотрите его на GitHub:
https://github.com/rust-lang/rust/issues/24111#issuecomment-377333542

--
Отправлено с моего устройства Android с помощью K-9 Mail. Пожалуйста, простите меня за краткость.

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

@whitequark Я не согласен ни с чем, что просто делает это («отбросьте побочные эффекты»), я думаю, что @oli-obk говорил о будущем расширении, но из обсуждений, в которых я участвовал, я знаю следующее

  • мы можем различать «детерминированные побочные эффекты» (которые не возвращают вам нечистые данные)
  • у нас могли бы быть специальные API, если бы мы хотели, например, "регистрировать данные во время компиляции"

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

  • в частности, мы бы не трогали ничего из того, что в настоящее время существует и использует глобалы/C FFI

РЕДАКТИРОВАТЬ : просто чтобы обсуждение не сошло с рельсов, например:

Отказ от побочных эффектов может привести к тому, что код будет вести себя по-разному (скажем, писать, а затем читать файл), если он называется константным или неконстантным.

Мы можем (вероятно?) все предположить, что @oli-obk оговорился относительно отказа от таких побочных эффектов.

Возможно, такая функция может быть реализована в виде флага конфигурации уровня ящика, который можно добавить в корень ящика.

Это подмножество второго примера прошлых предложений с https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588.
Если у нас есть «флаг конфигурации» с областью действия, пользователь должен иметь возможность выбирать более мелкие области действия IMO.

Как насчет того, чтобы сделать это в обратном порядке?
Вместо const fn используйте side fn.
Он по-прежнему явный (вам нужно поставить сторону fn, чтобы вызвать сторону fn, явно нарушая совместимость), и устраняет беспорядок. (Некоторые) встроенные функции и все, что связано с ассемблером, будет побочным fn.

В https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588 я попытался указать, что целые библиотеки могут быть «всеми const fn » или «всеми side fn ». ".
Если бы это было не в объявлениях функций, а скорее в области видимости, это могло бы работать в будущей версии.
Однако без семантики «вывести из тела» вам придется проектировать взаимодействие черт даже для подписки side fn , так что вы ничего не выигрываете и создаете потенциально серьезные трения.

Раздел 3.3 статьи Кентона Варды «Одиночки считаются вредными» кажется здесь уместным (честно говоря, стоит прочитать все целиком).

Как насчет ведения журнала отладки?

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

С точки зрения безопасности ведение журнала отладки — безобидный синглтон. Его нельзя использовать в качестве канала связи, поскольку он предназначен только для записи. И явно невозможно причинить какой-либо ущерб записью в журнал отладки, поскольку ведение журнала отладки не является фактором корректности программы. Даже если вредоносный модуль «заспамил» журнал, сообщения от этого модуля можно легко отфильтровать, поскольку журналы отладки обычно точно определяют, какой модуль создал каждое сообщение (иногда они даже предоставляют трассировку стека). Поэтому проблем с его предоставлением нет.

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

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

Мое заявление о том, что «мы можем найти решение [для отладки]», действительно относилось к потенциальному будущему API, который можно вызывать из констант, но который имеет некоторую форму печати. Произвольная реализация специфичных для платформы операций печати (только для того, чтобы мы могли сделать существующий код с операторами печати/отладки константными) — это не то, чем должен заниматься оценщик констант. Это было бы чисто согласием, явно не имеющим другого наблюдаемого поведения (например, предупреждения в const eval и вывод командной строки/файла во время выполнения). Точная семантика оставлена ​​для будущих RFC и должна считаться полностью ортогональной const fn в целом.

Есть ли существенные недостатки у const impl Trait и T: const Trait ?

Помимо еще большего разбрызгивания около const , только некоторые методы трейтов могут быть константными, что потребует более тонкого контроля. Я не знаю аккуратного синтаксиса, чтобы указать это. Возможно, where <T as Trait>::some_method is const ( is может быть контекстным ключевым словом).

Опять же, это ортогонально const fn.

[u8; SizeOf<T>::Output]

Если const и side fns разделены, мы должны принять во внимание фактические соображения дизайна. Самый простой способ разделить их — сделать const fns расширением того, что у нас есть сегодня — полной по Тьюрингу системы типов.

В качестве альтернативы, сделайте const fns действительно константой: любая константа fn должна оцениваться так, как если бы каждый параметр был универсальным константой.

Это делает их намного легче рассуждать, потому что я лично не могу рассуждать о const fns в том виде, в каком они есть в настоящее время. Я могу рассуждать о полных по Тьюрингу типах, макросах, обычных fns и т. д., но не могу рассуждать о const fn, поскольку даже незначительные детали полностью меняют свое значение.

так как даже незначительные детали полностью меняют свое значение.

Не могли бы вы уточнить? Вы имеете в виду расширения, такие как указатели const const fn границы признаков const, ...? Потому что я не вижу никаких мелких деталей в голом предложении const fn .

В качестве альтернативы, сделайте const fns действительно константой: любая константа fn должна оцениваться так, как если бы каждый параметр был универсальным константой.

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

Проблема в том, что любая незначительная деталь может превратить const eval в runtime eval. Поначалу это может показаться не таким уж большим делом, но может быть .

Допустим, вызов функции очень длинный, потому что он состоит из const fns? И вы хотите разделить его на несколько строк.

Таким образом, вы добавляете несколько let s.

Теперь ваша программа выполняется в 20 раз дольше.

@SoniEx2 Размер массивов ( $N в [u8; $N] ) всегда оценивается во время компиляции. Если это выражение не- const , компиляция завершится ошибкой. И наоборот, let x = foo() будет вызывать foo во время выполнения, независимо от того, является ли он const fn (по модулю встраивания оптимизатора и распространения констант, но это совершенно отдельно от const fn ). Если вы хотите назвать результат вычисления некоторого выражения во время компиляции, вам нужен элемент const .

Теперь ваша программа выполняется в 20 раз дольше.

Const fn работает совсем не так!

Если вы объявите функцию const fn и добавите в нее привязку let , ваш код перестанет компилироваться.

Если вы удалите const из const fn , это будет критическим изменением и нарушит все использование этой функции внутри, например, const , static или длины массива. Ваш код, который был кодом времени выполнения и запускал const fn , никогда не запустится во время компиляции. Это обычный вызов функции во время выполнения, поэтому он не становится медленнее.

Редактировать: @SimonSapin опередил меня: D

Const fn оценивается во время компиляции, если это возможно.

То есть,

const fn random() -> i32 {
    4
}

fn thing() -> i32 {
    let i = random(); // the RHS of this binding is evaluated at compile-time, there is no call to random at runtime.
}

Теперь предположим, что у вас есть константа fn, которая принимает аргументы. Это будет оцениваться во время компиляции:

fn thing() {
    let x = const_fn_with_1_arg(const_fn_returns_value());
}

Это приведет к тому, что const_fn_with_1_arg будет оцениваться во время выполнения:

fn thing() {
    let x = const_fn_returns_value();
    let y = const_fn_with_1_arg(x); // suddenly your program takes 20x longer to run, and compiles 20x faster.
}

@eddyb Интересно, можно ли решить проблему дизайна, заметив, что «минимальная константа fn» совместима со всеми потенциальными будущими расширениями? То есть я так понимаю, что мы хотим стабилизировать

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

Кажется, что это тривиально полностью совместимо с любым дизайном «константных эффектов для черт». Он также совместим с дизайном "inferred const", потому что позже мы можем сделать const необязательным.

Существуют ли какие-либо альтернативные будущие конструкции, несовместимые с текущим предложением «минимальной константы fn»?

@nrc

Обратите внимание, что rfcbot не зарегистрировал вашу проблему everything-const (одна проблема на комментарий!) Однако, похоже, это подмножество проблемы дизайна, которая рассматривается в моем предыдущем комментарии (TL; DR: текущее минимальное предложение полностью совместим со всем, мы могли бы сделать ключевое слово const необязательным в будущем).

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

  • const fn foo(x: i32) -> i32 { body } — относительно незначительное дополнение к const FOO: i32 = body; , поэтому риск выпадения невелик. То есть большая часть кода, который на самом деле реализует const fn , уже усердно работает в стабильном компиляторе (отказ от ответственности: это то, что я слышал от @oli-obk, я мог услышать неправильно).

  • встроенная рабочая группа очень хочет const fn :)

Кроме того, обратите внимание, что отсутствие стабилизации const fn приводит к распространению неоптимальных API в библиотеках, потому что им приходится использовать такие приемы, как ATOMIC_USIZE_INIT , чтобы обойти отсутствие константных fns.

пусть я = случайный(); // правая сторона этой привязки оценивается во время компиляции, случайный вызов во время выполнения отсутствует.

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

поэтому const fn оценивается только в константе, и в противном случае это в основном бесполезно?

почему бы тогда не разделить const и non-const fn?

видите, что семантика - это беспорядок, потому что они намеренно смешивают время компиляции и время выполнения.

поэтому const fn оценивается только в константе, и в противном случае это в основном бесполезно?

Они не бесполезны, они выполняются во время выполнения, как и любая другая функция. Это означает, что вам не нужно использовать разные «подъязыки» Rust в зависимости от того, находитесь ли вы в постоянной оценке или нет.

почему бы тогда не разделить const и non-const fn?

Вся мотивация для const fn состоит в том, чтобы не иметь этого разделения. В противном случае нам пришлось бы дублировать все виды функций: AtomicUsize::new() + AtomicUsize::const_new() , хотя оба тела идентичны.

Вы действительно хотите написать 90% libcore дважды, один раз для const eval и один раз во время выполнения? То же самое, вероятно, касается и многих других ящиков.

Я думал AtomicUsize::Of<value> . И да, я лучше буду писать все дважды, чем иметь неизвестные гарантии. (Кроме того, это не будет вести себя по-разному в зависимости от того, оценивается ли что-то или нет.)

Можете ли вы объявить константы в const fn, чтобы гарантировать оценку константы (для рекурсивной константы fn)? Или вам нужно пройти через const generics? И т.п.

@SoniEx2 в качестве примера того, как ваш пример должен быть написан, чтобы использовать преимущества const fn и превращаться в ошибку времени компиляции, если какая-либо функция становится не- const :

fn thing() {
    const x: u32 = const_fn_returns_value();
    const y: u32 = const_fn_with_1_arg(x);
}

(полный рабочий пример на детской площадке)

Чуть менее эргономично, потому что нет вывода типа, но кто знает, может в будущем это изменится.

чем иметь неизвестные гарантии.

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

Можете ли вы объявить константы в const fn, чтобы гарантировать оценку константы (для рекурсивной константы fn)? Или вам нужно пройти через const generics? И т.п.

Смысл const fn не в том, чтобы волшебным образом оценивать вещи во время компиляции. Это чтобы иметь возможность оценивать вещи во время компиляции.

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

const fn никак не влияет на распространение константы. Если бы у вас была функция, которая случайно могла быть распространена как константа, и llvm сделал это, и вы изменили эту функцию таким образом, чтобы она больше не распространялась как константа, llvm прекратила бы это делать. Это совершенно не зависит от присоединения const к функции. Для llvm нет разницы между const fn и fn .

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

Я не думаю о LLVM, я думаю о ржавчине. LLVM для меня здесь не имеет значения.

@SoniEx2

Const fn оценивается во время компиляции, если это возможно.

Это неправильно. const fn при вызове в определенном контексте БУДЕТ оцениваться во время компиляции. Если это невозможно, это серьезная ошибка. Во всех других контекстах часть const вообще не имеет значения.

Примерами таких контекстов, требующих const fn , являются длины массивов. Вы можете написать [i32; 15] . Вы также можете написать [i32; 3+4] , потому что компилятор может вычислить 7 . Вы не можете написать [i32; read_something_from_network()] , потому что какой в ​​этом смысл? С этим предложением вы МОЖЕТЕ написать [i32; foo(15)] , если foo равно const fn , что гарантирует, что это больше похоже на сложение, а не на доступ к сети.

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

Также прочитайте RFC: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md .

Что, если бы вместо аннотации const fn это было предполагаемое свойство? Это не было бы явным в исходном коде, но могло быть автоматически помечено как таковое в автоматически сгенерированной документации. Это позволит в конечном итоге расширить то, что считается const , без необходимости авторам библиотек изменять свой код. Сначала вывод можно было ограничить теми const fn , которые поддерживаются в настоящее время (чистые, детерминированные функции без каких-либо привязок let?).

При таком подходе оценка будет происходить во время компиляции, если результат привязан к переменной const , и во время выполнения в противном случае. Это кажется более желательным, так как дает вызывающему (а не вызываемому) контроль над вычислением функции.

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

Хм, да, это проблема...

Изменить: единственное решение, которое я могу придумать, не переходя полностью к const fn , состояло бы в том, чтобы иметь аннотацию отказа, чтобы автор библиотеки мог оставить за собой право сломать const нэсс. Хотя я не уверен, что это лучше, чем рассыпать повсюду const fn . Единственным реальным преимуществом будет более быстрое принятие расширенного определения const .

Редактировать 2: я полагаю, что это нарушит обратную совместимость, так что это не начало. Извините за отступление.

Итак... дискуссия утихла. Подведем итог:

комментарий rfcbot https://github.com/rust-lang/rust/issues/24111#issuecomment -376649804

Текущие проблемы

  • Это не является приоритетом, и выпуск 2018 уже положил на наши тарелки достаточно
  • Мы начинаем отмечать все const , что раздражает
  • Это тот дизайн, которому мы хотим посвятить себя?

Я не могу говорить о приоритете + беспокойстве о последствиях, за исключением того, что const fn испекся в nightly долгое-долгое время.

Два других пункта тесно связаны. Дизайн @eddyb (https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588, насколько я понял) состоит в том, чтобы не иметь const fn , а вместо этого иметь #[const] Атрибут

#[const]
mod foo {
    pub fn square(i: i32) -> i32 { i * i }
}
#[const]
fn bar(s: &str) -> &str i{ s }
#[const]
fn boo() -> fn(u32) -> u32 { meh }
fn meh(u: u32) -> u32 { u + 1 }

и это рекурсивно входит во все, что помечено им, поэтому любая функция внутри модуля #[const] является #[const] fn . Функция, объявленная внутри #[const] fn , также является #[const] fn .

Это уменьшает количество необходимых аннотаций, так как некоторые ящики просто вставят #![const] в lib.rs и покончат с этим.

Проблемы, которые я вижу с этим дизайном (но эти проблемы также часто существуют в const fn ):

  • может потребоваться поддержка отказа, потому что вы можете захотеть иметь возможность объявлять несколько неконстантных функций глубоко внутри дерева модулей, которое в противном случае #[const] .
  • должны ли указатели функций на #[const] fn в позиции типа аргумента/возврата быть #[const] fn ?

    • опять же, отказ был бы необходим

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

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

РЕДАКТИРОВАТЬ : (не хочу об этом забывать) @solson показывал мне, как Lean имеет такие атрибуты, как @pattern , которые автоматически получают различные вещи из тела функции.

@oli-obk Я думаю, нам не следует использовать атрибуты, потому что unsafe не использует атрибут.
Кроме того, async настоящее время тоже не работает. И если мы введем try fn для заданных блоков try { .. } , то у нас будет еще одна вещь, которая не основана на атрибутах. Я думаю, мы должны стараться оставаться как можно более последовательными в том, что похоже на эффект. использовать атрибуты или нет. #[target_feature(..)] действительно портит общую согласованность.

PS: Вы можете использовать const mod { .. } , чтобы получить тот же эффект, что и #![const] , более или менее. Это также может относиться к try mod , async mod , unsafe mod .

Я всегда буду склоняться к работе со специальными типами.

struct SizeOf<T>;

impl<T> SizeOf<T> {
    const intrisic Result: usize;
}

легче изучать новые типы, используя существующий синтаксис, чем изучать новые концепции с новым синтаксисом.

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

fn sq(v: i32) -> i32 {
    Square<v>::Result
}

типы во время компиляции, const generics либо во время компиляции, либо во время выполнения.

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

  1. Легко рассуждать о
  2. Предварительная совместимость с любыми более либеральными конструкциями
  3. Исторически с большим успехом использовался в нестабильном коде, при этом основной жалобой было недостаточное количество функций.
  4. Можно линтинговать за предложение добавить аннотацию к еще не аннотированным функциям, имеющим тело, позволяющее ее добавить.
  5. Лично я, как указано в запросе на слияние, вижу единственный дизайн, который мы можем стабилизировать без еще одного многолетнего пробного периода.
  6. Позволяет совместно использовать код между исполняющей средой и константным кодом вместо того, чтобы требовать собственный код для каждого из них.

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

Это зависимая типизация, которая далека , хотя вызов const fn во время выполнения сегодня работает просто отлично.

@oli-obk А как насчет черт? Я не хочу стабилизировать const fn без какого -либо представления о том, что мы собираемся делать с методами признаков, которые имеют const fn только в некоторых из impl свойств признаков.

@eddyb кажется, что тогда мне следует ускорить написание новых границ и методов const. :)

@Centril Я хочу сказать, что предложение атрибутов (независимо от того, используется ли ключевое слово или нет) приведет к гораздо более другому подходу к работе с методами признаков, и мы должны сравнить это .
Нынешний подход const fn может показаться простым и расширяемым, но только не в том случае, когда он расширяется на самом деле.

универсальные константы + константные дженерики:

intrinsic const SizeOf<T>: usize;

const Pow<const V: usize>: usize = V*V;

@eddyb У меня есть различные решения, которые полностью совместимы с дизайном const fn. Я напишу это.

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

@rfcbot касается адресов указателя времени выполнения

В другом вопросе возник вопрос о том, хотим ли мы ссылочной прозрачности от const fn , и возникла проблема использования необработанных адресов указателей в качестве оракула недетерминизма: https://github.com/rust- lang/rust/issues/49146#issuecomment -386727325. Там описано решение, но оно включает в себя выполнение некоторых необработанных операций с указателями unsafe (не уверен, сколько из них даже разрешено сегодня) перед стабилизацией.

@eddyb Не будет ли E0018 применяться и к const fn s?

Способ C заключается в том, что все указатели объектов могут быть равны 0, если только они не являются относительными (т.е. внутри объекта) и каким-то образом отслеживаются во время выполнения.

Я не уверен, поддерживает ли ржавчина правила алиасинга C.

@sgrif Многие ошибки, связанные с константами, рано или поздно исчезнут - мири не волнует, как выглядит значение, абстрактное местоположение, которое находится в значении usize , по-прежнему является абстрактным местоположением (и приведение его к указателю возвращает исходный указатель).

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

@eddyb Достаточно честно. Тем не менее, я ожидаю, что любые ваши проблемы, которые достигли const fn , уже достигли любых блоков const сегодня.

@sgrif Разница в том, что const (даже связанные const , которые зависят от параметров универсального типа) полностью оцениваются во время компиляции под мири, в то время как const fn не является const fn для вызовов во время выполнения.
Поэтому, если мы действительно хотим ссылочной прозрачности, нам нужно убедиться, что мы не разрешаем (по крайней мере, в безопасном коде) вещи, которые могут вызвать недетерминизм во время выполнения, даже если они в порядке с miri .
Что, вероятно, означает, что получение битов с плавающей запятой также является проблемой, потому что, например, полезная нагрузка NaN.

Вещи, которые вы должны рассмотреть в Мири:

Все указатели равны 0, кроме относительных. Например:

#[repr(C)]
struct X {
    a: usize,
    b: u8,
}
let x = X { a: 1, b: 2 };
let y: usize = 3;
assert_eq!(&x as *const _ as usize, 0);
assert_eq!(&x.a as *const _ as usize, 0);
assert_eq!(&x.b as *const _ as usize, 8);
assert_eq!(&y as *const _ as usize, 0);

Затем вы отслеживаете их при оценке Мири. Некоторые вещи будут UB, например, переход от указателя к usize обратно к указателю, но их легко запретить (запретить преобразование usize в указатель, поскольку вы уже будете отслеживать указатели во время выполнения/оценки).

Что касается битов с плавающей запятой, нормализация NaN?

Я думаю, что оба из них сделают все это детерминированным.

Что, вероятно, означает, что получение битов с плавающей запятой также является проблемой, потому что, например, полезная нагрузка NaN.

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

детерминизм с плавающей запятой сложен

Наиболее важным моментом здесь является то, что оптимизатор LLVM может и действительно изменяет порядок операций с плавающей запятой, а также ~выполняет~ операции фьюзов, для которых у него есть комбинированный код операции. Эти изменения действительно влияют на результат операции, даже если фактическая разница незначительна. Это влияет на ссылочную прозрачность, потому что miri выполняет mir, не оптимизированный для llvm, в то время как цель выполняет код, оптимизированный для llvm и, возможно, переупорядоченный и, следовательно, имеющий другую семантику, чем собственный код.

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

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

Операции слияния (я предполагаю, что вы имеете в виду mul-add) не разрешены/не выполняются без флагов быстрой математики именно потому, что они изменяют округление результатов. LLVM очень внимательно следит за сохранением точной семантики операций с плавающей запятой при нацеливании на оборудование, совместимое с IEEE, в пределах, установленных «средой с плавающей запятой по умолчанию».

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

Другой известный мне важный случай, когда решение компилятора может иметь значение для результатов с плавающей запятой, — это расположение сбросов и перезагрузки в коде x87 (до SSE). И это в основном проблема, потому что x87 по умолчанию округляет до 80 бит для промежуточных результатов и округляет до 32 или 64 бит для хранения. Правильная установка режима округления перед каждой инструкцией FPU для достижения правильно округленных результатов возможна, но на самом деле это непрактично, и поэтому (я полагаю) LLVM не поддерживает это.

О полной ссылочной прозрачности

Я считаю, что мы должны использовать полную ссылочную прозрачность, потому что это согласуется с общей идеей Rust о выборе безопасности/правильности над удобством/полнотой .

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

Тем не менее, есть, конечно, недостатки wrt. проигрыш по полноте wrt. ХТФЭ. Под этим я подразумеваю, что ссылочно-прозрачный механизм const fn не сможет оценить столько же во время компиляции, как непрозрачная по ссылкам схема const fn . Я бы попросил тех, кто предлагает не придерживаться ссылочной прозрачности, предоставить как можно больше конкретных вариантов использования этого предложения, чтобы мы могли оценить компромиссы.

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

Тем не менее, все еще существует множество состояний , от которых зависят операции с плавающей запятой, например внутренняя точность. Знает ли оценщик с плавающей запятой CTFE значение внутренней точности во время выполнения?

Кроме того, при сбросе значений в память мы конвертируем внутреннее значение в формат ieee754 и тем самым меняем точность. Это тоже может повлиять на результат, а алгоритм выполнения компиляторами сброса не указан, не так ли?

@ est31 Обратите внимание, что я предполагал, что нам все равно, различаются ли поведение во время компиляции и во время выполнения, а только в том, что повторные вызовы (с замороженными графами объектов) согласованы и не имеют глобальных побочных эффектов.

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

Таким образом, цель здесь состоит в том, чтобы гарантировать, что const fn является детерминированным и свободным от побочных эффектов во время выполнения, даже если мири выдаст ошибку об этом во время выполнения , по крайней мере, если const fn полностью безопасен. код? Я никогда не считал это важным, ТБХ. Это довольно сильное требование, и действительно, тогда нам, по крайней мере, придется сделать ptr-to-int и float-to-bits небезопасными, что будет трудно объяснить. OTOH, тогда мы также должны сделать сравнение необработанных указателей небезопасным, и я был бы рад этому: D

Какова мотивация для этого? Похоже, мы пытаемся заново ввести чистоту и систему эффектов, которые когда-то были и потеряны в Rust, предположительно потому, что они не несли своего веса (EDIT: или потому, что он просто не был достаточно гибким, чтобы делать все разные вещи, для которых люди хотели его использовать, см. https://mail.mozilla.org/pipermail/rust-dev/2013-April/003926.html).

ptr-to-int безопасен, если он преобразуется из начала объекта, и вы допускаете сбой int-to-ptr. это было бы на 100% детерминировано. и каждый вызов будет давать одинаковые результаты.

ptr-to-int безопасно, int-to-ptr небезопасно. ptr-to-int должен допускать «неожиданные» результаты при оценке const .

и вам действительно следует использовать канонизацию NaN в интерпретаторе/VM. (примером этого является LuaJIT, который делает все результаты NaN каноническими NaN, а каждый другой NaN является упакованным NaN)

ptr-to-int безопасен, если он преобразуется из начала объекта, и вы допускаете сбой int-to-ptr. это было бы на 100% детерминировано. и каждый вызов будет давать одинаковые результаты.

Как вы предлагаете реализовать это во время выполнения? (&mut *Box::new(...)) as usize сейчас является недетерминированной функцией и должно быть равно const fn . Итак, как вы предлагаете нам убедиться, что он «ссылочно прозрачн во время выполнения» в том смысле, что всегда возвращает одно и то же значение?

Box::new должен возвращать отслеживаемый указатель в виртуальной машине.

Преобразование приведет к 0 во время компиляции (т.е. в константной оценке). В неконстантной оценке он вернет что угодно.

Отслеживаемый указатель работает следующим образом:

Вы выделяете указатель и назначаете его. Когда вы назначаете его, виртуальная машина устанавливает значение указателя в 0, но берет адрес памяти указателя и прикрепляет его к таблице поиска. Когда вы используете указатель, вы проходите через таблицу поиска. Когда вы получаете значение указателя (то есть байты, из которых он состоит), вы получаете 0. Предполагая, что это базовый указатель объекта.

Box::new должен возвращать отслеживаемый указатель в виртуальной машине.

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

Мири уже может справиться со всем этим просто отлично и полностью детерминировано.


Кроме того, возвращаясь к беспокойству о const fn и детерминированности времени выполнения: если мы хотим этого (что я не уверен, что мы этого хотим, все еще ожидая, что кто-то объяснит мне, почему нас это волнует :D), мы могли бы просто объявить ptr-to-int и float-to-bits должны быть неконстантными операциями. Есть ли какие-либо проблемы с этим?

@RalfJung Я связал https://github.com/rust-lang/rust/issues/49146#issuecomment -386727325 уже в комментарии к проблеме, возможно, это потерялось под другими сообщениями - оно включает описание решения, предложенного @Centril ( сделать операции unsafe и внести в белый список несколько «правильных применений», например, offset_of для ptr-to-int и NaN-нормализованного float-to-bits).

@eddyb конечно, есть разные решения; то, о чем я прошу, это мотивация. Я тоже не нашел его там.

Кроме того, цитируя себя из IRC: я думаю, что мы даже можем с полным правом утверждать, что CTFE является детерминированным, даже когда разрешено приведение ptr к int. По-прежнему требуется код, отличный от CTFE (например, извлечение битов ptr, приведенных к int), чтобы фактически наблюдать любую недетерминированность.

const fn thing() -> usize {
    (*Box::new(0)) as *const _ as usize
}

const X: usize = thing();
const Y: usize = thing();

X == Y?

с моим предложением, X == Y.

@SoniEx2 , который ломает foo as *const _ as usize as *const _ , что сейчас совершенно не работает

Срочная мотивация:
Лично я не вижу проблем с поведением во время выполнения, отличным от поведения во время компиляции, или даже с недетерминированностью const fn во время выполнения. Безопасность здесь не при чем, так что небезопасность — совершенно неправильное ключевое слово imo. Это просто удивительное поведение, которое мы полностью улавливаем при оценке времени компиляции. Вы уже можете сделать это в обычных функциях, так что никаких сюрпризов. Const fn помечает существующие функции как доступные для оценки во время компиляции, не влияя на время выполнения. Дело не в чистоте, по крайней мере, это то, что я почувствовал, когда люди говорили о «чистом аде» (меня не было рядом, поэтому я мог неправильно истолковать)

@SoniEx2 Да, X == Y всегда.

Однако, если у вас есть:

const fn oracle() -> bool { let x = 0; let y = &x as *const _ as usize; even(y) }

fn main() {
    assert_eq!(oracle(), oracle());
}

Тогда main может запаниковать во время выполнения.

@RalfJung

или потому, что он просто не был достаточно гибким, чтобы делать все те вещи, которые люди хотели использовать [...]

Что это были за вещи? Без конкретики это трудно оценить.

Я думаю, мы даже можем с полным правом утверждать, что CTFE детерминирован, даже когда разрешено приведение ptr к int.
[...]
По-прежнему требуется код, отличный от CTFE (например, извлечение битов ptr, приведенных к int), чтобы фактически наблюдать любую недетерминированность.

Да, const fn по-прежнему детерминирован при выполнении во время компиляции, но я не верю, что он детерминирован во время выполнения, потому что всегда будут некоторые не const fn (всего fn ). const fn должен быть полезен для чего-либо во время выполнения, а затем этот код fn будет наблюдать побочные эффекты от выполненного const fn .

Весь смысл разделения чистого и нечистого кода в Haskell заключается в том, что вы можете принять решение «будут ли побочные эффекты» локальным, и вам не нужно думать о возможных побочных эффектах в глобальном масштабе. То есть учитывая:

reverse :: [a] -> [a]
reverse []       = []
reverse (x : xs) = reverse xs ++ [x]

putStrLn :: String -> IO ()
getLine :: IO String

main :: IO ()
main = do
    line <- getLine
    let revLine = reverse line
    putStrLn revLine

Вы знаете, что результат revLine в let revLine = reverse line может зависеть только от состояния, переданного в reverse , которое равно line . Это обеспечивает преимущества рассуждений и четкое разделение.

Интересно, как тогда выглядела система эффектов... Я думаю, нам нужен один заданный const fn , async fn и т. д. в любом случае, чтобы сделать это чисто и получить повторное использование кода (что-то https:// github.com/rust-lang/rfcs/pull/2237, но с некоторыми изменениями...), и параметризация кажется хорошей идеей для этого.

Бессмертными словами Фила Уодлера и Конора Макбрайда:

Буду ли я чист или нечист?
— Филип Уодлер [60]

Мы говорим «Да»: чистота — это выбор, который нужно сделать на месте

https://arxiv.org/pdf/1611.09259.pdf

@oli-obk

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

В качестве консервативного варианта я предлагаю отложить решение о ссылочной прозрачности/чистоте, стабилизировав const fn как ссылочно-прозрачный, сделав его unsafe (или не- const ) нарушающим это, но на самом деле мы не гарантируем ссылочную прозрачность, поэтому люди не могут это предполагать.

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

Да, но это не константная оценка.

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

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

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

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

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

нормально, если const fns является подъязыком с другими правилами сглаживания указателей.

@Centril @est31 Я не уверен, какое отношение к этому имеет хлортрифторэтилен (можно ли нам избежать использования множества сокращений без их определения, пожалуйста?)

@sgrif CTFE (оценка функции во время компиляции) уже давно используется для Rust (в том числе в старых частях этой ветки). Это один из тех терминов, который уже должен быть определен где-то в центре.

@sgrif, лол, извините, в большинстве случаев я человек, который задается вопросом «какие странные слова они сейчас используют?». Кажется, я разговаривал с @eddyb в IRC, когда он упомянул «CTFE», и мне пришлось спросить его, что это значит :p.

@eddyb ctrl+f + CTFE на этой странице ни к чему не приводит. В любом случае, я в основном просто исхожу из предположения, что мы не хотим заставлять людей много копать, чтобы принять участие в дискуссиях здесь. «Вы уже должны знать, что это значит» — довольно исключающая ИМО.

@Centril @est31 спасибо :heart:

Итак... любые мысли о

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

Должен ли доступ к полям объединения внутри const fn быть нестабильным?

ctrl+f + CTFE на этой странице ничего не определяет.

Извините, я имел в виду, что его использование в дизайне и разработке Rust предшествовало этой проблеме до версии 1.0.
Должен быть центральный документ для таких терминов, но, к сожалению, справочный глоссарий действительно отсутствует.
HRTB и NLL — два примера других аббревиатур, которые являются более новыми, но также не имеют встроенного пояснения.

Я не хочу, чтобы какие-либо обсуждения были исключающими, но я не думаю, что справедливо просить @Centril или @est31 дать определение CTFE, поскольку ни один из них не ввел аббревиатуру в первую очередь.
Есть решение, которое, к сожалению, не обязательно будет работать на GitHub, которое я видел в одном сабреддите, где бот создает комментарий верхнего уровня и обновляет его со списком расширений для всех часто используемых сокращений. этого субреддита, появляющегося в обсуждении.

Кроме того, мне любопытно, нахожусь ли я в пузыре Google, поскольку две статьи в Википедии для CTFE («Хлортрифторэтилен» и «Выполнение функции времени компиляции») являются для меня первыми двумя результатами. Даже в этом случае первое начинается с «О функции компилятора см. Выполнение функции времени компиляции».

( Я поместил вики-ссылку CTFE вверху; теперь мы можем вернуться к const fn s? :P)

Может ли кто-нибудь добавить CTFE в глоссарий руководства rustc (я пользуюсь мобильным телефоном)?

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

@Центрил

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

even , который вы написали выше, потерпит неудачу, если будет выполнен во время CTFE, потому что он проверяет биты указателя. (Хотя на самом деле мы можем детерминистически сказать, что это даже из-за выравнивания, и если проверка четности выполняется с помощью битовых операций, «полный мир» сделает это правильно. Но давайте предположим, что вы проверяете весь младший значащий байт, а не только последний бит.)
Случай, о котором мы здесь говорим, — это функция, которая выдает ошибку интерпретатора во время компиляции, но завершается успешно во время выполнения. Разница заключается в том, завершает ли функция выполнение. Я не думаю, что это трудно объяснить.

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

@oli-obk

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

Я потерял контекст, что это за "эти"?

Сейчас, к счастью, CTFE miri просто наотрез отказывается делать что-либо со значениями указателя — арифметика, сравнение, все ошибки. Это проверка, выполняемая во время CTFE на основе значений, фактически используемых в вычислении, ее нельзя обойти с помощью союзов, и в любом случае код, необходимый для выполнения арифметики/сравнения, просто не существует . Следовательно, я почти уверен, что мы удовлетворяем гарантии, которую я изложил выше.

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

Мы могли бы аккуратно добавить операции в CTFE miri, и на самом деле все, что нам нужно для offset_of [1] @eddyb, — это вычитание указателя. Этот код существует в «полном мире», и он работает только в том случае, если оба указателя находятся внутри одного и того же распределения, что достаточно для сохранения гарантии выше. Что не сработает, так это assert , которые @eddyb добавил в качестве защиты.
Мы также могли бы разрешить битовые операции над значениями указателя, если операции затрагивают только выровненную часть указателя, которая все еще детерминирована, а код фактически уже существует в «полном мире».

РЕДАКТИРОВАТЬ: [1] Для справки, я имею в виду его макрос в этой ветке , на который мы не можем ссылаться, потому что он помечен как не относящийся к теме, поэтому вот копия:

macro_rules! offset_of {
    ($Struct:path, $field:ident) => ({
        // Using a separate function to minimize unhygienic hazards
        // (e.g. unsafety of #[repr(packed)] field borrows).
        // Uncomment `const` when `const fn`s can juggle pointers.
        /*const*/ fn offset() -> usize {
            let u = $crate::mem::MaybeUninit::<$Struct>::uninit();
            // Use pattern-matching to avoid accidentally going through Deref.
            let &$Struct { $field: ref f, .. } = unsafe { &*u.as_ptr() };
            let o = (f as *const _ as usize).wrapping_sub(&u as *const _ as usize);
            // Triple check that we are within `u` still.
            assert!((0..=$crate::mem::size_of_val(&u)).contains(&o));
            o
        }
        offset()
    })
}

EDIT2: На самом деле, он также разместил это здесь .

«это» преобразование с плавающей запятой -> биты и преобразование указателя -> использование размера

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

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

Это сделало бы нашу жизнь в миллион раз проще, тем более что я не вижу способа предотвратить недетерминизм, не оставив лазеек или не нанеся вред const fn способами ломающего изменения.

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

Да, это то, что я предлагаю.


Подумав об этом еще немного (и прочитав ответ @oli-obk, который появился, пока я писал это), у меня возникло ощущение, что вы хотите получить дополнительную гарантию вроде «безопасный const fn не будет ошибаться при Время CTFE (кроме паники) при вызове с допустимыми аргументами». Какая-то гарантия "константной безопасности". Вместе с заявленной выше гарантией об успешном выполнении CTFE, совпадающем с поведением во время выполнения, это обеспечит гарантию того, что безопасный const fn будет детерминированным во время выполнения, поскольку он соответствует успешному выполнению CTFE.

Я согласен, что труднее получить гарантию. Хорошо это или плохо, но в Rust есть различные безопасные операции, которые CTFE miri не может гарантировать всегда успешное выполнение при сохранении детерминизма, например вариант oracle @Centril , который проверяет, что младший значащий байт равен 0. С точки зрения CTFE в этом параметре «допустимые значения типа usize » представляют собой только значения, которые являются PrimVal::Bytes , в то время как PrimVal::Ptr не должно быть разрешено [1]. Это похоже на то, что у нас есть немного другая система типов в контексте const — я не предлагаю изменить то, что делает miri, я предлагаю изменить то, что «значат» различные типы Rust при прикреплении к const fn . Такая система типов гарантировала бы, что все безопасные арифметические и битовые операции не могут пойти не так в CTFE: для входных данных, допустимых CTFE, целочисленное вычитание никогда не может дать сбой в CTFE, потому что обе стороны равны PrimVal::Bytes . Конечно, в этом параметре ptr-to-int должна быть небезопасной операцией, потому что возвращаемое значение имеет тип usize , но не является допустимым по CTFE usize .

Если это гарантия, о которой мы заботимся, мне не кажется неразумным сделать систему типов более строгой при проверке функций CTFE; в конце концов, мы хотим использовать его для большего количества вещей (проверка «константной безопасности»). Я думаю, что тогда было бы разумно объявить приведение типов ptr к int unsafe в контексте const , утверждая, что это необходимо, потому что контекст const делает дополнительные гарантии.

Точно так же, как наша обычная «безопасность во время выполнения» может быть подорвана небезопасным кодом, так же может быть и «константная безопасность», и это нормально. Я не вижу никаких проблем с небезопасным кодом, который все еще может выполнять приведение ptr к int через union — в конце концов, это небезопасный код . В этом мире обязательства проверки небезопасного кода в константном контексте сильнее, чем обязательства в неконстантном контексте; если ваша функция возвращает целое число, вы должны доказать, что это всегда будет PrimVal::Bytes и никогда не будет PrimVal::Ptr .

Макросу @eddyb потребуется небезопасный блок, но его все равно будет безопасно использовать, потому что он использует только эти «константные небезопасные функции» (ptr-to-usize, а затем вычитание результата или просто использование встроенного вычитания указателя напрямую) таким образом, чтобы гарантированно не вызвать ошибку CTFE.

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

[1] Здесь я полностью игнорирую числа с плавающей запятой, так как мало что знаю о том, где может возникнуть недетерминизм. Может ли кто-нибудь привести пример кода с плавающей запятой, который будет вести себя иначе во время CTFE, чем во время выполнения? Было бы достаточно, например, вывести ошибку miri, если при выполнении операции с плавающей запятой один из операндов является сигнальным NaN (для получения первой гарантии, той, что из моего предыдущего поста), и сказать, что система типов CTFE не позволяет сигнализировать NaN по типу f32 / f64 (для обеспечения «постоянной безопасности»)?

Что не сработает, так это утверждение, которое @eddyb добавил в качестве меры предосторожности.

Конечно, но пока вы можете переписать assert!(condition); в [()][!condition as usize]; .

Конечно, но вы можете переписать assert!(условие); to [()][!условие как размер]; на данный момент.

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

РЕДАКТИРОВАТЬ: Неважно, я только что понял, что assert проверяет смещение. Таким образом, на самом деле он никогда не может дать сбой во время CTFE, потому что, если указатели не находятся в одном и том же блоке при выполнении wrapping_sub , мири выдаст ошибку.

// guess we can't have this as const fn
fn is_eq<T>(a: &T, b: &T) -> bool {
    a as *const _ == b as *const _
}

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

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

получение четности 14-го бита указателя не является детерминированным. с виртуальными указателями он становится детерминированным. с реальными указателями это не детерминировано. это нормально, потому что одно происходит во время компиляции (детерминированная среда), а другое — во время выполнения (недетерминированная среда).

const fn должен быть таким же детерминированным, как и среда, в которой они используются.

@SoniEx2

// guess we can't have this as const fn

Действительно, мы не можем. Я думаю, что мог бы жить с версией сравнения необработанных указателей, которая выдает ошибку, если какой-либо указатель в настоящее время не разыменовывается в смысле нахождения внутри выделенного объекта (но это не то, что в настоящее время реализует «полный мир»). Однако это по- прежнему делает is_eq не "константно-безопасным", потому что, если T имеет нулевой размер, он может указывать на единицу после конца объекта, даже если мы рассматриваем только безопасный код.

C++ позволяет сравнивать указатели на один конец, чтобы получить неопределенный (подумайте: недетерминированный) результат. И C, и C++ позволяют сравнивать висячий указатель, чтобы получить неопределенный результат. Неясно, что гарантирует LLVM, но я бы не стал делать ставку на гарантии, которые превышают то, что они должны гарантировать для C/C++ (более слабые из двух, если они различаются). Это проблема, если мы хотим гарантировать детерминизм во время выполнения для всего, что успешно выполняется в CTFE, что, я думаю, мы делаем.

@RalfJung

Разница заключается в том, завершает ли функция выполнение.

Адвокат дьявола: «вернуть ⊥» в одном случае — это то же самое, что получить разные результаты.

Я не думаю, что это трудно объяснить.

Лично я считаю это удивительным поведением; Вы можете это объяснить, и я мог бы понять (но я не представитель...), но это не соответствует моей интуиции.

@oli-obk

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

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

@RalfJung

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

OK; Ты потерял меня; Я не понимаю, как вы пришли к этой гарантии «безопасная константа fn является детерминированной», учитывая две предпосылки; не могли бы вы уточнить аргументацию?

Если это гарантия, о которой мы заботимся, мне не кажется неразумным сделать систему типов более строгой при проверке функций CTFE; в конце концов, мы хотим использовать его для большего количества вещей (проверка «константной безопасности»). Я думаю, что тогда было бы разумно объявить приведения ptr к int небезопасными в контексте const, утверждая, что это необходимо, потому что контекст const дает дополнительные гарантии.

Точно так же, как наша обычная «безопасность во время выполнения» может быть подорвана небезопасным кодом, так же может быть и «константная безопасность», и это нормально. Я не вижу никаких проблем с небезопасным кодом, который все еще может выполнять приведение типов ptr к int через объединения — в конце концов, это небезопасный код. В этом мире обязательства проверки небезопасного кода в константном контексте сильнее, чем обязательства в неконстантном контексте; если ваша функция возвращает целое число, вы должны доказать, что это всегда будет PrimVal::Bytes и никогда не будет PrimVal::Ptr .

Эти абзацы - музыка для моих ушей! ❤️ Кажется, это обеспечивает детерминизм («чистоту»)? и это именно то, что я имел в виду ранее. Я думаю, что непрерывная безопасность также является прекрасным термином!

Для дальнейшего использования позвольте мне назвать эту гарантию «надежностью CTFE»: если CTFE не дает ошибок, то его поведение соответствует времени выполнения — оба расходятся или оба завершаются с одним и тем же значением. (Здесь я полностью игнорирую возвращаемые значения более высокого порядка.)

@Центрил

Адвокат дьявола: «вернуть ⊥» в одном случае — это то же самое, что получить разные результаты.

Ну, это явно вопрос определения. Я думаю, вы поняли гарантию надежности CTFE, которую я описывал, и вы согласны, что это гарантия, которую мы хотим; нужно ли это все , что мы хотим, обсуждается :)

OK; Ты потерял меня; Я не понимаю, как вы пришли к этой гарантии «безопасная константа fn является детерминированной», учитывая две предпосылки; не могли бы вы уточнить аргументацию?

Допустим, у нас есть некоторый вызов foo(x) , где foo — безопасная константная функция, а x — допустимое константное значение (т. е. это не &y as *const _ as usize ). ). Тогда мы знаем, что foo(x) будет выполняться в CTFE без возникновения ошибки, в соответствии с константной безопасностью. Как следствие, по надежности CTFE, во время выполнения foo(x) будет вести себя так же, как и в CTFE.

По сути, я думаю, что разложил вашу гарантию на две части: одна из них гарантирует, что безопасная константа fn никогда не попытается сделать что-то, что не поддерживает CTFE (например, чтение со стандартного ввода или определение того, равен ли младший значащий байт указателя 0), и один гарантирует, что все, что поддерживает CTFE, соответствует времени выполнения.

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

Рад, что вам это нравится. :) Это значит, что я наконец понял, о чем мы тут говорим. «чистота» может означать так много разных вещей, что я часто чувствую себя немного неловко, когда использую этот термин. И детерминизм не является достаточным условием для константной безопасности, важным критерием является то, вызывает ли выполнение в CTFE ошибку. (Одним из примеров детерминированной неконстантно-безопасной функции является мой вариант вашего orcale , умноженного на 0. Этого нельзя делать даже с использованием небезопасного кода, поскольку мири выдаст ошибку при проверке байтов указателя, даже если байты в конечном итоге не имеют значения. Это похоже на операцию, которая извлекает младший значащий байт указателя, «const-UB» и, следовательно, не разрешена даже в небезопасном константном коде.)

да, указатель, следующий за концом элемента массива, вероятно, будет указывать на следующий элемент массива. И что? это не совсем недетерминировано? детерминированный во время компиляции - это все, что имеет значение в любом случае. насколько мне известно, оценка во время выполнения может привести к segfault из-за исчерпания стека.

@RalfJung

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

Я считаю, что это можно серьезно смягчить, чтобы поддерживать преобразование большей части существующего кода, введя ?const , где вы можете писать функции более высокого порядка, результат которых может быть привязан к const тогда и только тогда, когда предоставленная функция равна ?const fn(T) -> U и где is_const(x : T) ; Так что у тебя есть:

?const fn twice(fun: ?const fn(u8) -> u8) { fun(fun(42)) }

fn id_impure(x: u8) -> u8 { x }
const fn id_const(x: u8) -> u8 { x }
?const fn id_maybe_const(x: u8) -> u8 { x }

fn main() {
    let a = twice(id_impure); // OK!
    const b = twice(id_impure); // ERR!
    let c = twice(id_const); // OK!
    const d = twice(id_const); // OK!
    let e = twice(id_maybe_const); // OK!
    const f = twice(id_maybe_const); // OK!
}

Примерно через неделю я напишу RFC с предложением чего-то подобного (и не только).

@Centril, на данный момент вы разрабатываете систему эффектов с полиморфизмом эффектов. Я знаю, что это всегда было твоей тайной (?) повесткой дня, просто дать тебе понять, что это становится вопиюще очевидным.^^

@RalfJung Я уже раскрыл секрет на https://github.com/rust-lang/rfcs/pull/2237 в прошлом году, но мне придется его переписать ;)
Сейчас почти общественное достояние ^,-

@SoniEx2

да, указатель, следующий за концом элемента массива, вероятно, будет указывать на следующий элемент массива. И что? это не совсем недетерминировано? детерминированный во время компиляции - это все, что имеет значение в любом случае. насколько мне известно, оценка во время выполнения может привести к segfault из-за исчерпания стека.

Проблема возникает в следующих ситуациях (на С++):

int x[2];
int y; // let's assume y is put right after the end of x in the stack frame
if (&x[0] + 2 == &y) {
  // ...
}

Компиляторы C хотят (и делают!) оптимизировать это сравнение до false . В конце концов, один указатель указывает на x , а другой — на y , поэтому они никогда не могут быть равны.
За исключением, конечно, того, что адреса на машине равны , потому что один указатель указывает прямо на конец x , который является тем же адресом, что и (начало) y ! Итак, если вы затеняете код настолько, что компилятор больше не видит, откуда берутся адреса, вы можете сказать, что сравнение оценивается как true . Таким образом, стандарт C++ допускает недетерминированность обоих результатов, оправдывая как оптимизацию (которая говорит false ), так и компиляцию в сборку (которая говорит true ). Стандарт C не допускает этого, что делает компиляторы LLVM (и GCC) несовместимыми, поскольку оба будут выполнять такие виды оптимизации.

Я написал краткое изложение своих идей о константной безопасности, постоянной надежности и т. д., которые появились здесь, в этой теме и/или в соответствующем обсуждении в IRC: https://www.ralfj.de/blog/2018/07/19/ const.html

Этот вопрос здесь стало несколько трудно распутать, потому что было обсуждено очень много вещей. @oli-obk услужливо создал репозиторий для проблем с const-eval, поэтому хорошим местом для обсуждения конкретных подпроблем, вероятно, является средство отслеживания проблем https://github.com/rust-rfcs/const-eval.

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

  • нет общих аргументов с границами типажа
  • нет аргументов или возвращаемых типов указателя fn или типа dyn Trait

    • проверяется рекурсивно по типу аргумента, поэтому поля аргументов также могут не относиться к этим типам

  • нет небезопасного кода (потому что мы не знаем, есть ли там проблемные вещи)

    • Я лично думаю, что это нормально, за исключением union , которые уже находятся за дополнительными воротами функций, и необработанных ссылок на указатели (обычно запрещены в любой константе прямо сейчас). Любой другой небезопасный код должен пройти через другие небезопасные константы fns или константные встроенные функции, которые требуют отдельного обсуждения относительно стабилизации.

(nit: мое предложение также включало рекурсивную проверку указателей fn или dyn Trait на возвращаемый тип const fn s)

нет общих аргументов с границами типажа

Чтобы уточнить, будет ли что-то подобное принято или нет?

struct Mutex<T> where T: Send { /* .. */ }

impl<T> Mutex<T> where T: Send {
    pub const fn new(val: T) -> Self { /* .. */ }
}

Граница не является частью самого const fn .

Также, чтобы уточнить: является ли предложение стабилизировать эти вещи и оставить остальные за воротами функций ИЛИ стабилизировать их и сделать все остальное ошибкой?

@mark-im остальные останутся за функциональными воротами.

@oli-obk в чем проблема с небезопасным кодом? Мы разрешаем unsafe в const X : Ty = ... , у которого все те же проблемы. Я думаю, что тела const fn должны проверяться точно так же, как тела const .

Я думаю, что мы хотим оставаться консервативными в отношении «неконстантных» операций — операций, которые безопасны, но не константно-безопасны (в основном все, что связано с необработанными указателями), — но они уже полностью запрещены в контексте констант, верно?

Граница не является частью самой константы fn.

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

в чем проблема с небезопасным кодом?

Я не вижу никаких проблем, как указано в моем комментарии. Каждая константная небезопасная функция/функция в любом случае должна пройти собственную стабилизацию.

@RalfJung Я думаю, что проблема в том, что « @Centril нервничает из-за того, что мы что-то упустили из-за того, что они уже полностью запрещены в контексте const». ;) Но мы должны в какой-то момент стабилизировать unsafe { .. } в const fn , так что если вы уверены, что проблем нет и что мы поймали все неконстантные операции, то давайте сделаем это?

Более того, если мы что-то пропустили, мы уже как бы облажались, так как люди могут использовать это на const .

Я по-прежнему планирую написать PR, заполняющий части безопасности и продвижения const в репозитории const fn RFC ; самое время тщательно проверить, все ли мы рассмотрели (и есть ли тестовые примеры).

Еще одна вещь, которая появилась на Discord, — это операции FP: в настоящее время мы не можем гарантировать, что они будут соответствовать реальному оборудованию. CTFE будет точно следовать IEEE, а LLVM/аппаратное обеспечение — нет.

Это также относится к элементам const , но они никогда не будут выполняться во время выполнения, в то время как const fn может быть. Поэтому кажется разумным не стабилизировать операции FP в const fn .

OTOH, мы уже продвигаем результаты операций FP? Итак, у нас уже есть это несоответствие времени выполнения/времени компиляции, наблюдаемое в стабильной версии. Стоит ли бежать в кратер, чтобы посмотреть, сможем ли мы это исправить?

Для дальнейшего использования следующая статья имеет отношение к плавающей запятой и детерминизму:

@RalfJung

Стоит ли бежать в кратер, чтобы посмотреть, сможем ли мы это исправить?

Я был бы удивлен, если бы мы могли это сделать, но по крайней мере стоит попробовать. :)

@RalfJung может быть интересная информация в этой теме https://github.com/rust-lang/rust/issues/24111#issuecomment -386764565

Предполагая, что мы хотим сохранить форму ключевого слова const fn , я думаю, что стабилизация чего-то сейчас , достаточно ограниченного, является довольно приличным решением (как я не видел этого раньше ?!)

Когда мы делаем эти частичные стабилизации, я просто хочу зарегистрировать
запрос на четкий список ограничений и их обоснования. Это
будет раздражать, когда пользователи сделают, казалось бы, безобидное изменение и
столкнуться с ошибкой компиляции, поэтому мы можем, по крайней мере, исправить ошибки. я
признать, что рассуждения разбросаны по многим дискуссиям, не все из которых
Я следил, но я думаю, что в документах должна быть таблица (или
справочник или, по крайней мере, nomicon) с перечислением каждой запрещенной операции,
проблема, которую это может вызвать, и перспективы стабилизации (например, «никогда»,
«если RFC XYZ будет реализован», «после того, как мы примем эту часть спецификации»).

В понедельник, 20 августа 2018 г., в 13:44 Эдуард-Михай Буртеску <
уведомления@github.com> написал:

Предполагая, что мы хотим сохранить форму ключевого слова const fn, я думаю, что стабилизация
что-то сейчас , достаточно ограниченное, является довольно приличным решением (как
неужели я его раньше не видел?!)


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-414403036 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAC3n80yVxIa3agsJP4wXZFkgyHVmAsjks5uSvWXgaJpZM4D66IA
.

@ est31 Как уже писал @rkruppe , эти фьюзы было бы незаконно выполнять без -ffast-math - и я думаю, что LLVM справляется с этим правильно.

Насколько я помню, базовая арифметика даже не так уж проблематична (за исключением 32-битной x86, потому что x87...), но трансцендентные функции - проблематичны. И это не const fn , верно? Так что я надеюсь, что, в конце концов, мы сможем добиться паритета между const товарами, продвижением и const fn в этом отношении.

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

Насколько я помню, базовая арифметика даже не так уж проблематична (за исключением 32-битной x86, потому что x87...), но трансцендентные функции - проблематичны.

Как трансцендентные функции означают проблему?

IIRC, хотя некоторые трансцендентные функции поддерживаются обычными процессорами x86, эти функции медленны и избегаются и включены только для полноты и совместимости с существующими реализациями. Таким образом, почти везде трансцендентные функции выражаются через комбинации основных арифметических функций. Это означает, что их ссылочная прозрачность ничем не отличается от прозрачности арифметических функций. Если базовые функции «безопасны», то все, что на них построено, включая трансцендентные функции, является «безопасным». Единственным источником «референтной непрозрачности» здесь могут быть различные приближения (реализации) этих трансцендентных функций в терминах этих основных арифметических функций. Это источник проблемы?

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

  • Существует реализация времени выполнения, которую программа использует для цели (например, целевая платформа libm или аппаратные инструкции в некоторых обстоятельствах).
  • Там есть постоянная папка, которую использует LLVM (видимо, это просто хост-платформа C libm)
  • Есть все, что MIRI будет делать с этими операциями (например, что-то в rustc_apfloat или интерпретация реализации Rust, такой как https://github.com/japaric/libm/)

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

@rfcbot отменить

Мы вряд ли стабилизируем полную монти в ближайшее время.
Вместо этого я хотел бы достичь консенсуса для более минимального подмножества (как примерно указано в https://github.com/rust-lang/rust/issues/24111#issuecomment-414310119), которое мы, надеюсь, сможем стабилизировать в ближайшем будущем. .
Это подмножество отслеживается в № 53555. Дальнейшее описание доступно там.

Предложение @Centril отменено.

@rkruppe есть ли причина для понижения трансцендентных функций до встроенных функций llvm? Разве мы не можем просто избежать всей проблемы, понизив их до хорошо известных реализаций только для ржавчины, которые мы контролируем и которые одинаковы на всех платформах?

есть ли смысл понижать трансцендентные функции до внутренностей llvm?

Помимо простоты, заключающейся в том, что не нужно реализовывать целую кросс-платформенную библиотеку, у встроенных функций есть преимущество в оптимизаторе и генераторе кода LLVM, которого не получит обычная библиотечная функция. Очевидно, что постоянное сворачивание (и связанные с ним вещи, такие как анализ диапазона значений) является проблемой в этом контексте, но в остальном это весьма полезно. Существуют также алгебраические идентификаторы (применяемые проходом SimplifyLibCalls). Наконец, некоторые функции (в основном sqrt и его обратные функции, которые не трансцендентны, но что-то еще) имеют специальную поддержку генерации кода, например, для генерации sqrtss на x86 с SSE.

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

Даже игнорируя все вышеперечисленное, это не лучший вариант ИМО. Если на целевой платформе есть библиотека C, ее можно будет использовать либо потому, что она более оптимизирована, либо для того, чтобы избежать раздувания.

у встроенных функций есть преимущество в оптимизаторе и генераторе кода LLVM, которого нет у обычной библиотечной функции.

Я думал, что такие оптимизации включаются только в том случае, если включена быстрая математика?

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

Конечно. Всегда должна быть возможность обменять это довольно академическое свойство — ссылочную прозрачность — на более важные вещи, такие как повышение скорости компилируемых двоичных файлов или меньших двоичных файлов. Например, используя платформу libm или включив быстрый математический режим. Или вы предлагаете навсегда запретить быстрый математический режим?

IMO, мы не должны запрещать f32::sin() в константных контекстах, по крайней мере, если мы разрешаем + , - и т. д. Такой запрет заставит людей создавать и использовать ящики которые обеспечивают константно-совместимые реализации.

Я думал, что такие оптимизации включаются только в том случае, если включена быстрая математика?

Постоянное вычисление этих функций и выброс sqrtss легко обосновать без -ffast-math, так как его можно правильно округлить.

Или вы предлагаете навсегда запретить быстрый математический режим?

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

Не удалось найти открытую проблему для ICE, вызванную Vec в контексте const fn.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=508238a9f06fd85720307bf6cc227586

Должен ли я открыть новую тему для этого?

@Voultapher , да, это похоже на новый ICE.

Хорошо открыл #55063.

Если компилятор может проверить, может ли функция быть вызвана в constexpr времени компиляции, когда пользователь аннотирует ее с помощью const fn , почему бы просто не выполнить автоматическую проверку для всех функций? (аналогично авто-чертам)? Я не могу думать о каких-либо реальных неблагоприятных последствиях, и очевидное преимущество состоит в том, что нам не нужно зависеть от подверженных ошибкам человеческих суждений.

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

Также нам не нужно полагаться на человеческое суждение. У нас может быть clippy lint, который сообщает нам, когда неаннотированная функция может быть const fn : https://github.com/rust-lang/rust-clippy/issues/2440

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

@remexre const fn действует как спецификация интерфейса. Я не очень хорошо знаком с крошечными деталями этой функции (и, возможно, то, что следует здесь, уже продумано), но два случая, когда я могу думать о том, что компилятор сообщает, когда функция неправильно аннотируется как const , являются давать сбой при компиляции, если такая функция принимает &mut в качестве параметра или если она вызывает другие функции, отличные const . Поэтому, если вы измените реализацию const fn и нарушите эти ограничения, компилятор вас остановит. Затем вы можете реализовать биты, отличные от const , в (а) отдельных функциях или сломать API, если это было запланированным изменением.

Есть еще одна промежуточная точка, о которой я не видел обсуждения, и это возможность ввести противоположность этому маркеру и своего рода «вывод о чистоте функции», когда он не установлен явно. Тогда документы будут показывать фактический маркер, но с каким-то предупреждением о том, что не гарантируется стабильность этого маркера, если это const . Проблема в том, что это может поощрять лень и делать это почти каждый раз, что не является его целью.

должен ли const fn производить вывод? почему &mut должно быть запрещено?

@aledomu Мой комментарий был адресован @AGaussman; Я говорю о случае, когда автор библиотеки предоставляет функцию, которая «не должна быть» константной (в том смысле, что константность не предназначена для того, чтобы быть частью API); если бы константа была выведена, было бы критическим изменением сделать указанную функцию неконстантной.

@SoniEx2 const fn — это функция, которую можно оценить во время компиляции, что бывает только в случае любой чистой функции.

@remexre Если это не должно быть стабильной частью API, просто не отмечайте его.

Что касается вывода, который я прокомментировал, поэтому я упомянул о необходимости некоторого предупреждения в документации ящика.

какая разница? абсолютно никакой!

const fn add_1(x: &mut i32) { x += 1; }
let mut x = 0;
add_1(&mut x);
assert_eq!(x, 1);
x = 0;
add_1(&mut x);
assert_eq!(x, 1);

const fn added_1(x: i32) -> i32 { x + 1 }
let mut x = 0;
x = added_1(x);
assert_eq!(x, 1);
x = 0;
x = added_1(x);
assert_eq!(x, 1);

Я зарегистрировал целевые проблемы для:

Уже существуют следующие целевые проблемы:

Если есть другие области, которые еще не отслеживаются другими проблемами, их необходимо обсудить. const eval и const fn , я предлагаю людям создавать новые выпуски (и копировать меня + @oli-obk в них).

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

Я не помню всех подробностей, но есть ли что-то еще, что поддерживается miri, но еще не включено в min_const_fn ? Например, необработанные указатели.

@SimonSapin Да, хороший улов. Для этого есть еще несколько существующих проблем. Я обновил комментарий + описание проблемы. Если есть что-то нераскрытое, с чем вы столкнулись, создайте новые выпуски, пожалуйста.

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

Когда я удаляю #![feature(const_fn)] в Servo, появляются сообщения об ошибках:

  • trait bounds other than `Sized` on const fn parameters are unstable
  • function pointers in const fn are unstable

(Все эти const fn являются тривиальными конструкторами для типов с закрытыми полями. Предыдущее сообщение относится к конструктору struct Guard<T: Clone + Copy> , хотя Clone не используется в конструкторе. последний предназначен для инициализации Option<fn()> (упрощенно) до None или Some(name_of_a_function_item) .)

Однако в описании этой проблемы не упоминаются ни трейты, ни типы указателей на функции.

Я не имею в виду, что у нас должны быть только два более конкретных вопроса для вышеизложенного. Я имею в виду, что мы должны снова открыть это, пока мы каким-то образом не удостоверимся, что все, что находится за функциональными воротами const_fn (которые все еще указывают здесь в сообщениях об ошибках), имеет проблему с отслеживанием. Или пока const_fn полностью не стабилизируется.

@SimonSapin

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

Эта проблема похожа на https://github.com/rust-lang/rust/issues/34511 , которая является одной из самых больших проблем с отслеживанием. Эта проблема также была бесплатной для всех в течение некоторого времени, поэтому сейчас она не действует как мета-проблема. Для таких общедоступных, пожалуйста, используйте http://internals.rust-lang.org/ вместо этого.

Однако в описании этой проблемы не упоминаются ни трейты, ни типы указателей на функции.

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

Это именно то, что я думаю, должно быть сделано. С точки зрения сортировки T-Lang желательно иметь целевые и действенные проблемы.

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

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

Мне даже не ясно, что представляет собой const_fn функциональный шлюз

Именно поэтому мы не должны закрывать его, пока не выясним это, ИМО.

Все

Впрочем, так ли это все?

Кто-нибудь знает, что случилось с функцией const_string_new ? Есть ли проблема с отслеживанием? Нестабильная книга просто ссылается здесь.

@phansch Это потому, что все rustc_const_unstable указывают здесь. (cc @oli-obk мы можем это исправить?)

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

Ср, 9 января 2019 г., 04:05 Маздак Фаррохзад < [email protected]
написал:

@phansch https://github.com/phansch Это потому, что все
rustc_const_unstable укажите здесь.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-452622097 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAC3n7JhzsZZpizmWlp0Nww5bcfIqH2Vks5vBbC8gaJpZM4D66IA
.

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

Я сопротивлялся комментированию здесь, и, возможно, нам следует перенести этот разговор в ветку внутренних дел (есть ли уже такая?), но...

Решение закрыть это не имеет смысла для меня. Это проблема с отслеживанием, потому что она проявляется в сообщениях об ошибках компилятора, и не только она одна. Дополнительные примеры см. в этом посте: https://internals.rust-lang.org/t/psa-tracking-for-gated .

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

Опять же, если для этого нужна (или уже есть) ветка о внутреннем устройстве, может быть, давайте перенесем этот разговор туда?

РЕДАКТИРОВАТЬ: Или проблема только в том, что книга устарела? Пример из RFC (не хватает пары #[derive(...)] s) работает без ошибок на Rust rustc 1.31.1. Есть ли еще сообщение об ошибке компилятора, указывающее здесь? Было бы неплохо иметь место для ссылки на такие ошибки, как:

error: only int, `bool` and `char` operations are stable in const fn

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

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

https://github.com/rust-lang/rust/blob/6ecad338381cc3b8d56e2df22e5971a598eddd6c/src/libsyntax/feature_gate.rs#L194

— единственная функция active , указывающая на закрытую проблему.

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

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

Да, это правильное решение, и то, что уже предложил @Centril .

Первоначальный комментарий также был отредактирован, чтобы перенаправить людей к конкретным проблемам, которые появляются здесь в «окне», которое упоминает @ErichDonGubler .

https://github.com/rust-lang/rust/issues/57563 теперь открыт для отслеживания оставшихся нестабильных константных функций.

Тогда кто-нибудь может отредактировать тело проблемы, чтобы сделать ссылку на # 57563 на видном месте?

@glaebhoerl готово :)

Привет, я попал сюда, потому что получил error[E0658]: const fn is unstable (see issue #24111) при компиляции ncurses-rs. Что я должен делать? Обновить ржавчину? у меня есть

$ cargo version
cargo 1.27.0
$ rustc --version
rustc 1.27.2

РЕДАКТИРОВАТЬ: сделал brew uninstall rust и следовал инструкциям по установке rustup , теперь rustc --version равно rustc 1.33.0 (2aa4c46cf 2019-02-28) , и эта ошибка исчезла.

Да, чтобы иметь возможность использовать const fn в стабильной версии, вам необходимо обновить компилятор.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги