Rust: Проблема с отслеживанием продвижения символа `!` К типу (RFC 1216)

Созданный на 29 июл. 2016  ·  259Комментарии  ·  Источник: rust-lang/rust

Проблема отслеживания для rust-lang / rfcs # 1216, которая переводит ! в тип.

Нерешенные проблемы для решения

Интересные события и ссылки

A-typesystem B-RFC-approved B-unstable C-tracking-issue F-never_type Libs-Tracked T-lang T-libs finished-final-comment-period

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

@petrochenkov Забудьте о ! и посмотрите только перечисления.

Если у меня есть перечисление с двумя вариантами, я могу сопоставить его с двумя случаями:

enum Foo {
    Flim,
    Flam,
}

let foo: Foo = ...;
match foo {
    Foo::Flim => ...,
    Foo::Flam => ...,
}

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

enum Void {
}

let void: Void = ...;
match void {
}

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

enum Foo {
    Flim,
    Flam,
}

let result_foo: Result<T, Foo> = ...;
match result_foo {
    Ok(t) => ...,
    Err(Flim) => ...,
    Err(Flam) => ...,
}

Мы можем расширить внутренний узор внутрь внешнего. Есть два варианта Foo , поэтому есть два случая для Err . Нам не нужны отдельные операторы сопоставления для сопоставления Result и Foo . Это работает для перечислений с любым количеством вариантов ... _ кроме нуля_.

enum Void {
}

let result_void: Result<T, Void> = ...;
match result_void {
    Ok(t) => ...,
    // ERROR!
}

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

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

Ура!

Здесь есть реализация WIP: https://github.com/canndrew/rust/tree/bang_type_coerced

Его текущий статус таков: он построен с использованием старых трансляций и может использоваться, но имеет несколько неудачных тестов. Некоторые тесты терпят неудачу из-за ошибки, которая приводит к сбою кода типа if (return) {} во время транзакции. Другие тесты связаны с оптимизацией времени компоновки и всегда были для меня ошибочными, поэтому я не знаю, имеют ли они какое-либо отношение к моим изменениям.

Моя текущая дорожная карта:

  • Получите это работать с МИР. Надеюсь, это не будет слишком сложно, так как я начал его реализовывать, но у меня возникла проблема, когда MIR создает segfault во время компиляции.
  • Удалите устаревшие данные о расхождениях из компилятора ( FnOutput , FnDiverging и т.п.).
  • Спрячьте новый тип за воротами функций. Это будет означать, что когда функция отключена:

    • ! можно анализировать только как тип в позиции возврата.

    • Переменные расходящегося типа по умолчанию равны () .

  • Выясните, как мы можем выдавать предупреждения о совместимости, когда значение по умолчанию () используется для разрешения признака. Один из способов сделать это - добавить в AST новый тип с именем DefaultedUnit . Этот тип ведет себя как () и при некоторых обстоятельствах превращается в () но выдает предупреждение при разрешении признака (как () ). Проблема с этим подходом в том, что, я думаю, будет сложно отловить и исправить все ошибки с реализацией - мы в конечном итоге сломаем код людей, чтобы спасти их код от взлома.

Есть ли что-нибудь, что нужно добавить в этот список? Я просто буду над этим работать? И нужно ли эту ветку перенести в основной репозиторий?

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

@eddyb , @ arielb1 , @anyone_else : Что вы думаете об этом подходе? Я почти нахожусь на этом этапе (без пары неудачных тестов, которые я (очень медленно) пытаюсь исправить).

Какие черты мы должны реализовать!? Первоначальный PR # 35162 включает Ord и несколько других.

Разве ! не должны автоматически реализовывать _все_ черты?

Такой код довольно распространен:

trait Baz { ... }

trait Foo {
    type Bar: Baz;

    fn do_something(&self) -> Self::Bar;
}

Я ожидал, что ! можно использовать для Foo::Bar , чтобы указать, что Bar самом деле никогда не может существовать:

impl Foo for MyStruct {
    type Bar = !;
    fn do_something(&self) -> ! { panic!() }
}

Но это возможно только в том случае, если ! реализует все черты.

@tomaka Об этом есть RFC: https://github.com/rust-lang/rfcs/pull/1637

Проблема в том, что если ! реализует Trait он также должен реализовать !Trait ...

Проблема в том, что если! реализует Черту, которую он также должен реализовать! Черта ...

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

@tomaka ! не может автоматически реализовать _all_ трейты, потому что у трейтов могут быть статические методы и связанные типы / константы. Он может автоматически реализовывать черты, у которых есть только нестатические методы (т.е. методы, которые принимают Self ).

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

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

Когда именно мы по умолчанию устанавливаем переменные расходящегося типа на () / ! и когда мы выдаем ошибку о невозможности вывести достаточно информации о типе? Это где-нибудь указано? Я бы хотел скомпилировать следующий код:

let Ok(x) = Ok("hello");

Но первая ошибка, которую я получаю, это « unable to infer enough type information about _ ». В этом случае я думаю, что для _ по умолчанию было бы разумно значение ! . Однако, когда я писал тесты для определения поведения по умолчанию, я обнаружил, что сделать переменную типа по умолчанию было на удивление сложно. Вот почему эти тесты так запутаны.

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

Но первая ошибка, которую я получаю, это «не могу вывести достаточно информации о типе _». В этом случае я думаю, что для _ имеет смысл по умолчанию!. Однако, когда я писал тесты для определения поведения по умолчанию, я обнаружил, что сделать переменную типа по умолчанию было на удивление сложно. Вот почему эти тесты так запутаны.

На мой взгляд, это очень хорошая идея. То же самое для None которое, например, по умолчанию будет Option<!> .

@carllerche

Тест unit_fallback - определенно странный способ продемонстрировать это. Менее макроуровневая версия

trait Balls: Default {}
impl Balls for () {}

struct Flah;

impl Flah {
    fn flah<T: Balls>(&self) -> T {
        Default::default()
    }
}

fn doit(cond: bool) {
    let _ = if cond {
        Flah.flah()
    } else {
        return
    };
}

fn main() {
    let _ = doit(true);
}

Только переменная типа, созданная return / break / panic!() умолчанию не имеет значения.

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

Определите «указанный». :) Ответ заключается в том, что некоторые операции, которые не записываются нигде вне кода, требуют, чтобы в этот момент был известен тип. Наиболее распространенный случай - это доступ к полю ( .f ) и отправка метода ( .f() ), но другой пример - deref ( *x ), и, вероятно, есть еще один или два. В основном есть веские причины для того, чтобы это требовалось - вообще говоря, есть несколько расходящихся способов действовать, и мы не можем добиться прогресса, не зная, какой из них выбрать. (Потенциально можно было бы реорганизовать код, чтобы эту потребность можно было зарегистрировать как своего рода «отложенное обязательство», но это сложно сделать.)

Если вы дойдете до конца fn, мы выполним все отложенные операции выбора признака, пока не будет достигнуто устойчивое состояние. Это точка, где применяются значения по умолчанию (например, i32 и т. Д.). Эта последняя часть описана в RFC, где говорится о параметрах типа по умолчанию, задаваемых пользователем (хотя этот RFC в целом требует доработки).

https://github.com/rust-lang/rust/issues/36011
https://github.com/rust-lang/rust/issues/36038
https://github.com/rust-lang/rust/issues/35940

Есть некоторые ошибки, которые нужно добавить в список нерешенных проблем.

@canndrew они немного похожи на https://github.com/rust-lang/rust/issues/12609

Боже, это старая ошибка! Но да, я бы сказал, что мой # 36038 - это обман (я думал, что видел его где-то раньше). Я не думаю, что ! действительно можно рассматривать как прайм-тайм, пока это не будет исправлено.

Планируется ли, что ! повлияет на полноту сопоставления с образцом? Пример текущего, возможно неправильного поведения:

#![feature(never_type)]

fn main() {
    let result: Result<_, !> = Ok(1);
    match result {
//        ^^^^^^ pattern `Err(_)` not covered
        Ok(i) => println!("{}", i),
    }
}

@tikue да, это одна из перечисленных выше ошибок.

@lfairy упс , не видел этого, потому что он не был указан в

Есть ли планы реализовать From<!> for T и Add<T> for ! (с типом вывода ! )? Я знаю, что это действительно странно конкретная просьба - я пытаюсь использовать и то, и другое в этом PR .

From<!> for T определенно. Add<T> for ! , вероятно, должна решить команда libs, но я лично считаю, что ! должен реализовать все свойства, для которых у него есть логическая каноническая реализация.

@canndrew Спасибо! Я привык к scala-трейту Nothing который является подтипом каждого типа и поэтому может использоваться практически везде, где может появиться значение. Тем не менее, я определенно сочувствую желанию понять эффекты, которые impl All for ! или подобное может иметь на систему типов ржавчины, особенно в отношении отрицательных границ черт и тому подобного.

Per https://github.com/rust-lang/rfcs/issues/1723#issuecomment -241595070 From<!> for T имеет проблемы с согласованностью.

Ах да, да. Нам нужно что-то с этим делать.

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

impl<T> From<T> for T
    overridden_by<T> From<!> for T
{ ... }

impl<T> From<!> for T { ... }

Разве это не относится к специализации? Изменить: я считаю, что это правило решетки.

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

Все эти match (res: Res<A, !>) { Ok(a) /* No Err */ } , специальные методы для Result выглядят очень надуманными, как функции ради функций, и, похоже, не стоят усилий и сложности.
Я понимаю, что ! - это любимая функция

@petrochenkov # 12609 - это не особенность для типа Never. Это просто исправление ошибки, позволяющее обнаружить явно недостижимый код.

@petrochenkov Забудьте о ! и посмотрите только перечисления.

Если у меня есть перечисление с двумя вариантами, я могу сопоставить его с двумя случаями:

enum Foo {
    Flim,
    Flam,
}

let foo: Foo = ...;
match foo {
    Foo::Flim => ...,
    Foo::Flam => ...,
}

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

enum Void {
}

let void: Void = ...;
match void {
}

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

enum Foo {
    Flim,
    Flam,
}

let result_foo: Result<T, Foo> = ...;
match result_foo {
    Ok(t) => ...,
    Err(Flim) => ...,
    Err(Flam) => ...,
}

Мы можем расширить внутренний узор внутрь внешнего. Есть два варианта Foo , поэтому есть два случая для Err . Нам не нужны отдельные операторы сопоставления для сопоставления Result и Foo . Это работает для перечислений с любым количеством вариантов ... _ кроме нуля_.

enum Void {
}

let result_void: Result<T, Void> = ...;
match result_void {
    Ok(t) => ...,
    // ERROR!
}

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

@petrochenkov Может быть, я неправильно понял, что вы говорили. В ветке # 12609 обсуждаются два вопроса:

(0) Следует ли разрешить компиляцию этого кода?

let res: Result<u32, !> = ...;
match res {
    Ok(x) => ...,
}

(1) Следует ли разрешить компиляцию этого кода?

let res: Result<u32, !> = ...;
match res {
    Ok(x) => ...,
    Err(_) => ...,
}

В настоящее время ответами являются «нет» и «да» соответственно. # 12609 говорит о (1) конкретно в самой проблеме, но я думал о (0), когда отвечал. Что касается того, какими должны быть ответы, я думаю, что (0) определенно должно быть «да», но и в отношении (1) я тоже не уверен.

@canndrew
Может быть разумным сделать (1), то есть недостижимые шаблоны, линт, а не серьезную ошибку, независимо от необитаемых типов, RFC 1445 содержит больше примеров того, почему это может быть полезно.

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

Кстати, я сделал PR, чтобы попытаться исправить (0) здесь: https://github.com/rust-lang/rust/pull/36476

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

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

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

Я думаю, что (1) должен компилироваться по умолчанию, но выдает предупреждение из того же ворса, что и здесь:

fn a() -> u32 {
    return 4;
    5
}
warning: unreachable expression, #[warn(unreachable_code)] on by default

Пока мы занимаемся этим, имеет ли смысл делать некоторые закономерности неопровержимыми перед лицом ! ?

let res: Result<u32, !> = ...;
let Ok(value) = res;

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

Некоторое время у меня были пиарщики, которые собирали гниль. Могу ли я чем-нибудь помочь с их рассмотрением? Вероятно, их нужно обсудить. Я говорю о 36476, 36449 и 36489.

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

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

fn(A,B)->!
fn(A,fn(B,C)->!)->!

Но ты не должен говорить

let g:! = panic!("whatever");

или

fn(x:!) -> !{
     x
}

или даже

type ID=fn(!)->!;

так как никакие переменные не должны иметь тип ! , и поэтому никакие входные переменные не должны иметь тип ! .

Пустой enum в этом случае другой, можно сказать

enum Empty {}

impl Empty {
    fn new() -> Empty {
         panic!("empty");
    }
}

тогда

 match Empty::new() {}

То есть существует фундаментальная разница между ! и Empty : вы не можете объявить какую-либо переменную с типом ! но можете сделать это для Empty .

@earthengine В чем преимущество введения этого (на мой взгляд, полностью искусственного) различия между двумя видами непригодных для проживания типов?

Были подняты многочисленные причины отсутствия такого различия - например, возможность писать Result<!, E> , хорошо взаимодействовать с расходящимися функциями, при этом имея возможность использовать монадические операции над Result , например map и map_err .

В функциональном программировании пустой тип ( zero ) часто используется для кодирования того факта, что функция не возвращает значение или что значение не существует. Когда вы говорите bottom type, мне непонятно, к какой концепции теории типов вы имеете в виду; обычно bottom - это имя типа, который является подтипом всех типов, но в этом смысле ни ! ни пустое перечисление не являются bottom в Rust. Но опять же, zero не является типом bottom не редкость в теории типов.

Это означает, что существует фундаментальная разница между ! и Empty : вы не можете объявить какую-либо переменную с типом ! но можете сделать это для Empty .

Вот что исправляет этот RFC. ! самом деле не является "типом", если вы не можете использовать его как тип.

@RalfJung
Эти концепции взяты из линейной логики https://en.wikipedia.org/wiki/Linear_logic

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

top - это значение, которое можно использовать любым возможным способом

Что это значит в подтипах? Что он может стать любым типом? Потому что тогда это bottom .

@eddyb Я допустил несколько ошибок, пожалуйста, дождитесь моих новых обновлений.

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

ffs, ребята

@canndrew argh , мне очень жаль. = (Я тоже планировал сегодня накинуть твой пиар ...

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

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

Используя https://is.gd/4EC1Dk в качестве примера и ориентира, что, если бы мы пропустили это, и.

1) Обработайте любую функцию, которая может вызвать панику, но не возвращает ошибку или результат, если ее подпись типа неявно изменяется с -> Foo на -> Result<Foo,!> 2) any Resulttypes would have their Error types implicitly be converted to enum AnonMyErrWrapper {Die (!), Ошибка} ``
3) С! объявление нулевого размера непригодно для проживания. Преобразование между типами потребует нулевых затрат, и можно добавить неявное преобразование, чтобы сделать его обратно совместимым.

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

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

Кроме того, это, вероятно, перекликается с возможной системой эффектов в будущем.

@tupshin - это большой гимнастики. Я рекомендую отключить размотку и использовать "Результат" вручную, если вам нужна такая ясность. [И, кстати, в этой проблеме не стоит поднимать такую ​​тему - дизайн ! - это не то, что вы ставите под сомнение, так что это чисто будущая работа.]

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

@nikomatsakis Что касается нерешенных проблем, эти два можно

  • Очистка кода от # 35162, реорганизация typeck для типов потоков через возвращаемую позицию вместо вызова expr_ty
  • Разрешить обработку необитаемых типов в матчах

Я собираюсь начать еще одну трещину в этом:

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

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

@canndrew

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

Ок, отлично!

Ну может здорово. =) Кажется немного сложным иметь два семантически эквивалентных типа ( () vs () ), которые отличаются в этом смысле, но я не могу придумать лучшего способа. = (И так или иначе, большая часть кода должна быть подготовлена ​​для этого благодаря регионам.

Я имею в виду, что я добавляю логическое значение к TyTuple

TyTuple(&'tcx Slice<Ty<'tcx>>, bool),

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

Надеюсь, нам нужно сохранить этот лог только на один цикл предупреждений.

Что касается проблемы uninitialized / transmute / MaybeUninitialized я думаю, что разумным способом действий будет:

  • Добавить MaybeUninitialized в стандартную библиотеку под mem
  • Добавьте отметку uninitialized что этот тип известен как обитаемый. В противном случае сделайте предупреждение с пометкой о том, что в будущем это будет серьезной ошибкой. Предложите вместо этого использовать MaybeUninitialized .
  • Добавьте проверку transmute что этот тип "to" необитаем, только если он также является типом "from". Аналогичным образом выведите предупреждение, которое станет серьезной ошибкой.

Мысли?

См. Эту ветку о последних изменениях в полноте:

Похоже, есть некоторые разногласия по поводу семантики &! . После # 39151 с включенными воротами функции never_type он рассматривается как необитаемый в матчах.

Если автоматических признаков для ! не будет, по крайней мере, реализация соответствующих признаков из std будет очень полезна. Одно огромное ограничение void::Void заключается в том, что он находится за пределами std а это означает, что blanket impl<T> From<Void> for T невозможен, и это ограничивает обработку ошибок.

Я думаю, что как минимум From<!> следует реализовать для всех типов, а Error , Display и Debug следует реализовать для ! .

Я думаю, что как минимум From<!> должен быть реализован для всех типов

К сожалению, это конфликтует с From<T> for T impl.

К сожалению, это конфликтует с From<T> for T impl.

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

Хорошие моменты. Я надеюсь, что когда-нибудь это будет сделано.

Следует ли [T; 0] подтип [!; 0] и &[T] подтип &[!] ? Мне кажется интуитивно понятным, что ответ должен быть «да», но текущая реализация думает иначе.

[!; 0] заселен, как и &[!] поэтому я бы сказал нет. Кроме того, на этот раз мы не используем подтипы.

И [!; 0] и &[!] не должны быть незаселенными, оба типа могут принимать значение [] (или &[] ).

Никто не сказал, что они необитаемы или должны быть необитаемыми.

let _: u32 = unsafe { mem::transmute(return) };
В принципе, это могло быть нормально, но компилятор жалуется на «преобразование между 0 битами и 32 битами».

Однако преобразование ! -> () разрешено. У меня нет особых причин указывать на это; это несоответствие, но я не могу придумать практических или теоретических проблем с этим.

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

Можно ли реализовать Fn , FnMut и FnOnce для ! таким образом, чтобы он был универсальным для всех типов ввода и вывода?

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

struct Builder<F: FnOnce(Input) -> Output> {
    func: Option<F>,
}

Поскольку func - это Option , конструктор, использующий None не может вывести тип F . Следовательно, реализация fn new должна использовать Bang:

impl Builder<!> {
    pub fn new() -> Builder<!> {
        Builder {
            func: None,
        }
    }
}

Функция построителя func должна выглядеть примерно так, чтобы ее можно было вызывать как в Builder<!> и в Builder<F> :

impl<F: FnOnce(Input) -> Output> Builder<F> {
    pub fn func<F2: FnOnce(Input) -> Output>(self, func: F) -> Builder<F2> {
        Builder {
            func: func,
        }
    }
}

Теперь проблема: в настоящее время Fn* не реализованы для ! . Кроме того, у вас не может быть общего связанного типа для признаков (например, Output в Fn ). Итак, возможно ли реализовать семейство признаков Fn* для ! таким образом, чтобы оно было универсальным для всех типов ввода и вывода?

Звучит противоречиво. Разве <! as Fn>::Output нужно преобразовывать в один тип?

<! as Fn>::Output - это ! , не так ли?

Теоретически <! as Fn>::Output должно быть ! , но текущая программа проверки типов хочет, чтобы связанные типы точно совпадали: https://is.gd/4Mkxfm

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

Если следующий код

#![feature(never_type)]

fn with_print<T>(i:i32, r:T) -> T {
    println!("{}", i);
    r
}


fn main() {
    #[allow(unreachable_code)]
    *with_print(10,&return)
}

печатает 10 ? Сейчас он ничего не печатает. Или любые другие обходные пути, которые задержат return до печати?

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


Поскольку @RalfJung интересует предыдущая версия, в которой используется &return , я снова скорректировал код. И снова, я хочу сказать, что мы сможем отложить return позже. Здесь мы могли бы использовать замыкания, но я могу использовать только return , а не break или continue и т. Д.

Например, было бы хорошо, если бы мы могли написать

#![feature(never_type)]

fn with_print<T>(i:i32, r:T) -> T {
    println!("{}", i);
    r
}


fn main() {
    for i in 1..10 {
        if i==5 { with_print(i, /* delay */break) }
        with_print(i, i);
    }
}

с перерывом, отложенным до тех пор, пока не будет напечатано 5 .

@earthengine Нет, он определенно ничего не должен печатать. ! необитаем, что означает, что нельзя создать значения типа ! . Итак, если у вас есть ссылка на значение типа ! , вы находитесь внутри мертвого блока кода.

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

oO Я не знал, что можно писать &return . Это не имеет смысла, почему вам разрешено использовать адрес return ? ^^

Но, в любом случае, @earthengine в вашем коде аргументы оцениваются до quit_with_print . Оценка второго аргумента запускает return , что завершает программу. Это похоже на запись чего-то вроде foo({ return; 2 }) - foo никогда не выполняется.

@RalfJung return - это выражение типа ! , как и break или continue . У него есть тип, но его тип необитаемый.

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

Одна вещь, которая не была решена, - что делать с внутренними функциями, которые могут возвращать ! (т.е. uninitialized , ptr::read и transmute ). Что касается uninitialized , последнее, что я слышал, существует консенсус в отношении того, что он должен быть устаревшим в пользу типа MaybeUninit и возможного будущего &in , &out , &uninit ссылки. Для этого нет RFC, хотя есть более ранний RFC, в котором я предлагал отказаться от uninitialized только для типов, которые не реализуют новую черту Inhabited . Возможно, этот RFC следует отбросить и заменить на RFC «отказаться от uninitialized » и «добавить MaybeUninit »?

Для ptr::read , думаю, можно оставить его как UB. Когда кто-то называет ptr::read они утверждают, что данные, которые они читают, действительны, а в случае ! это определенно не так. Может быть, у кого-то есть более тонкое мнение по этому поводу?

Исправить transmute легко - просто сделайте ошибку, чтобы преобразовать его в необитаемый тип (вместо ICEing, как сейчас). Это был PR, чтобы исправить это, но он был закрыт по той причине, что нам все еще нужно лучше понять, как обрабатывать неинициализированные данные.

Итак, где мы находимся с этим сейчас? (/ cc @nikomatsakis)? Готовы ли мы двигаться дальше, отказавшись от поддержки uninitialized и добавив тип MaybeUninit ? Если мы вызовем внутреннюю панику при вызове с ! , будет ли это подходящей временной мерой, которая позволит нам стабилизировать ! ?

Также перечислены нерешенные проблемы:

Какие черты мы должны реализовать для ! ? Первоначальный PR # 35162 включает Ord и несколько других. Вероятно, это скорее проблема T-lib, поэтому я добавляю этот тег к проблеме.

В настоящее время существует довольно простой выбор: PartialEq , Eq , PartialOrd , Ord , Debug , Display , Error . Кроме Clone , который обязательно должен быть добавлен в этот список, я не вижу других жизненно важных. Нужно ли нам блокировать стабилизацию по этому поводу? Мы могли бы просто добавить больше импровизаций позже, если сочтем нужным.

Как реализовать предупреждения для людей, использующих запасной вариант (): Trait где это поведение может измениться в будущем?

Предупреждение resolve_trait_on_defaulted_unit реализовано и уже находится в стабильном состоянии.

Желаемая семантика для ! в приведении (# 40800)
Переменные какого типа должны возвращаться к ! (# 40801)

Это похоже на настоящие блокираторы. @nikomatsakis :

Имеет ли смысл для ! также реализовать Sync и Send ? Есть несколько случаев, когда типы ошибок имеют границы Sync и / или Send , где было бы полезно использовать ! как «невозможно сбой».

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

@Kixunil , не было бы более явным использовать здесь https://doc.rust-lang.org/beta/std/intrinsics/fn.unreachable.html ?

@RalfJung , да, но это нестабильно. Напоминает мне, что запрет на превращение в необитаемые типы было бы серьезным изменением.

@jsgf Извините, да, ! также подразумевает все признаки маркера ( Send , Sync , Copy , Sized ).

@Kixunil Хм, как жаль. Однако было бы намного лучше стабилизировать внутреннюю unreachable .

Не блокировщик стабилизации (потому что perf, а не семантика), но похоже, что Result<T, !> не использует необитаемость ! в макете перечисления или сопоставлении кода: https://github.com / ржавчина-ланг / ржавчина / вопросы / 43278

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

Может ли мел помочь ответить на некоторые более сложные вопросы относительно реализаций !: Trait ?

@ExpHP Я так не думаю. Автоматическая реализация !: Trait для признаков, которые не имеют связанных типов / констант, и только нестатические методы считаются правильными. Вопрос лишь в том, хотим мы этого или нет. Автоматическая реализация других трейтов на самом деле не имеет смысла, потому что компилятору потребуется вставить произвольные значения для связанных типов / констант и изобрести собственный произвольный статический метод impls.

В rustc уже есть линт resolve-trait-on-defaulted-unit . Следует ли поставить галочку напротив соответствующего пункта?

@canndrew. Неправильно ли устанавливать связанные типы в ! ?

@taralx в этом нет ничего плохого, но это помешает правильному коду работать. Обратите внимание, что следующий код компилируется сегодня:

trait Foo {
    type Bar;
    fn make_bar() -> Self::Bar;
}

impl Foo for ! {
    type Bar = (i32, bool);
    fn make_bar() -> Self::Bar { (42, true) }
}

@lfairy Это зависит от вашего мнения о том, какой код является "правильным". Что касается меня, я бы с радостью согласился с тем, что предоставленный вами код «недействителен», так как вы не сможете получить что-либо из ! .

@earthengine Я не понимаю, что вы пытаетесь ! , не имеет к этому никакого отношения.

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

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

Все, что мы делаем, должно быть равно или более консервативно с этим (что также не подходит).

@ Ericson2314 Я не понимаю, что это значит, не могли бы вы привести несколько примеров?

@SimonSapin Правило «больше никаких расхождений» означает, что нет panic!() или loop { } , но v: ! который уже находится в области видимости, - это нормально. При исключении подобных выражений многие черты имеют только одно возможное значение! тот тип проверяет. (У других может быть неформальный контракт, который исключает все, кроме одного, но мы не можем разобраться с ними автоматически.)

// two implementations: constant functions returning true and false.
// And infinitely more with side effects taken into account.
trait Foo { fn() -> bool }
// Exactly one implementation because the body is unreachable no matter what.
trait Bar { fn(Self) -> Self }

С математической точки зрения, ! является «начальным элементом», что означает, что для сигнатуры типа, в аргументах которой есть ! , существует ровно одна реализация, т. Е. Все реализации равны - когда наблюдается снаружи. Это потому, что все они не могут быть названы.

Что на самом деле препятствует стабилизации? Это только ситуация mem::uninitialized или что-то еще?

@ arielb1 : Думаю, это тоже # 40800 и # 40801. Вы знаете, какой у них статус?

Какие документы по этому поводу написаны, или люди планируют писать об этом? С недавними добавлениями к страницам примитивных типов в документации std (# 43529, # 43560), если! типа получить там страничку? Ссылка, вероятно, также должна быть обновлена, если это еще не сделано.

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

#[naked]
unsafe fn error() -> !{
  asm!("hlt");
}

Без необходимости использовать loop{} для проверки типов?

Изменить: хотя я полагаю, что использование core::intrinsics::unreachable может быть приемлемым в коде очень низкого уровня.

@ Eroc33, когда ! является типом, вы можете сделать это:

#[naked]
unsafe fn error() -> ! {
  asm!("hlt");
  std::mem::uninitialized()
}

А пока вы можете сделать это:

#[naked]
unsafe fn error() -> ! {
  asm!("hlt");

  enum Never {}
  let never: Never = std::mem::uninitialized();
  match never {}
}

@Kixunil ! - это тип на ночь, который вам нужно использовать asm! . Обычно это делается так:

#[naked]
unsafe fn error() -> ! {
    asm!("hlt");
    std::intrinsics::unreachable();
}

@Kixunil Это можно сделать проще, используя std::intrinics::unreachable() который точно указывает, что предыдущий код расходится.

@Kixunil Из вышеприведенного обсуждения возможность возврата std::mem::uninitialized ! похоже, может быть изменена? Разве я это неправильно понял?

Из приведенного выше обсуждения возможность возврата std::mem::uninitialized ! похоже, может быть изменена? Разве я это неправильно понял?

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

Я также думаю, что нам нужно стабилизировать std::intrinsics::unreachable где-нибудь в libcore и назвать его unchecked_unreachable или что-то еще, чтобы отличить его от макроса unreachable! . Я собирался написать для этого RFC.

@eddyb А, да, забыл, что asm!() нестабилен, поэтому также можно использовать std::intrinsics::unreachable() .

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

@ Eroc33 Насколько я понимаю, некоторые люди предлагают это сделать, но это всего лишь предложение, а не конкретное решение. И я против такого предложения, потому что оно нарушит обратную совместимость и вынудит Rust стать 2.0. Я не думаю, что в этом есть какой-то вред. Если людей беспокоят ошибки, лучше использовать lint.

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

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

Замена неинициализированного - union {!, T}. Вы можете стать недоступным
ptr :: read :: <* const!> () и многими другими подобными способами.

8 августа 2017 г., 15:58, "Мартин Хабовштиак" [email protected]
написал:

@eddyb https://github.com/eddyb Ах, да, забыл, что asm! () - это
нестабильно, поэтому также можно использовать std :: intrinsics :: unreachable ().

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

@ Eroc33 https://github.com/eroc33, насколько я понимаю, некоторые люди
предлагаю это сделать, но это всего лишь предложение, а не конкретное решение. И я
выступить против такого предложения, потому что это нарушит обратную совместимость,
превращение Rust в версию 2.0. Я не думаю, что в этом есть какой-то вред.
Если людей беспокоят ошибки, лучше использовать lint.

@canndrew https://github.com/canndrew Как вы думаете, почему неинициализированный
будет устаревшим? Я не могу в это поверить. Я считаю это чрезвычайно полезным,
поэтому, если не существует очень хорошей замены, я не вижу причин для
делать это.

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

-
Вы получаете это, потому что подписаны на эту беседу.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-320948013 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AApc0iEK3vInreO03Bt6L3EAByBHQCv9ks5sWFt3gaJpZM4JYi9D
.

@nagisa Спасибо! Похоже, это решило бы проблему на техническом уровне.

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

fn foo() -> ! { ··· }

И хотите использовать ? , вы не можете выполнить обычное преобразование в

fn foo() -> io::Result<!> { ··· }

Влияют ли на этот случай сложные вопросы о приведении и параметрах типа по умолчанию?

40801 Можно отметить галочкой.

Мы должны попытаться исправить # 43061, прежде чем стабилизировать ! .

Я не обнаружил каких-либо открытых проблем с упоминанием never_type, поэтому я отправил для этого новый: # 47563. Кажется, что все предполагает, что [!; 0] необитаем, но я могу создать его с помощью простого [] .

@dtolnay добавлен в заголовок

@canndrew, как ! стабилизировалось (за исключением правил, касающихся того, что является исчерпывающим). Я как бы потерял нить того, где мы находимся, и я думаю, что для этого нужен чемпион. У тебя есть время быть таким человеком?

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

Похоже, у @varkor уже есть

Хотя еще одна вещь: хотим ли мы вызвать панику mem::uninitialized::<!>() во время выполнения (в настоящее время это вызывает UB)? Или мы должны пока оставить такие изменения? Я не в курсе того, что происходит с рекомендациями по небезопасному коду.

Я думаю, что план по-прежнему https://github.com/rust-lang/rfcs/pull/1892 , о котором я только что заметил, что вы написали. :)

@RalfJung Есть что-то конкретное, что блокирует это? Я мог бы написать PR сегодня, который добавляет MaybeUninit и обесценивает uninitialized .

@canndrew Поскольку многие проекты хотят поддерживать все три канала выпуска, а uninitialized доступен в стабильной версии, предпочтительно начинать выдавать предупреждения об устаревании в Nightly только после того, как замена станет доступной в стабильном канале. Мягкое прекращение поддержки через doc-comments - это нормально.

Я создал PR для стабилизации ! : https://github.com/rust-lang/rust/pull/47630. Не знаю, готовы ли мы это объединить. Мы должны хотя бы подождать, чтобы увидеть, исправят ли

Я также думаю, что нам следует еще раз посетить https://github.com/rust-lang/rfcs/pull/1699, чтобы люди могли начать писать элегантные имплицитные черты за ! .

@canndrew : Хотя этот RFC не был принят, он очень похож на предложение в https://github.com/rust-lang/rust/issues/20021.

https://github.com/rust-lang/rust/issues/36479, вероятно, также следует добавить в заголовок проблемы.

@varkor очень похоже. Заметили ли вы какие-либо проблемы с вашим недостижимым кодом при работе с таким кодом, который теперь помечается как недостижимый ?:

impl fmt::Debug for ! {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        *self    // unreachable!
    }
}

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

@canndrew кое-что, что я защищал в последнее время - хотя мы еще не разработали формальный шаблон - это своего рода «итоговая проблема», которая пытается четко очертить то, что мы стабилизируем. Вы можете думать об этом как об отчете после RFC, пытаясь суммировать в виде маркированного списка основных характеристик того, что стабилизируется, но также и того, что не стабилизируется.

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

Как вы думаете, вы могли бы попытаться составить такую ​​вещь? Мы можем немного поговорить вместе, если вам нравится что-то вроде «наброска» - или я могу попытаться набросать его.

Связанный вопрос: должны ли мы попытаться разрешить https://github.com/rust-lang/rust/issues/46325 до стабилизации? Может, это не имеет значения.

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

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

@nikomatsakis

Как вы думаете, вы могли бы попытаться составить такую ​​вещь? Мы можем немного поговорить вместе, если вам нравится что-то вроде «наброска» - или я могу попытаться набросать его.

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

@nikomatsakis Что-то вроде этого?

Сводная проблема - стабилизация !

Что стабилизируется

  • ! теперь является полноценным типом и теперь может использоваться в любой позиции типа (например, RFC 1216 ). Тип ! может преобразовываться в любой другой тип, см. Пример https://github.com/rust-lang/rust/tree/master/src/test/run-fail/adjust_ Never.rs.
  • Вывод типа теперь по умолчанию для переменных неограниченного типа будет ! вместо () . Линт resolve_trait_on_defaulted_unit больше не используется. Например, если у вас есть что-то вроде:

    // We didn't specify the type of `x`. Under some circumstances, type inference
    // will pick a type for us rather than erroring
    let x = Deserialize::deserialize(data);
    

    По старым правилам это десериализует () , тогда как по новым правилам это десериализует ! .

  • Функциональный шлюз never_type стабилен, хотя некоторые из поведения, которые он использовал для гейта, теперь живут за новым шлюзом функции exhaustive_patterns (см. Ниже).

Что не стабилизируется

  • Исчерпывающее сопоставление с образцом для необитаемых типов. например.

    let x: Result<u32, !> = ...;
    let Ok(y) = x;
    

    Этот код по-прежнему будет жаловаться на то, что Ok(_) является опровержимым паттерном. Это можно исправить, используя вентиль функции exhaustive_patterns . См. RFC 1872 для получения информации о ходе решения этого вопроса. См. Https://github.com/rust-lang/rust/tree/master/src/test/ui/feature-gate-exhaustive-patterns.rs для тестового примера, который подтверждает, что это поведение по-прежнему ограничено.

должны ли мы попытаться разрешить # 46325 до стабилизации?

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

@canndrew

Что-то вроде этого?

Ага, спасибо! Замечательно.

Главное, чего не хватает, - это указатели на тестовые примеры, показывающие, как ведет себя ! . Аудитория должна быть командой lang или другими людьми, за которыми внимательно следят, я думаю, это не совсем нацелено на людей «общего назначения». Так, например, я хотел бы несколько примеров мест, где принуждение разрешено, или где можно использовать ! . Я также хотел бы увидеть тесты, которые показывают нам, что исчерпывающее сопоставление с образцом (без включения функции Gate Gate) все еще работает. Это должны быть указатели на репозиторий.

@canndrew

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

Что ж, это означает, что мы включим новый код, который немедленно изменит поведение (например, let x: ! = ... , я думаю, будет вести себя по-другому для некоторых выражений). Кажется, лучше всего разрешить, если мы сможем. Может быть, вы сделаете это серьезной ошибкой в ​​открытом PR, и мы сможем объединить это с существующей воронкой на PR?

@nikomatsakis Я снова обновил этот сводный выпуск,

Может быть, вы сделаете это серьезной ошибкой в ​​открытом PR, и мы сможем объединить это с существующей воронкой на PR?

Выполнено.

@rfcbot fcp слияние

Я предлагаю стабилизировать тип ! - или хотя бы его часть, как описано здесь . PR здесь .

Я хотел бы получить один бит данных - кратерный пробег. Я нахожусь в процессе перенастройки https://github.com/rust-lang/rust/pull/47630 (поскольку @canndrew сейчас не отвечает на пинги), чтобы мы могли получить эти данные.

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

  • [x] @Kimundi
  • [x] @alexcrichton
  • [] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [] @pnkfelix
  • [x] @sfackler
  • [x] @withoutboats

В настоящее время не перечислено никаких проблем.

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

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

О, я только что вспомнил пару вещей:

  • Мы должны рассматривать идею стабилизации этого только в новую эпоху . В частности, изменение резервных правил обратно несовместимо - пробег кратера даст нам своего рода нижнюю границу для выпадения осадков, но это только нижнюю границу. Но, может быть, мы сможем просто сохранить старые резервные правила, если только вы не живете в новую эпоху.
  • Во-вторых, я считаю, что часть плана здесь также заключалась в том, чтобы иметь некоторые руководящие принципы, когда уместно реализовать черту для ! . TL; DR заключается в том, что это нормально, если методы в трейте непригодны для использования без предварительного предоставления значения ! - поэтому реализация Clone для ! - это нормально, я думаю , но реализация Default - нет. Другими словами, если реализация impl потребует от вас panic! без разбора, это плохой знак. знак равно

@nikomatsakis Можем ли мы изменить резервное правило в новую эпоху, но все же сделать ! как тип, доступный в эпоху 2015 года?

@nikomatsakis

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

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

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

Это упоминается в документации для !

@SimonSapin

Можем ли мы изменить резервное правило в новую эпоху, но все же сделать! как тип имеющийся в эпоху 2015 года?

да

@canndrew

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

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

TL; DR заключается в том, что это нормально, если методы в трейте непригодны для использования без предварительной передачи значения !

Это обычное правило - вы добавляете impl, когда можете реализовать его разумным способом, где «реализовать его разумным способом» исключает панику, но включает «ex falso» UB при наличии неверных данных.

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

Может быть, было бы полезно иметь безопасный метод fn absurd(x: !) -> ! который задокументирован как безопасный способ выразить недостижимый код или что-то в этом роде где-нибудь в libstd? Я думаю, для этого был RFC ... или, по крайней мере, проблема RFC: https://github.com/rust-lang/rfcs/issues/1385

@RalfJung Разве это не функция absurd просто identity ? Почему это полезно?

Это не то же самое, что встроенная функция unsafe fn unreachable() -> ! которая не принимает никаких аргументов и имеет неопределенное поведение.

Ну да, в основном так. Я использовал возвращаемый тип ! в смысле «не прекращается». (Обычный тип absurd - fn absurd<T>(x: !) -> T , но в Rust это тоже бесполезно.)

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

Кажется, у меня тоже возникла не та проблема ... Я думал, где-то было обсуждение такой функции "ex falso". Похоже, я ошибаюсь.

fn absurd<T>(x: !) -> T также можно записать как match x {} , но это трудно придумать, если вы не видели его раньше. По крайней мере, стоит отметить это в rustdoc и в книге.

fn абсурд(x:!) -> T также можно записать как match x {}, но это трудно придумать, если вы не видели его раньше.

Правильно, поэтому я предложил метод где-то в libstd. Не уверен, где лучше всего.

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

fn absurd<T>(x: !) -> T также можно записать как match x {}

Его также можно записать просто как x (AFAIK) - вам нужно только match если у вас есть пустой enum .

Функция absurd(x: !) -> T была бы полезна для перехода к функциям более высокого порядка. Мне это нужно (например) при связывании фьючерсов, один из которых имеет тип ошибки ! а другой тип ошибки T . Хотя выражение типа ! может преобразоваться в T , это не означает, что ! и T будут объединены, поэтому иногда вам все равно нужно вручную преобразовать тип.

Точно так же я думаю, что у Result -подобных типов должен быть метод .infallible() который преобразует Result<T, !> в Result<T, E> .

Типы, похожие на результат, должны иметь метод .infallible (), который преобразует Resultк результату.

Я ожидал, что такой метод будет иметь тип Result<T, !> -> T .

Я ожидал, что такой метод будет иметь тип Result-> Т.

Разве это не то же самое, что unwrap ? Паника будет устранена, поскольку случай Err недоступен.

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

unwrap читается как assert - «предварительная проверка во время выполнения» (IIRC были даже предложения переименовать его, но они пришли слишком поздно ... к сожалению). Если вы не хотите и не нуждаетесь в проверке во время выполнения, infallible сделает это явным.

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

@rfcbot fcp отменить

@aturon и @pnkfelix не отметили свои флажки.

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

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

  • [x] @alexcrichton
  • [x] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [] @pnkfelix
  • [x] @sfackler
  • [x] @withoutboats

В настоящее время не перечислено никаких проблем.

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

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

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

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

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

И это оставляет две фактические регрессии: oplog-0.2.0 (на самом деле это его зависимость bson-0.3.2, которая сломана) и rspec-1.0.0-beta.4. Оба они полагаются на старое резервное поведение, хотя, к счастью, они ломаются во время компиляции, а не во время выполнения. Я отправлю PR, чтобы починить эти ящики.

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

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

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

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

Более того, если кто-то захочет наброситься на это и сказать: «Ага! Rust в конце концов нарушает обратную совместимость! Их обещание стабильности при достижении 1.0 было ложью!» то очень вдумчивое обсуждение этого вопроса показывает, что решение было принято нелегко, даже с учетом незначительного практического воздействия.

Хорошо, я говорю, давайте просто изменим это.

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

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

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

Эти незавершенные флажки в верхнем посте просто еще не отмечены? Есть ли что-нибудь отмененное в этом списке?

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

В основном это очень минимальный набор.

@nikomatsakis Я только что создал сводную проблему здесь: https://github.com/rust-lang/rust/issues/48950

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

Я бы нашел это полезным для таких типов, как StreamFuture которые создают составные типы ошибок из потенциально ! типов ошибок . Используя шаблон ! в map_err , если тип ошибки в какой-то момент в будущем изменится, тогда вызов map_err перестанет компилироваться, а не сделает что-то другое.

В качестве примера приведен

let foo: Result<String, (!, String)> = Ok("hello".to_owned());

это позволило бы написать более явный

let bar: Result<String, String> = foo.map_err(|(!, _)| unreachable!());

вместо потенциально подверженного ошибкам

let bar: Result<String, String> = foo.map_err(|_| unreachable!());

или чуть менее подверженный ошибкам

let bar: Result<String, String> = foo.map_err(|(e, _)| e);

РЕДАКТИРОВАТЬ: перечитайте https://github.com/rust-lang/rfcs/pull/1872, в комментариях упоминается введение шаблона ! . Это чисто в контексте выражений match хотя я не уверен, распространяется ли это на аргументы закрытия / функции.

@ Nemo157

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

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

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

fn foo() -> Result<String, String> {
    let bar: Result<String, (!, String)> = Ok("hello".to_owned());
    Ok(bar?)
}

это все равно расширилось бы, как сегодня (_Я не уверен на 100%, что это правильное расширение, но кажется достаточно близким_)

fn foo() -> Result<String, String> {
    let bar: Result<String, (!, String)> = Ok("hello".to_owned());
    Ok(match Try::into_result(bar) {
        Result::Ok(e) => e,
        Result::Err(e) => return Try::from_err(From::from(e)),
    })
}

но ветка Result::Err не будет проверяться по типу, и вы не получите ошибку the trait bound `std::string::String: std::convert::From<(!, std::string::String)>` is not satisfied которую получаете сегодня.

Я не думаю, что это совместимо с тем, что предлагал RFC 1872, поскольку существует явная рука, которая выполняет неглубокое сопоставление, она не обязательно полагается на действительность значения ! и потенциально может запустите связанную ветвь «безопасно», пока эта ветка вообще не использует e.0 .

! - это положительный тип, такой как bool , поэтому невозможно сопоставить его с шаблоном в списке аргументов закрытия без расширения синтаксиса закрытия, чтобы разрешить несколько ветвей. например. возможно, мы могли бы позволить замыканиям bool -> u32 записывать что-то вроде (уродливый гипотетический синтаксис) [~ |false| 23, |true| 45 ~] -> u32 , тогда замыкание любого пустого типа на u32 можно было бы просто записать [~ ~] -> u32 .

Что касается исходных примеров, вы можете написать foo.map_err(|_: (!, _)| unreachable!()) хотя я предпочитаю foo.map_err(|(e, _)| e) поскольку он избегает использования unreachable! .

! - положительный тип

Что вы подразумеваете под положительным типом?

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

Списки аргументов уже поддерживают неопровержимые подшаблоны, например

let foo: Result<String, ((), String)> = Ok("hello".to_owned());
let bar: Result<String, String> = foo.map_err(|((), s)| s);

Я бы просто рассмотрел шаблон ! как неопровержимый шаблон, который соответствует всем 0 значениям типа ! , так же, как () - это неопровержимый шаблон, который соответствует всем 1 значениям тип () .

Я бы просто подумал о! шаблон как неопровержимый шаблон, который соответствует всем 0 значениям! type, так же, как (), является неопровержимым шаблоном, который соответствует всем 1 значениям типа ().

Ну, но 0! = 1.;) Бессмысленно даже писать какой-либо код после сопоставления на ! - так, например, если тип аргумента (!, i32) , тогда |(!, x)| code_goes_here - это глупо, так как этот код в любом случае мертв. Я бы, вероятно, написал |(x, _)| match x {} чтобы очень четко указать, что x является элементом ! . Или, используя функцию absurd я предложил выше , |(x, _)| absurd(x) . Или, может быть, |(x, _)| x.absurd() ?

Можно представить себе другой синтаксис , который позволяет одной записи нет коды, как то , что @canndrew писал выше. match имеет любое количество ветвей, поэтому, в частности, возможно не иметь ветки, но у замыканий есть ровно один регистр, поэтому единственные шаблоны, которые имеют смысл, - это те, которые имеют ровно 1 способ сопоставления. Не 0 способов.

Жаль, что сейчас мы не можем добавить impl<T> From<!> for T . (Он перекрывается с From<T> for T , разве что что-то особенное?) Вероятно, мы не сможем добавить его позже. (В циклах выпуска после стабилизации ! в некоторых ящиках может быть реализовано From<!> for SomeConcreteType .)

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

Я бы серьезно подумал о разовом взломе

Тогда этот взлом должен приземлиться в ближайшие несколько дней. (Или получите поддержку во время бета-тестирования.)

@SimonSapin Как быстро / легко можно добавить такой хак? Было бы действительно отстойно никогда не иметь From<!> for T .

В качестве альтернативы мы могли бы быстро выдать предупреждение против реализации From<!> for SomeConcreteType .

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

К сожалению, специализация:

  • Потребовалось бы, чтобы From::from было изменено на default fn , которое было бы «видимым» вне std. Я не знаю, хотим ли мы сделать это в чертах std, если специализация нестабильна.
  • Как принято в RFC 1210, он специально не поддерживает функцию языка, о которой я думал (устранение неоднозначности двух перекрывающихся имплиментов, где ни один из них не является более конкретным, чем другой, путем написания третьего импликации для пересечения).

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

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

Существует ли существующий RFC для формализации этой идеи «разрешить произвольное перекрытие, если все задействованные имплименты недостижимы»? Похоже, интересный подход для рассмотрения.

Не то, что я знаю из. Чтобы быть ожидаемым, сам impl не является недостижимым, а скорее все методы внутри не вызываются (включая никакие связанные типы или константы, поскольку они всегда являются «вызываемыми»). Я полагаю, я мог бы быстро написать один, если будет сочтено, что взлом заблокирован для такой вещи.

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

Интересно , что другие impls кроме T: From<!> не могут быть добавлены после ! стабилизируется. Вероятно, мы должны попытаться обработать все такие разумные импы до стабилизации, в отличие от импов для ! в целом, где нет такой спешки.

Перекрестная публикация из этого комментария :

Мне было интересно - каков классический пример того, почему откат на () "должен" быть изменен? То есть, если бы мы продолжили откат к (), но все же добавили!, Это, безусловно, позволило бы избежать подобных проблем - и вернуться к! не является оптимальным, учитывая, что бывают случаи, когда откат происходит к переменной типа, которая в конечном итоге влияет на «живой» код (намерение, конечно, иное, но, как мы обнаружили, трудно предотвратить утечки).

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

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

Во время встречи мы немного отказались от некоторых вариантов здесь - я не хочу вносить изменения, в основном потому, что это меняет семантику существующего кода в некоторых угловых случаях, и не ясно, что новые правила лучше . Но в основном я думаю, что мы решили, что было бы здорово попытаться снова собрать плюсы и минусы каждого дизайна, чтобы мы могли провести более полное обсуждение. Я хотел бы увидеть список вариантов использования, а также подводных камней. @cramertj упомянули о своем желании Ok(33) имеет тип, который возвращается к Result<i32, !> , вместо того, чтобы ошибаться, как это было бы сегодня; Я думаю, они тогда говорили, что сохранение отката от return как () будет несовместимо с таким изменением (хотя они ортогональны) и может привести к конфликтам в будущем. Это честно.

Проблема с Err feedback (# 40801) заключается в том, что там тоже есть крайние случаи, например:

let mut x = Ok(22);
x = Err(Default::default());

где тип x в конечном итоге выводится на Result<T, !> .

Мне вспоминается отдельный план, который я должен был попробовать и посмотреть, сможем ли мы определить (и, возможно, предупредить? Или ошибку?), "Повлиял ли резервный ! на" живой код "- это оказалось довольно сложно, хотя кажется как мы могли бы на практике использовать множество кейсов (например, тот, и, вероятно, случай @SimonSapin ).

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

К вашему сведению, эта функция вызвала регресс в 1.26: # 49932

(Установка меток на https://github.com/rust-lang/rust/issues/35121#issuecomment-368669041.)

Должен ли элемент контрольного списка о принуждении к ! указывать на https://github.com/rust-lang/rust/issues/50350 вместо ссылки на эту проблему?

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

@remexre Я думаю, что лучшее описание проблемы, которая приводит к отмене стабилизации, находится на https://github.com/rust-lang/rust/issues/49593

Таким образом, было бы работоспособным решением было бы использовать Box в особом случае как подтип
Коробка? Есть ли какие-то объектно-безопасные черты, с которыми нельзя справиться таким образом?

В воскресенье, 8 июля 2018 г., 8:12 Ральф Юнг [email protected] написал:

@remexre https://github.com/remexre Я думаю, что лучшее описание
проблема, которая приводит к возврату стабилизации, находится на # 49593
https://github.com/rust-lang/rust/issues/49593

-
Вы получаете это, потому что вас упомянули.

Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-403286892 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AEAJtcnsEaFmHrrlHhuQeVOkR8Djzt50ks5uEgVLgaJpZM4JYi9D
.

>

Спасибо,
Натан

Это обсуждение действительно должно перейти на https://github.com/rust-lang/rust/issues/49593 , но ключевой его частью является то, что Box::<T>::new требует T: Sized , но это new предполагает наличие T = Error (признака DST) на основе возвращаемого типа.

Помимо неэлегантности особого случая, есть ли какие-либо проблемы со специальным регистром Box::new :

Box::new : T -> Box<U>
where T <: U,
      T: Sized

или

Box::new : T -> Box<U>
where Box<T> <: Box<U>,
      T: Sized

Прежде всего, мы должны подумать, какого размера должен быть ! . Математики предложат что-то вроде Inf , но на самом деле это будет usize::MAX , поэтому мы гарантируем, что любые попытки выделить пространство для этого типа не удастся или, по крайней мере, panic .

Если ! равно Sized тогда ничто не мешает нам скомпилировать Box::new(x as !) , но это, по сути, еще один способ panic! , поскольку никакая модель памяти на самом деле не может alocate usize::MAX байт.

Мне не кажется очевидным, что ! должен иметь бесконечный / usize::MAX размер, а не ZST?

use std::mem::size_of;
enum Void {}
fn main() { println!("{}", size_of::<Void>()); }

в настоящее время 0.

Причина была объяснена в представленном тексте.

bool : два допустимых значения => log (2) / log (2) = 1 бит
() : 1 допустимые значения => log (1) / log (2) = 0 бит
! : 0 допустимых значений => log (0) / log (2) = Inf биты

Как человек, не разбирающийся в соответствующей теории, я вижу, что следование формуле log(x)/log(y) сводится к погоне за элегантностью теоретической модели в ущерб практическому использованию. (AKA слишком умен для собственного блага)

Интуитивно кажется, что ! также должен иметь нулевой размер, потому что:

  • bool : требуется место, чтобы различать два действительных значения => log (2) / log (2) = 1 бит в дополнение к системе типов
  • () : требуется пробел, чтобы отличить одно допустимое значение => пробел не требуется за пределами системы типов
  • ! : требуется пробел, чтобы отличать недопустимые значения => пробел не требуется за пределами системы типов

Ну, на самом деле это -infinity, что имеет смысл, так как размещение! как поле в структуру по существу превращает структуру в! также (c + -inf = -inf). Итак, поскольку мы имеем дело с usize, 0 - это ближайшее значение, которое мы должны представить. (Я думаю, что фактическая реализация в rustc даже использует -inf).

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

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

0 допустимые значения => log (0) / log (2) = Inf биты

log (0) не определен; это не бесконечно.

С другой стороны, размер usize::MAX - это надежный способ принудить к ненаселению. Согласен?

@CryZe @varkor

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

@CryZe
Ваша формула -Inf + c == -Inf имеет смысл, но при замене -Inf на 0usize она больше не действует.

С другой стороны, если арифметика "ограничена": любые переполнения вычисляются как usize::MAX тогда usize::MAX идеально соответствует формуле.

Модель мозга: если у вас есть объект типа ! , для выделения структуры, которая его содержит, вам понадобится структура еще большего размера, чем ! , но самый большой размер структуры, который вы можете изобразить, - это usize::MAX . Таким образом, вам нужно по-прежнему usize::MAX .

Почему бы просто не «зафиксировать» размер любого типа, который содержит тип void ( ! или определяемый пользователем enum Void {} ), равным size=0,alignment=0 ? Мне это кажется семантически более разумным ...

@remexre
Потому что это не работает. Пример: sizeof(Result<usize,!>) == sizeof(usize) .

Что ж, Result<usize, !> не содержит напрямую ! ; sizeof(Result::Ok(usize)) == 8 , sizeof(Result::Err(!)) == 0

Нет, в моем предложении sizeof(Result::Err(!)) == usize::MAX , потому что Result::Err(!) :: Result<!,!> .

Правило должно быть таким: в enum s размер ! считается меньшим, чем все другие значения размера, но в structs это максимальный размер, который может когда-либо отображаться.

Заключение:

  • ! не является летним временем. DST - это не Sized не потому, что у них нет размера, а потому, что информация о размере скрыта. Но для ! мы знаем об этом все.
  • ! должен иметь размер, поэтому Box::new(x as !) должно быть разрешено, по крайней мере, для компиляции.
  • struct s содержит ! должен иметь размер, как если бы он был ! , enum s содержит ! непосредственно в варианте должен иметь размер как если такого варианта не существует.

Либо мы рассматриваем sizeof(!)==0 либо sizeof(!)==usize::MAX , мы должны ввести некоторую специальную арифметику, чтобы разрешить вышеуказанное:

sizeof(!)==0 : в структурах, 0 + n = 0 : беспокоится :, в перечислениях max(0,n) = n : blush :.
sizeof(!)==usize::MAX : в структурах, usize::MAX + n =usize::MAX : нейтральное_лицо :, в перечислениях, max(usize::MAX,n) = n : flush :.

Однако есть преимущество в пользу usize::MAX : оно технически предотвращает любые попытки, даже unsafe , построить такой объект, поскольку это невозможно ни в одной реальной системе.

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

Я имею в виду, если unsafe махинации разрешены: *transmute::<Box<usize>, Box<!>>(Box::new(0)) дает мне ! независимо от его размера. usize::MAX делает более вероятным, что компилятор выдаст ошибку, если кто-то попытается сделать std::mem::zeroed<!>() , я полагаю, но я бы возложил бремя на человека, который пишет этот небезопасный код, а не на математику странность выше.

Обратите внимание, что ! имеющий размер _actually_ 0 - не MAX и -INF -sathibited-to-0 - необходим для обработки частичной инициализации, как было найдено в https://github.com/rust-lang/rust/issues/49298#issuecomment -380844923 (и реализовано в https://github.com/rust-lang/rust/pull/50622).

Если вы хотите изменить то, как это работает, напишите об этом в новом выпуске или сделайте PR; это не то место.

@remexre

Box::new : T -> Box<U>
where T <: U,
      T: Sized

Box (в основном) тип библиотеки, а его метод new определен в синтаксисе Rust в src/liballoc/boxed.rs . В вашем описании предполагается оператор <: , которого нет в Rust. Это связано с тем, что подтипирование в Rust существует только через параметры времени жизни. Если вы хотите прочитать или посмотреть больше:

! может быть принужден к любому типу, но слабее , чем быть любого типа. (Например, Vec<&'static Foo> также равно Vec<&'a Foo> для любых 'a , но неявного преобразования из Vec<!> в Vec<Foo> : http: / /play.rust-lang.org/?gist=82d1c1e1fc707d804a57c483a3e0198f&version=nightly&mode=debug&edition=2015)

Я думаю, что идея состоит в том, что вы можете делать все, что считаете приемлемым, в режиме unsafe , но вам придется столкнуться с UB, если вы не сделаете это с умом. Если официальный размер ! равен usize::MAX , это должно предупредить пользователя о том, что этот тип никогда не должен создаваться.

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

@ScottAbbey
Я сосредотачиваюсь на проблеме, чтобы убедиться, что у Box::new(x as !) нет проблем, чтобы мы могли стабилизировать это. Предлагаемый способ - сделать так, чтобы ! имел размер. Если ZST является стандартом, все должно быть в порядке. Я больше не спорю. Просто реализуйте так, что подойдет sizeof(!)==0 .

естественно установить его выравнивание также usize :: MAX

usize::MAX - это не степень двойки, поэтому выравнивание недопустимо. Кроме того, LLVM не поддерживает выравнивания, превышающие 1 << 29 . И ! любом случае не должно иметь большого выравнивания, потому что let x: (!, i32); x.1 = 4; должен занимать только 4 байта стека, а не гигабайты или больше.

Если хотите продолжить, создайте новую ветку для этого обсуждения,

Проблема здесь в том, что Box::new(!) - это Box<$0> где мы ожидаем Box<Error> . При наличии достаточного количества аннотаций типов нам потребуется принуждение Box<$0> к Box<Error> , что позже оставит нас с $0 умолчанию равным ! .

Однако $0 - это переменная типа, поэтому принуждение не выполняется, и тогда мы получаем $0 = Error .

Один из хакерских способов исправить ситуацию - обнаружить, что у нас есть ограничение $0: Sized (следующие подтипы?), И вставить привязанное к нему принуждение, так что Box по-прежнему вызывается для данного типа.

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

Вот и все, если во время принуждения мы сталкиваемся с обязательством формы $0: Unsize<Y> где Y определенно не имеет размера, а $0 определенно имеет размер, не рассматривайте это как двусмысленность, а скорее относитесь к этому как к «хорошо» и продолжайте принуждение без размера.

@ arielb1

Так чем же это отличается от принуждения Box::new(()) к Box<Debug> ? std::error::Error - это признак типа Debug , а не тип типа [u8] . std::io::Error другой стороны, Sized .

У нас никогда не бывает проблем со следующим:

use std::fmt::Debug;

fn f(x:()) -> Box<Debug> {
    Box::new(x)
}

fn main() {
}

@earthengine разница между этой проблемой и вашим кодом:

| Написано | Box::new(!): Box<Debug> | Box::new(()): Box<Debug> |
| ------ | ------ | ------- |
| Выполнено | Box::new(! as Debug): Debug | (Box::new(()) as Box<Debug>): Debug |

По сути, поскольку ! может принуждать к чему угодно, перед вызовом Box::new его принуждают к Debug Box::new . Это не вариант с () , поэтому во втором случае проблем нет - принуждение происходит после Box::new .

@RalfJung

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

Например, следующее не должно компилироваться:

let v: str = !;
let v: [u8] = !;
let v: dyn Debug = !;

Просто потому, что вы не можете заменить ! любым существующим выражением Rust, чтобы оно скомпилировалось.

Редактировать

Это не вариант с ()

Так может кто-нибудь объяснить, почему? Если () as dyn Debug не компилируется, ! as dyn Debug не компилируется, и наоборот. &() as &Debug компилируется, так же как и &! as &Debug , без проблем. Если () as dyn Debug может когда-нибудь скомпилироваться, проблема, которая у нас есть сегодня, будет повторяться для () , поэтому разработчикам DST RFC придется решить эту проблему, и поэтому она решит ту же проблему, что и мы. за ! .

! ни к чему не принуждает, потому что это невозможно. "Ex falso quodlibet". Таким образом, все ваши примеры должны компилироваться - нет веских теоретических причин делать специальное исключение для нестандартных типов.

Это даже не нарушает «DTS не может существовать», потому что ! тоже не может существовать. :)

() as dyn Debug не компилируется

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

Однако, возможно, вы указываете здесь на возможное решение (и, возможно, это то, что @ arielb1 также предлагал выше): можно ли ограничить принуждение ! чтобы оно применялось только в том случае, если размер целевого типа (известен) ? Для этого нет веской теоретической причины, кроме практической. : D А именно, чтобы это помогло решить эту проблему.

Когда я сказал «DTS не может существовать», я имел в виду синтаксический. Например, невозможно иметь локальную переменную с типом str .

С другой стороны, ! не может существовать семантически. Ты можешь написать

let v = exit(0);

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

Итак, вот причина: мы разрешаем приведение ! к любому типу, только если вы можете написать выражение, имеющее тот же тип. Если тип не может существовать даже синтаксически, это не должно быть разрешено.

Это также применимо к безразмерным rvalue, поэтому до того, как мы получим безразмерные rvalue, принуждение ! к безразмерным типам не допускается, пока мы не узнаем, как обрабатывать неотразимые rvalue. И только в этот момент мы можем начать думать об этой проблеме (кажется, одно очевидное решение позволяет Box::new получать значения без размера).

Например, невозможно иметь локальную переменную с типом str.

Это просто ограничение текущей реализации: https://github.com/rust-lang/rust/issues/48055

@RalfJung

Проблема не в этом.

Опять же, предположим, что мы находимся в ситуации, когда Box::new(!: $0): Box<dyn fmt::Debug> где $0 - это переменная типа.

Тогда компилятор может довольно легко вывести, что $0: Sized (потому что $0 равно параметру типа Box::new , который имеет границу T: Sized ).

Проблема возникает, когда компилятор должен выяснить, какой тип принуждения использовать для преобразования Box<$0> в Box<dyn fmt::Debug> . С точки зрения «местного» обзора есть 2 решения:

  1. Имейте принуждение CoerceUnsized , требующее Box<$0>: CoerceUnsized<Box<dyn fmt::Debug>> . Это допустимая программа для $0 = ! и по умолчанию она будет скомпилирована для этого.
  2. Имейте принуждение к идентичности с помощью $0 = dyn fmt::Debug . Это несовместимо с требованием $0: Sized .

Компилятор не хочет открывать слишком много вещей при рассмотрении двусмысленностей, поскольку это может вызвать как проблемы с производительностью, так и проблемы с отладкой, поэтому он довольно глупо выбирает, какое принуждение использовать (в частности, мы не используем 'нет T: CoerceUnsized<T> , поэтому, если компилятор выберет вариант 1, он может довольно легко "застрять"), и в итоге он выберет вариант 2, который не работает. У меня возникла идея сделать его немного умнее и выбрать вариант 1.

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

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

Имейте принуждение идентичности с $ 0 = dyn fmt :: Debug. Это несовместимо с требованием $ 0: размер.

Я с трудом понимаю, почему мы должны разрешать let v: str=! но не let v: str; . Если вы даже не можете объявить переменную определенного типа, почему вы можете привязать к ней (возможно, невозможное) значение?

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

Итак, я пришел к выводу, что проблема Box::new(!) as Box<Error> - это проблема блокировки для нестандартных rvalue, а не для типа ! . Правила принуждения ! должны быть такими:

Вы можете написать let v: T тогда и только тогда, когда вы можете написать let v: T = ! .

Фактическое значение затем будет оцениваться с помощью языка. В частности, после того, как у нас есть нестандартные rvalue, у нас есть Box::new(! as dyn Debug) но до этого я бы сказал нет.

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

Добавьте в код функциональный шлюз, который запускает безразмерное принуждение, если безразмерное rvalue включено, попробуйте это принуждение, в противном случае пропустите его. Если это единственное доступное принуждение, сообщение об ошибке должно иметь вид «привязка признака str: std::marker::Sized не выполняется», как в аналогичных случаях. Так

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

адресован: просто проверка функций.

Тем временем добавьте новую проблему для нестандартных rvalue и

Интересно.

Этот

fn main() {
    let _:str = *"";
}

компилируется, но

fn main() {
    let v:str = *"";
}

не надо. Это даже не связано с типом ! . Могу ли я создать для этого проблему?

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

В первом примере шаблон _ особенный тем, что он не только соответствует чему-либо (например, привязке локальной переменной), но даже не создает переменную вообще. Таким образом, этот код такой же, как fn main() { *""; } без let . Разыменование ссылки (даже DST), а затем ничего не делать с результатом бесполезно, но кажется действительным, и я не уверен, что он должен быть недействительным.

Верно. Но это действительно сбивает с толку, особенно следующие

fn main() {
    let _v:str = *"";
}

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

Насколько я помню, единственная разница между _v и v заключается в том, что верхний знак подчеркивания подавляет предупреждения о неиспользованных значениях.

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

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

Верный. Помимо того, что вы сказали, let _ = foo() приводит к немедленному вызову drop , тогда как _v удаляется только тогда, когда он выходит за пределы области действия (например, v будет ).

В самом деле, это то, что я имел в виду под « _ особенный».

Итак, теперь похоже, что отказ от всех безразмерных rvalues ​​(если не включена функция «unsized rvalues») в дикой природе был бы критическим изменением, хотя я думаю, что эффект был бы незначительным.

Затем я по-прежнему предлагаю иметь шлюз функций, чтобы запретить принуждение от ! к нестандартным rvalue. Это отрицает код вроде let _:str = return; , но я не думаю, что кто-то будет использовать его в коде.

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

Вопрос в том, если мы стабилизируем его без принуждения к DST, чтобы исправить https://github.com/rust-lang/rust/issues/49593 , захотим ли мы восстановить такое принуждение позже, когда добавим нестандартные значения r, и будем ли мы иметь возможность сделать это, не нарушая снова https://github.com/rust-lang/rust/issues/49593 ?

Мое предыдущее предложение включает это. # 49593 должен быть проблемой блокировки для некорректных значений r.

Мое личное предложение для окончательного решения - позволить Box::new (может также включать некоторые другие важные методы) принимать параметры без размера и допускать двусмысленность в аналогичных ситуациях.

FWIW, _ - это не что иное, как имя специальной переменной. Это совершенно отдельный синтаксис шаблона, который ничего не делает с совпадающим местом . Сопоставление с образцом работает с местами , а не со значениями .
Ближайшим к связыванию переменных является ref _x , но даже в этом случае вам, вероятно, понадобится NLL, чтобы избежать конфликтного заимствования в результате этого. Это работает, кстати:

// The type `str` is the type of the place being matched. `x` has type `&str`.
let ref x: str = *"foo";
// Fully equivalent to this:
let x: &str = &*"foo";

@eddyb

Вы не поняли мою точку зрения. Я предполагаю, что в Rust в настоящее время нет никаких нестандартных значений r, которые вообще могут появляться в коде. Однако оказывается, что они появляются в let _:str = *"" . Хотя такое значение может существовать недолго, на синтаксическом уровне оно существует. Что-то вроде привидения ...

Вместо этого ваши примеры технически полностью законны, и не в этом суть. str имеет размера, но &str имеет размер.

Однако оказывается, что они появляются в let _: str = * "". Хотя такое значение может существовать недолго, на синтаксическом уровне оно существует. Что-то вроде привидения ...

Подобный «призрак» есть в выражении вроде &*foo . Сначала вы разыменовываете foo а затем берете адрес результата, не перемещая его . Так что, например, это не то же самое, что & { *foo } .

@earthengine @SimonSapin Здесь не существует нестандартных rvalue ("выражение значения"). Только lvalues ​​("пространственные выражения"). *"foo" - это место , и для сопоставления с образцом не требуется значение, только место.

Имена «lvalue» и «rvalue» являются пережитками и несколько вводят в заблуждение и в C, но еще хуже в Rust, потому что правая часть let является «lvalue» (выражением места), несмотря на название.
(Выражения присваивания - единственное место, где имена и правила C имеют смысл в Rust)

В ваших примерах let x = *"foo"; , значение требуется для привязки к x .
Точно так же &*"foo" создает выражение места, а затем заимствует его, без необходимости создавать значение без размера, тогда как {*"foo"} всегда является выражением значения и, следовательно, не допускает типы без размера.

В https://github.com/rust-lang/rust/pull/52420 я ударил это

let Ok(b): Result<B, !> = ...;
b

больше не работает. Это намеренно?

AFAIK да - история непогрешимого паттерна сложна и имеет отдельный элемент функции от самого ! . Также см. Https://github.com/rust-lang/rfcs/pull/1872 и https://github.com/rust-lang/rust/issues/48950.

@ Ericson2314, в частности, функция, которую вам нужно добавить в liballoc - это exhaustive_patterns .

Спасибо!!

Интересный разговор о внутренностях:

Если вы собираетесь это сделать, почему бы не лечить! как переменная вывода?

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

https://github.com/rust-lang/rust/issues/49593 теперь исправлен. Это было причиной предыдущего возврата к стабилизации. Предыдущий отчет о стабилизации здесь . Попробуем еще раз!

@rfcbot fcp слияние

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

https://github.com/rust-lang/rust/pull/50121 не только отменил стабилизацию, но и семантику отката. Хотим ли мы к этому вернуться?

Оставшийся неустановленный флажок в описании проблемы:

Какие черты мы должны реализовать для ! ? Первоначальный PR # 35162 включает Ord и несколько других. Вероятно, это скорее проблема T-lib, поэтому я добавляю этот тег к проблеме.

Мы можем добавить импы позже, не так ли? Или это блокиратор?

@SimonSapin Открыл https://github.com/rust-lang/rust/issues/57012. Я ожидал, что откат к ! будет снова включен как часть этого изменения, да (хотя давайте обсудим это по вопросу стабилизации).

Перекрестные ссылки: https://github.com/rust-lang/rust/issues/58184

По-видимому, есть ошибка / дыра в том, что тип never за воротами функций, и можно сослаться на него в Stable: https://github.com/rust-lang/rust/issues/33417#issuecomment -467053097

Изменить: подано https://github.com/rust-lang/rust/issues/58733.

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

trait MyTrait {
    type Output;
}

impl<T> MyTrait for fn() -> T {
    type Output = T;
}

type Void = <fn() -> ! as MyTrait>::Output;

fn main() {
    let _a: Void;
}

Он компилируется в Rust 1.12.0, который, как мне кажется, является первым с https://github.com/rust-lang/rust/pull/35162. В 1.11.0 это ошибки с:

error: the trait bound `fn() -> !: MyTrait` is not satisfied [--explain E0277]
  --> a.rs:12:13
   |>
12 |>     let _a: Void;
   |>             ^^^^
help: the following implementations were found:
help:   <fn() -> T as MyTrait>

error: aborting due to previous error

В каком состоянии он?

Насколько мне известно, статус существенно не изменился с момента моего резюме в https://github.com/rust-lang/rust/issues/57012#issuecomment -467889706.

В каком состоянии он?

@hosunrise : в настоящее время заблокирован на https://github.com/rust-lang/rust/issues/67225

Возможно, я здесь далеко, но конкретные проблемы, которые упоминаются в обсуждении lint (https://github.com/rust-lang/rust/issues/66173), кажутся разрешимыми, если каждое перечисление имеет ветку ! в система типов?

Замечу, что это не относится к проблеме, упомянутой в OP https://github.com/rust-lang/rust/issues/67225 , которая по-прежнему будет проблемой.

Возможно, я здесь далеко, но конкретные проблемы, которые упоминаются в обсуждении lint (# 66173), кажутся разрешимыми, если каждое перечисление имеет ветвь ! в системе типов?

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

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