Rust: Problema de rastreamento para promover `!` A um tipo (RFC 1216)

Criado em 29 jul. 2016  ·  259Comentários  ·  Fonte: rust-lang/rust

Problema de rastreamento para rust-lang / rfcs # 1216, que promove ! a um tipo.

Problemas pendentes para resolver

Eventos e links interessantes

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

Comentários muito úteis

@petrochenkov Esqueça ! e apenas olhe para enums.

Se eu tiver um enum com duas variantes, posso corresponder a dois casos:

enum Foo {
    Flim,
    Flam,
}

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

Isso funciona para qualquer n, não apenas dois. Então, se eu tiver um enum com nenhuma variante, posso compará-lo com nenhum caso.

enum Void {
}

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

Até agora tudo bem. Mas veja o que acontece, nós tentamos combinar padrões aninhados. Aqui está a correspondência em um enum de duas variantes dentro de Result .

enum Foo {
    Flim,
    Flam,
}

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

Podemos expandir o padrão interno dentro do externo. Existem duas variantes de Foo , portanto, há dois casos para Err . Não precisamos de instruções de correspondência separadas para corresponder em Result e em Foo . Isso funciona para enums com qualquer número de variantes ... _exceto zero_.

enum Void {
}

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

Por que não deveria funcionar? Eu não chamaria de consertar isso adicionando "suporte especial" para tipos desabitados, eu chamaria de consertar uma inconsistência.

Todos 259 comentários

Huzzah!

Há uma implementação WIP disso aqui: https://github.com/canndrew/rust/tree/bang_type_coerced

Seu status atual é: ele é compilado com old-trans e pode ser usado, mas tem alguns testes com falha. Alguns testes falham devido a um bug que faz com que um código como if (return) {} trave durante a transação. Os outros testes têm a ver com otimização de tempo de link e sempre foram bugs para mim, então não sei se eles têm algo a ver com minhas mudanças.

Meu roteiro atual é:

  • Faça funcionar com o MIR. Espero que não seja muito difícil, pois foi assim que comecei a implementá-lo, mas tenho tido um problema onde o MIR cria segfault durante a compilação.
  • Limpe o material de divergência obsoleto do compilador ( FnOutput , FnDiverging e relacionados).
  • Oculte o novo tipo atrás de um portão de recursos. Isso significaria, quando o recurso está desativado:

    • ! só pode ser analisado como um tipo na posição de retorno.

    • O padrão de variáveis ​​de tipo divergente é () .

  • Descubra como podemos gerar avisos de compatibilidade quando um () padrão é usado para resolver uma característica. Uma maneira de fazer isso seria adicionar um novo tipo ao AST chamado DefaultedUnit . Este tipo se comporta como () e se transforma em () em algumas circunstâncias, mas emite um aviso quando resolve um traço (como () ). O problema com essa abordagem é que acho que será difícil detectar e corrigir todos os bugs da implementação - acabaríamos quebrando o código das pessoas para evitar que seu código fosse quebrado.

Existe algo que precisa ser adicionado a esta lista? Serei eu trabalhando nisso? E este branch deve ser movido para o repositório principal?

Descubra como podemos gerar avisos de compatibilidade quando um () padrão é usado para resolver uma característica. Uma maneira de fazer isso seria adicionar um novo tipo ao AST chamado DefaultedUnit . Este tipo se comporta como () e se transforma em () em algumas circunstâncias, mas emite um aviso quando resolve um traço (como () ). O problema com essa abordagem é que acho que será difícil detectar e corrigir todos os bugs da implementação - acabaríamos quebrando o código das pessoas para evitar que seu código fosse quebrado.

@eddyb , @ arielb1 , @anyone_else : O que você

Para quais características devemos implementar!? O PR # 35162 inicial inclui Ord e alguns outros.

Não deveria ! implementar automaticamente _todos_ os traços?

Este tipo de código é bastante comum:

trait Baz { ... }

trait Foo {
    type Bar: Baz;

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

Eu esperaria que ! fosse utilizável por Foo::Bar para indicar que um Bar nunca pode realmente existir:

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

Mas isso só é possível se ! implementar todas as características.

@tomaka Há um RFC sobre isso: https://github.com/rust-lang/rfcs/pull/1637

O problema é que se ! implementa Trait ele também deve implementar !Trait ...

O problema é que se! implementa o Traço, ele também deve implementar! Traço ...

Então, caso especial ! para que ele ignore qualquer requisito de característica?

@tomaka ! não pode implementar automaticamente _todos_ traços porque os traços podem ter métodos estáticos e tipos / consts associados. Ele pode implementar automaticamente características que têm apenas métodos não estáticos (isto é, métodos que usam Self ).

Quanto a !Trait , alguém sugeriu que ! poderia implementar automaticamente ambos Trait _and_ !Trait . Não tenho certeza se isso é válido, mas suspeito que os traços negativos não são sólidos.

Mas sim, seria bom se ! pudesse autoimplementar Baz no exemplo que você deu para exatamente esses tipos de casos.

Quando exatamente padronizamos as variáveis ​​de tipo divergente para () / ! e quando lançamos um erro sobre não ser capaz de inferir informações de tipo suficientes? Isso está especificado em algum lugar? Eu gostaria de poder compilar o seguinte código:

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

Mas o primeiro erro que recebo é " unable to infer enough type information about _ ". Neste caso, acho que faria sentido que _ assumisse como padrão ! . No entanto, quando eu estava escrevendo testes sobre comportamento padrão, achei surpreendentemente difícil tornar padrão uma variável de tipo. É por isso que testes são tão complicados.

Eu gostaria de ter uma ideia clara de exatamente por que temos esse comportamento padrão e quando ele deve ser invocado.

Mas o primeiro erro que recebo é "não é possível inferir informações de tipo suficientes sobre _". Nesse caso, acho que faria sentido _ ser o padrão!. No entanto, quando eu estava escrevendo testes sobre comportamento padrão, achei surpreendentemente difícil tornar padrão uma variável de tipo. É por isso que esses testes são tão complicados.

É uma ideia muito boa na minha opinião. O mesmo para None que seria Option<!> por exemplo.

@carllerche

O teste unit_fallback é certamente uma forma estranha de demonstrá-lo. Uma versão menos macro-ey é

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);
}

Somente a variável de tipo criada por return / break / panic!() padrão para qualquer coisa.

Quando exatamente padronizamos as variáveis ​​de tipo divergente para () /! e quando lançamos um erro sobre não ser capaz de inferir informações de tipo suficientes? Isso está especificado em algum lugar?

Defina "especificado". :) A resposta é que certas operações, que não são afetadas em nenhum lugar fora do código, requerem que o tipo seja conhecido naquele ponto. O caso mais comum é o acesso de campo ( .f ) e envio de método ( .f() ), mas outro exemplo é deref ( *x ), e provavelmente há mais um ou dois. Na maioria das vezes, existem razões decentes para isso ser exigido - de modo geral, existem várias maneiras divergentes de proceder e não podemos progredir sem saber qual delas seguir. (Seria possível, potencialmente, refatorar o código para que essa necessidade pudesse ser registrada como uma espécie de "obrigação pendente", mas é complicado fazer isso.)

Se você chegar ao final do fn, então executaremos todas as operações de seleção de características pendentes até que um estado estacionário seja alcançado. Este é o ponto onde os padrões (por exemplo, i32, etc) são aplicados. Esta última parte é descrita no RFC falando sobre parâmetros de tipo padrão especificados pelo usuário (embora esse RFC em geral precise funcionar).

@canndrew, aqueles são um pouco semelhantes a https://github.com/rust-lang/rust/issues/12609

Rapaz, esse é um bug antigo! Mas sim, eu diria que meu # 36038 é um idiota (pensei já tê-lo visto em algum lugar antes). Não acho que ! possa realmente ser considerado para o horário nobre até que isso seja consertado.

Está planejado para ! afetar a exaustividade da correspondência de padrões? Exemplo de comportamento atual possivelmente errado:

#![feature(never_type)]

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

@tikue sim, é um dos bugs listados acima.

@lfairy ops , não vi porque não estava listado nas caixas de seleção no topo. Obrigado!

Existem planos para implementar From<!> for T e Add<T> for ! (com um tipo de saída de ! )? Eu sei que é um pedido muito específico - estou tentando usar os dois neste PR .

From<!> for T definitivamente. Add<T> for ! provavelmente caberá à equipe de libs decidir, mas eu pessoalmente acho que ! deve implementar todas as características para as quais tenha uma implementação lógica e canônica.

@canndrew Obrigado! Estou acostumado com o traço Nothing do scala, que é um subtipo de todos os tipos e, portanto, pode ser usado em qualquer lugar em que um valor possa aparecer. No entanto, eu definitivamente simpatizo com o desejo de compreender os efeitos que impl All for ! ou similar teria no sistema de tipos da ferrugem, especialmente em relação aos limites de traços negativos e semelhantes.

Por https://github.com/rust-lang/rfcs/issues/1723#issuecomment -241595070 From<!> for T tem problemas de coerência.

Ah certo, sim. Precisamos fazer algo sobre isso.

Seria bom se os impls de traços pudessem declarar explicitamente que são substituídos por outros impls de traços. Algo como:

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

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

Isso não é coberto pela especialização? Edit: Eu acredito que esta é a regra de implemento da rede.

É uma alternativa viável para evitar o suporte especial para tipos desabitados tanto quanto possível?

Todos esses match (res: Res<A, !>) { Ok(a) /* No Err */ } , métodos especiais para Result parecem muito inventados, como recursos por causa de recursos, e não parecem valer o esforço e a complexidade.
Eu entendo que ! é um recurso de estimação do @canndrew e ele deseja desenvolvê-lo ainda mais, mas talvez seja uma direção errada desde o início e # 12609 nem seja um bug?

@petrochenkov # 12609 não é um recurso especial para o tipo Never. É apenas uma correção de bug para detectar algum código claramente inacessível.

@petrochenkov Esqueça ! e apenas olhe para enums.

Se eu tiver um enum com duas variantes, posso corresponder a dois casos:

enum Foo {
    Flim,
    Flam,
}

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

Isso funciona para qualquer n, não apenas dois. Então, se eu tiver um enum com nenhuma variante, posso compará-lo com nenhum caso.

enum Void {
}

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

Até agora tudo bem. Mas veja o que acontece, nós tentamos combinar padrões aninhados. Aqui está a correspondência em um enum de duas variantes dentro de Result .

enum Foo {
    Flim,
    Flam,
}

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

Podemos expandir o padrão interno dentro do externo. Existem duas variantes de Foo , portanto, há dois casos para Err . Não precisamos de instruções de correspondência separadas para corresponder em Result e em Foo . Isso funciona para enums com qualquer número de variantes ... _exceto zero_.

enum Void {
}

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

Por que não deveria funcionar? Eu não chamaria de consertar isso adicionando "suporte especial" para tipos desabitados, eu chamaria de consertar uma inconsistência.

@petrochenkov Talvez eu tenha entendido mal o que você estava dizendo. Há duas questões discutidas no tópico # 12609:

(0) Este código deve ser compilado?

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

(1) Este código deve ser compilado?

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

Conforme implementado atualmente, as respostas são "não" e "sim", respectivamente. # 12609 fala sobre (1) especificamente no problema em si, mas eu estava pensando em (0) quando respondi. Quanto às respostas _devem_ ser, acho que (0) definitivamente deveria ser "sim", mas para (1) também não tenho certeza.

@canndrew
Pode ser razoável fazer (1), ou seja, padrões inalcançáveis, um lint e não um erro grave, independentemente dos tipos desabitados, RFC 1445 contém mais exemplos de porque isso pode ser útil.

Em relação a (0) estou mais ou menos convencido com a sua explicação. Ficarei totalmente feliz se esse esquema se basear naturalmente na implementação da verificação de padrões no compilador e remover mais código especial do que adicionar.

A propósito, fiz um PR para tentar corrigir (0) aqui: https://github.com/rust-lang/rust/pull/36476

Ficarei totalmente feliz se esse esquema se basear naturalmente na implementação da verificação de padrões no compilador e remover mais código especial do que adicionar.

Não faz, estranhamente. Mas isso provavelmente é mais um artefato da maneira como eu o hackeava no código existente, em vez de não haver uma maneira elegante de fazer isso.

Acho que para macros é útil para (1) não ser um erro grave.

Acho que (1) deve compilar por padrão, mas dar um aviso do mesmo lint como aqui:

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

Já que estamos nisso, faz sentido tornar alguns padrões irrefutáveis ​​em face de ! ?

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

Concordo que fazer correspondências não exaustivas é um erro, mas inacessíveis, ou seja, padrões redundantes apenas um aviso parece fazer sentido.

Eu tive alguns PRs sentados por um tempo apodrecendo. Há algo que eu possa fazer para ajudar na revisão? Provavelmente, é necessário haver alguma discussão sobre eles. Estou falando sobre # 36476, # 36449 e # 36489.

Eu contra a ideia de pensar que o tipo divergente (ou tipo "inferior" na teoria dos tipos) é o mesmo que o tipo enum vazio (ou tipo "zero" na teoria dos tipos). Eles são criaturas diferentes, embora ambos não possam ter nenhum valor ou instância.

Na minha opinião, um tipo de fundo só pode aparecer em qualquer contexto que represente um tipo de retorno. Por exemplo,

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

Mas você não deve dizer

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

ou

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

ou mesmo

type ID=fn(!)->!;

já que nenhuma variável deve ter o tipo ! e, portanto, nenhuma variável de entrada deve ter o tipo ! .

Vazio enum é diferente neste caso, você pode dizer

enum Empty {}

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

então

 match Empty::new() {}

Isso quer dizer que há uma diferença fundamental entre ! e Empty : você não pode declarar que nenhuma variável tenha o tipo ! mas pode fazê-lo para Empty .

@earthengine Qual é a vantagem de introduzir esta distinção (aos meus olhos, completamente artificial) entre dois tipos de tipos inabitáveis?

Inúmeras razões para não haver tal distinção foram levantadas - por exemplo, ser capaz de escrever Result<!, E> , fazer com que isso interaja bem com funções divergentes, embora ainda seja capaz de usar as operações monádicas em Result , como map e map_err .

Na programação funcional, o tipo vazio ( zero ) é freqüentemente usado para codificar o fato de que uma função não retorna ou que um valor não existe. Quando você diz bottom type, não está claro para mim a qual conceito de teoria de tipo você está se referindo; geralmente bottom é o nome de um tipo que é um subtipo de todos os tipos - mas, nesse sentido, nem ! nem um enum vazio são bottom em Rust. Mas, novamente, zero não ser um tipo bottom não é incomum na teoria de tipos.

Isso quer dizer que há uma diferença fundamental entre ! e Empty : você não pode declarar qualquer variável com o tipo ! mas pode fazê-lo para Empty .

É isso que esta RFC está corrigindo. ! não é realmente um "tipo" se você não pode usá-lo como um tipo.

@RalfJung
Esses conceitos são da lógica linear https://en.wikipedia.org/wiki/Linear_logic

Como há alguns erros no texto anterior deste post, eu os retirei. Assim que eu acertar, irei atualizar isso.

top é um valor que pode ser usado de todas as maneiras possíveis

O que isso significa em subtipagem? Que pode se tornar qualquer tipo? Porque isso é bottom então.

@eddyb Cometi alguns erros, aguarde minhas novas atualizações.

Este PR - que foi fundido em um dia - conflita muito mal com o meu PR de correspondência de padrões, que está em análise há mais de um mês. Desculpe, meu segundo PR de correspondência de padrão desde que o primeiro se tornou intransponível e teve que ser reescrito depois de ficar em revisão por dois meses.

ffs galera

@canndrew argh, sinto muito por isso. = (Eu estava planejando r + 'ing seu PR hoje, também ...

Desculpe, não quero reclamar, mas isso tem se arrastado. Desisti de tentar manter vários PRs ao mesmo tempo e agora estou tentando fazer essa mudança desde setembro. Acho que parte do problema é a diferença de fuso horário - vocês estão todos online enquanto estou na cama, então é difícil conversar sobre essas coisas.

Dado que as exceções são um buraco lógico e necessário no sistema de tipos, eu gostaria de saber se alguém pensou seriamente em lidar com elas de forma mais formal com!.

Usando https://is.gd/4EC1Dk como exemplo e ponto de referência, o que aconteceria se ultrapassássemos isso, e.

1) Trate qualquer função que pode entrar em pânico, mas não retorna um erro ou resultado, tendo sua assinatura de tipo alterada implicitamente de -> Foo para -> Result<Foo,!> 2) any Resultadotypes would have their Error types implicitly be converted to enum AnonMyErrWrapper {Die (!), Error} `` `
3) Desde então! é um anúncio de tamanho zero inabitável. Haveria custo zero para a conversão entre os tipos e a conversão implícita poderia ser adicionada para torná-lo compatível com versões anteriores.

O benefício, é claro, é que as exceções são efetivamente levantadas para o sistema de tipos e seria possível raciocinar sobre elas, realizar análises estáticas nelas, etc.

Eu sei que isso seria ... não trivial do ponto de vista da comunidade, se não do ponto de vista técnico. :)

Além disso, isso provavelmente se sobrepõe a um possível sistema de efeitos futuros.

@tupshin é uma mudança muita ginástica. Eu recomendo desativar o desenrolamento e usar "Resultado" manualmente se você quiser essa clareza. [E aliás, este problema não é realmente o lugar para trazer tal coisa --- o design de ! não é algo que você está questionando, então este é puramente um trabalho futuro.]

Percebo como seria quebrá-lo, pelo menos sem uma ginástica significativa, e um ponto justo sobre ser um trabalho totalmente futuro. E sim, estou muito feliz com o que está planejado! até agora, na medida em que vai. :)

@nikomatsakis Com relação às questões pendentes, essas duas podem ser marcadas

  • Limpeza de código de # 35162, reorganize o typeck um pouco para os tipos de thread por meio da posição de retorno em vez de chamar expr_ty
  • Resolver o tratamento de tipos desabitados em partidas

Vou começar outra rachadura com este:

  • Como implementar avisos para pessoas que confiam em (): Recuo de característica em que esse comportamento pode mudar no futuro?

Estou planejando adicionar um sinalizador a TyTuple para indicar que ele foi criado por padrão de uma variável de tipo divergente e, em seguida, verificar esse sinalizador na seleção de característica.

@canndrew

Estou planejando adicionar um sinalizador ao TyTuple para indicar que ele foi criado padronizando uma variável de tipo divergente e, em seguida, verificando esse sinalizador na seleção de característica.

OK ótimo!

Bem, talvez ótimo. =) Parece um pouco complexo ter dois tipos semanticamente equivalentes ( () vs () ) que são distintos dessa forma, mas não consigo pensar em uma maneira melhor. = (E muito do código deve ser preparado para isso de qualquer forma, graças às regiões.

O que quero dizer é que estou adicionando um bool a TyTuple

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

o que indica que devemos gerar avisos nesta tupla (unidade) se tentarmos selecionar certas características nela. Isso é mais seguro do que minha abordagem original de adicionar outro TyDefaultedUnit .

Esperançosamente, só precisamos manter esse bool por perto por um ciclo de aviso.

Com relação ao problema de uninitialized / transmute / MaybeUninitialized , acho que um curso de ação sensato seria:

  • Adicione MaybeUninitialized à biblioteca padrão, em mem
  • Adicione uma verificação em uninitialized que seu tipo é conhecido por ser habitado. Emita um aviso caso contrário com uma nota dizendo que este será um erro grave no futuro. Sugira usar MaybeUninitialized vez disso.
  • Adicione uma verificação em transmute que seu tipo "para" só é desabitado se o tipo "de" também estiver. Da mesma forma, gere um aviso que se tornará um erro grave.

Pensamentos?

Parece haver alguma discordância sobre a semântica de &! . Depois de # 39151, com o portão de recurso never_type habilitado, ele é tratado como desabitado nas partidas.

Se impls automáticos de características para ! não estiverem lá, pelo menos implementar características apropriadas de std seria muito útil. Uma grande limitação de void::Void é que ele vive fora de std e isso significa que o cobertor impl<T> From<Void> for T é impossível e isso limita o tratamento de erros.

Acho que pelo menos From<!> devem ser implementados para todos os tipos e Error , Display e Debug devem ser implementados para ! .

Acho que pelo menos From<!> deve ser implementado para todos os tipos

Infelizmente, isso está em conflito com o From<T> for T impl.

Infelizmente, isso está em conflito com o From<T> for T impl.

Pelo menos até que a especialização de implemento de rede seja implementada.

Bons pontos. Espero ver isso feito um dia.

Deve [T; 0] subtipo um [!; 0] e &[T] subtipo um &[!] ? Parece-me intuitivo que a resposta deva ser “sim”, mas a implementação atual pensa o contrário.

[!; 0] é habitada e também &[!] então eu diria que não. Além disso, não estamos usando subtipagem desta vez.

Ambos [!; 0] e &[!] não devem ser desabitados, ambos os tipos podem aceitar o valor [] (ou &[] ).

Ninguém disse que eles são ou deveriam ser desabitados.

let _: u32 = unsafe { mem::transmute(return) };
Em princípio, isso poderia estar OK, mas o compilador reclama "transmutando entre 0 bits e 32 bits".

Transmutar ! -> () é permitido no entanto. Não tenho nenhum motivo particular para apontar isso; é uma inconsistência, mas não consigo pensar em um problema prático ou teórico com isso.

Eu não esperaria subtipagem e me oporia a isso com o fundamento de evitar complicar todos os tipos de lógica no compilador. Não quero subtipagem que não esteja relacionada a regiões ou ligação de região, basicamente.

É possível implementar Fn , FnMut e FnOnce para ! de uma forma que seja genérico para todos os tipos de entrada e saída?

No meu caso, tenho um construtor genérico que pode assumir uma função, que será invocada durante a construção:

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

Como func é um Option , um construtor usando None não pode inferir o tipo de F . Portanto, uma implementação fn new deve usar Bang:

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

A função func do construtor deve ser semelhante a isto para ser chamada tanto em Builder<!> quanto em Builder<F> :

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

Agora o problema: Atualmente, os traços Fn* não estão implementados para ! . Além disso, você não pode ter um tipo associado genérico para características (por exemplo, Output em Fn ). Portanto, é possível implementar a família de características Fn* para ! de uma forma que seja genérica para todos os tipos de entrada e saída?

Isso parece contraditório. <! as Fn>::Output precisa ser resolvido para um único tipo?

<! as Fn>::Output é ! , não é?

Em teoria <! as Fn>::Output deveria ser ! , mas o verificador de tipo atual deseja que os tipos associados correspondam exatamente: https://is.gd/4Mkxfm

@SimonSapin Ele precisa ser resolvido para um único tipo, e é por isso que levanto minha pergunta. Do ponto de vista da linguagem, é um comportamento totalmente correto. Mas do ponto de vista da biblioteca, o uso de ! em contextos de funções genéricas seria muito limitado se o comportamento atual fosse mantido.

Deve o seguinte código

#![feature(never_type)]

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


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

imprime 10 ? No momento, ele não imprime nada. Ou alguma outra solução alternativa que atrasará return até a impressão?

Quero dizer, como eu disse antes, na verdade existem dois tipos diferentes de tipo ! : um é preguiçoso e o outro é ansioso. A notação usual denota um ansioso, mas às vezes podemos precisar do preguiçoso.


Como @RalfJung está interessado em uma versão anterior que usa &return , ajustei o código novamente. E novamente, meu ponto é que devemos ser capazes de atrasar return mais tarde. Poderíamos usar fechamentos aqui, mas eu só posso usar return , não break ou continue etc.

Por exemplo, seria bom se pudéssemos escrever

#![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);
    }
}

com o intervalo adiado até que 5 seja impresso.

@earthengine Não - isso definitivamente não deve imprimir nada. ! é desabitado, o que significa que nenhum valor do tipo ! pode ser criado. Portanto, se você tiver uma referência a um valor do tipo ! , você está dentro de um bloco de código morto.

A razão pela qual return tem o tipo ! é porque ele diverge. O código que usa o resultado de uma expressão return nunca pode ser executado porque return nunca "termina".

oO Eu não sabia que você podia escrever &return . Isso não faz sentido, por que você deveria ter o endereço de return ? ^^

Mas, de qualquer maneira, @earthengine em seu código, os argumentos são avaliados antes de quit_with_print ser chamado. A avaliação do segundo argumento executa return , que termina o programa. É como escrever algo como foo({ return; 2 }) - foo nunca é executado.

@RalfJung return é uma expressão do tipo ! , assim como break ou continue . Tem um tipo, mas seu tipo é inabitado.

! foi implementado e está funcionando todas as noites há um ano. Eu gostaria de saber o que precisa ser feito para movê-lo em direção à estabilização.

Uma coisa que não foi resolvida é o que fazer sobre os intrínsecos que podem retornar ! (ou seja, uninitialized , ptr::read e transmute ). Por uninitialized , a última vez que ouvi houve um consenso de que deveria ser preterido em favor de um tipo MaybeUninit e possível futuro &in , &out , &uninit Referências de um RFC anterior onde propus descontinuar uninitialized apenas para tipos que não implementam um novo traço Inhabited . Talvez esse RFC deva ser descartado e substituído por RFCs "deprecate uninitialized " e "adicionar MaybeUninit "?

Por ptr::read , acho que não há problema em deixar como UB. Quando alguém chama ptr::read eles estão afirmando que os dados que estão lendo são válidos e, no caso de ! , definitivamente não são. Talvez alguém tenha uma opinião mais matizada sobre isso?

Consertar transmute é fácil - apenas torne um erro transmutar para um tipo desabitado (em vez de ICEing como acontece atualmente). Houve um PR para corrigir isso, mas foi fechado com o motivo de que ainda precisamos de uma ideia melhor de como tratar dados não inicializados.

Então, onde estamos com isso no momento? (/ cc @nikomatsakis)? Estaríamos prontos para prosseguir com a suspensão de uninitialized e a adição de um tipo MaybeUninit ? Se fizéssemos esses intrínsecos entrarem em pânico quando chamados de ! , isso seria uma medida provisória adequada que nos permitiria estabilizar ! ?

Existem também as questões pendentes listadas:

Quais características devemos implementar para ! ? O PR # 35162 inicial inclui Ord e alguns outros. Este é provavelmente mais um problema de T-libs, então estou adicionando essa tag ao problema.

Atualmente há uma seleção bastante básica: PartialEq , Eq , PartialOrd , Ord , Debug , Display , Error . Além de Clone , que definitivamente deveria ser adicionado a essa lista, não consigo ver nenhum outro que seja de vital importância. Precisamos bloquear a estabilização nisso? Poderíamos apenas adicionar mais impls mais tarde, se acharmos adequado.

Como implementar avisos para pessoas que dependem de (): Trait substituto, onde esse comportamento pode mudar no futuro?

O aviso resolve_trait_on_defaulted_unit está implementado e já está estável.

Semântica desejada para ! em coerção (# 40800)
Quais variáveis ​​de tipo devem cair em ! (# 40801)

Estes parecem ser os verdadeiros bloqueadores. @nikomatsakis : Você tem alguma ideia concreta sobre o que fazer a respeito disso, que está apenas esperando para ser implementado?

Faz sentido que ! também implemente Sync e Send ? Existem vários casos em que os tipos de erro têm limites de Sync e / ou Send , onde seria útil usar ! como "não pode falhar".

@canndrew Eu discordo de proibir isso em código inseguro. O unreachable crate usa transmutar para criar um tipo não habitado e, portanto, sugere o otimizador de que determinado ramo do código nunca pode ocorrer. Isso é útil para otimizações. Pretendo fazer minha própria caixa usando essa técnica de maneira interessante.

@Kixunil não seria mais explícito usar https://doc.rust-lang.org/beta/std/intrinsics/fn.unreachable.html aqui?

@RalfJung sim, mas isso é instável. Lembra-me que proibir a transmutação em tipos desabitados seria uma mudança significativa.

@jsgf Desculpe, sim, ! também implementa todas as características do marcador ( Send , Sync , Copy , Sized ).

@Kixunil Hmm, que pena. No entanto, seria muito melhor estabilizar o unreachable intrínseco.

Não é um bloqueador de estabilização (por causa do perf, não da semântica), mas parece que Result<T, !> não está usando a inabitabilidade de ! no layout enum ou codegen de correspondência: https://github.com / rust-lang / rust / issues / 43278

Esta é uma ideia tão vaga que quase me sinto culpada por deixá-la cair aqui, mas:

O giz poderia ajudar a responder algumas das perguntas mais difíceis em relação às implementações de !: Trait ?

@ExpHP Acho que não. Autoimplementação !: Trait para características que não têm tipos / consts associados e apenas métodos não estáticos são conhecidos por serem sólidos. É apenas uma questão de saber se queremos fazer isso ou não. A implementação automática de outras características não faz sentido porque o compilador precisaria inserir valores arbitrários para os tipos / consts associados e inventar seus próprios métodos estáticos arbitrários impls.

Já existe um fiapo resolve-trait-on-defaulted-unit no rustc. O item correspondente deve ser marcado?

@canndrew É ! ?

@taralx não é

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

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

@lfairy Depende da sua opinião sobre o que é um código "válido". Para mim, ficaria feliz em aceitar que o código fornecido seja "inválido", já que você não deve ser capaz de obter nada de ! .

@earthengine Não vejo o que você está tentando fazer. Você pode chamar um método estático em um tipo sem ter uma instância dele. Se você pode ou não "conseguir alguma coisa" de ! não tem nada a ver com isso.

Não há razão para que você não possa obter nada de ! do tipo . Você não pode obter qualquer coisa de um ! porém, é por isso que métodos não-estáticos em ! poderia ser derivada sem forçar quaisquer decisões sobre o programador.

Acho que a regra mais fácil de lembrar seria implementar qualquer característica para a qual existe exatamente 1 implementação válida (excluindo aquelas que adicionam mais divergência do que ! s no escopo já causam).

Qualquer coisa que escolhermos deve ser igual ou mais conservadora com isso (que nenhum implante automático também se encaixa).

@ Ericson2314 Não entendo o que isso significa, você poderia dar alguns exemplos?

@SimonSapin A panic!() ou loop { } , mas v: ! que já está no escopo está bom. Com expressões como essas descartadas, muitos traços têm apenas um implemento possível! que verifica o tipo. (Outros podem ter um contrato informal que exclui todos, exceto 1 impl, mas não podemos lidar com eles automaticamente.)

// 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 }

Matematicamente falando, ! é um "elemento inicial", o que significa que para uma assinatura de tipo que tem ! em seus argumentos, há exatamente uma implementação, ou seja, todas as implementações são iguais - quando observado de fora. Isso ocorre porque todos eles não podem ser chamados.

O que está realmente impedindo isso de estabilização? É apenas a situação mem::uninitialized ou outra coisa?

@ arielb1 : Acho que são # 40800 e # 40801 também. Você sabe qual é o status deles?

Quais documentos foram escritos sobre isso, ou as pessoas estão planejando escrever sobre isso? Com as recentes adições às páginas de tipo primitivo nos documentos std (# 43529, # 43560), o! digite obter uma página lá? A referência provavelmente também deve ser atualizada, se ainda não foi.

Existem planos para permitir especificar que o conjunto está divergindo para que possamos escrever coisas como?

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

Sem ter que usar loop{} para apaziguar o verificador de tipo?

Edit: Embora eu suponha que usar core::intrinsics::unreachable pode ser aceitável em código de nível muito baixo.

@ Eroc33 quando ! é um tipo, você pode fazer o seguinte:

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

Até então, você pode fazer isso:

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

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

@Kixunil ! é um tipo noturno que você precisa usar asm! . Isso geralmente é feito como:

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

@Kixunil Isso pode ser feito mais facilmente usando std::intrinics::unreachable() que especifica exatamente que o código anterior está divergindo.

@Kixunil Da discussão acima std::mem::uninitialized ser capaz de retornar ! parece estar sujeito a alterações? A menos que eu não tenha entendido isso?

Da discussão acima std::mem::uninitialized ser capaz de retornar ! parece estar sujeito a alterações? A menos que eu não tenha entendido isso?

Eu acho que uninitialized acabará sendo totalmente obsoleto e, como uma lacuna, entrará em pânico em tempo de execução quando usado com ! (embora isso não afetaria este caso).

Também acho que precisaremos estabilizar std::intrinsics::unreachable em algum lugar no libcore e nomeá-lo unchecked_unreachable ou algo para distingui-lo da macro unreachable! . Tenho pretendido escrever um RFC para isso.

@eddyb Ah, sim, esqueci que asm!() é instável, então std::intrinsics::unreachable() pode ser usado.

@ tbu- Claro, prefiro muito esse em vez de criar um tipo desabitado. O problema é que ele é instável, então, se fosse de alguma forma impossível marcar de forma insegura um branch de código como inalcançável, isso não apenas quebraria o código existente, mas também o tornaria impossível de corrigir. Acho que isso prejudicaria seriamente a reputação do Rust (especialmente considerando os principais desenvolvedores que afirmam que é estável). Por mais que eu ame Rust, tal coisa me faria reconsiderar se trata de uma linguagem útil.

@ Eroc33 meu entendimento é que algumas pessoas sugerem fazê-lo, mas é mera sugestão, não uma decisão definitiva. E eu me oponho a essa sugestão porque quebraria a compatibilidade com versões anteriores, forçando o Rust a se tornar 2.0. Não acho que haja mal nenhum em apoiá-lo. Se as pessoas estão preocupadas com bugs, o lint seria mais apropriado.

@canndrew Por que você acha que uninitialized será preterido? Eu não posso acreditar nisso. Acho extremamente útil, então, a menos que exista um substituto muito bom, não vejo nenhuma razão para fazê-lo.

Finalmente, gostaria de reiterar que concordo que intrinsics::unreachable() é melhor do que os hacks atuais. Dito isso, me oponho a proibir ou mesmo reprovar esses hacks até que haja um substituto suficiente.

A substituição para não inicializado é união {!, T}. Você pode ficar inacessível por
ptr :: read :: <* const!> () ing e muitas outras maneiras semelhantes.

Em 8 de agosto de 2017, 15:58, "Martin Habovštiak" [email protected]
escreveu:

@eddyb https://github.com/eddyb Ah, sim, esqueci que asm! () é
instável, então std :: intrinsics :: unreachable () também pode ser usado.

@ tbu- https://github.com/tbu- Claro, prefiro muito esse em vez de
criando tipo desabitado. O problema é que é instável, então se fosse
de alguma forma impossível marcar de forma insegura um branch de código como inalcançável, não
apenas quebrar o código existente, mas também torná-lo impossível de corrigir. Eu penso tal coisa
prejudicaria gravemente a reputação do Rust (especialmente considerando os principais desenvolvedores
orgulhosamente alegando que é estável). Por mais que eu ame Rust, tal coisa
me faça reconsiderar isso sendo uma linguagem útil.

@ Eroc33 https://github.com/eroc33 meu entendimento é que algumas pessoas
sugiro fazê-lo, mas é mera sugestão, não uma decisão definitiva. E eu
opor-se a tal sugestão porque quebraria a compatibilidade com versões anteriores,
forçando Rust a se tornar 2.0. Não acho que haja mal nenhum em apoiá-lo.
Se as pessoas estão preocupadas com bugs, o lint seria mais apropriado.

@canndrew https://github.com/canndrew Por que você acha que não foi inicializado
será descontinuado? Eu não posso acreditar nisso. Acho extremamente útil,
então, a menos que exista um substituto muito bom, não vejo nenhuma razão para
fazendo isso.

Finalmente, gostaria de reiterar que concordo que intrinsics :: unreachable ()
é melhor do que os hacks atuais. Dito isso, eu me oponho a proibir ou mesmo
descontinuar esses hacks até que haja um substituto suficiente.

-
Você está recebendo isto porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-320948013 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AApc0iEK3vInreO03Bt6L3EAByBHQCv9ks5sWFt3gaJpZM4JYi9D
.

@nagisa Obrigado! Parece que resolveria o problema a nível técnico.

Seria possível extrair um subconjunto disso para que ! pudesse ser usado como um parâmetro de tipo em tipos de retorno? Agora, se você tiver uma função estável

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

E quiser usar ? , você não pode fazer a transformação usual para

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

A coerção espinhosa e as perguntas padrão do parâmetro de tipo afetam esse caso?

40801 Pode ser selecionado.

Devemos tentar consertar # 43061 antes de estabilizar ! .

Não vi nenhum problema de I-insano aberto mencionando never_type, então preenchi um novo para este: # 47563. As coisas parecem assumir que [!; 0] é desabitado, mas posso criar um com um simples [] .

@dtolnay adicionado ao cabeçalho do problema

@canndrew, como você está com o tempo? Eu gostaria de ver um subconjunto do uso de ! estabilizado (excluindo as regras sobre o que é exaustivo). Eu meio que perdi o fio de onde estamos, no entanto, e acho que precisa de um campeão. Você tem tempo para ser essa pessoa?

@nikomatsakis Tenho certeza que posso encontrar algum tempo para trabalhar nisso. O que exatamente precisa ser feito? São apenas os bugs vinculados a esse problema?

Parece que @varkor já tem PRs abertos para corrigir os problemas restantes (incrível!). Pelo que eu posso ver, as únicas coisas que faltam fazer são decidir se estamos satisfeitos com as características que estão implementadas atualmente e mover / renomear o portão de recursos para que ele cubra apenas as mudanças exaustivas de correspondência de padrões.

Embora outra coisa: queremos fazer mem::uninitialized::<!>() pânico em tempo de execução (atualmente causa UB)? Ou devemos deixar esse tipo de mudança por enquanto? Não estou atualizado com o que está acontecendo com as diretrizes de código inseguro.

Acho que o plano ainda é https://github.com/rust-lang/rfcs/pull/1892 , que acabei de notar que você escreveu. :)

@RalfJung Existe algo em particular bloqueando isso? Eu poderia escrever um PR hoje que acrescente MaybeUninit e desative uninitialized .

@canndrew Uma vez que muitos projetos desejam oferecer suporte a todos os três canais de lançamento e uninitialized está disponível no stable, é preferível apenas começar a emitir avisos de depreciação no Nightly quando a substituição estiver disponível no canal stable. A depreciação suave por meio de comentários de documentos é adequada.

Eu criei um PR para estabilizar ! : https://github.com/rust-lang/rust/pull/47630. Não sei se estamos prontos para mesclá-lo ainda. Devemos pelo menos esperar para ver se os PRs de @varkor corrigem os problemas em aberto restantes.

Também acho que devemos revisitar https://github.com/rust-lang/rfcs/pull/1699 para que as pessoas possam começar a escrever impls elegantes de traços por ! .

@canndrew : Embora esse RFC não tenha sido aceito, ele se parece muito com a proposta em https://github.com/rust-lang/rust/issues/20021.

https://github.com/rust-lang/rust/issues/36479 provavelmente também deve ser adicionado ao cabeçalho do problema.

@varkor é muito parecido. Com o seu trabalho de código inacessível, você notou algum problema com um código como este agora sendo sinalizado como inacessível ?:

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

Porque para isso seria necessário aceitar algum tipo de proposta como essa.

@canndrew algo que venho defendendo ultimamente - embora ainda tenhamos que desenvolver um modelo formal - é uma espécie de "questão resumida" que tenta delinear claramente o que estamos estabilizando. Você pode pensar nisso como um relatório após o RFC, tentando resumir em uma espécie de lista com marcadores as características salientes do que está sendo estabilizado, mas também do que não está .

Parte disso seriam ponteiros para casos de teste que demonstram o comportamento em questão.

Você acha que poderia tentar fazer uma coisa dessas? Podemos conversar um pouco juntos se você quiser sobre uma espécie de "esboço" - ou posso tentar esboçá-lo.

Pergunta relacionada: devemos tentar resolver https://github.com/rust-lang/rust/issues/46325 antes de estabilizar? Talvez isso não importe.

@nikomatsakis Eu voto por não esperar que nenhum problema de aviso seja resolvido. Isso é inofensivo. Se nenhuma outra preocupação real aparecer, acho que devemos prosseguir e estabilizá-la.

@canndrew : Não acho que tenha realmente olhado para nenhum caso como esse, embora eu definitivamente ache que ser capaz de omitir implementações impossíveis será muito necessário quando ! estiver estabilizado.

@nikomatsakis

Você acha que poderia tentar fazer uma coisa dessas? Podemos conversar um pouco juntos se você quiser sobre uma espécie de "esboço" - ou posso tentar esboçá-lo.

Eu poderia escrever pelo menos um rascunho e você poderia me dizer se é o que tem em mente. Vou tentar fazer isso nos próximos dias.

@nikomatsakis Algo assim?

Problema resumido - estabilização de !

O que está sendo estabilizado

  • ! agora é um tipo completo e pode ser usado em qualquer posição de tipo (por exemplo, RFC 1216 ). O tipo ! pode forçar qualquer outro tipo, consulte https://github.com/rust-lang/rust/tree/master/src/test/run-fail/adjust_never.rs para um exemplo.
  • A inferência de tipo agora padronizará as variáveis ​​de tipo irrestrito para ! vez de () . O fiapo resolve_trait_on_defaulted_unit foi retirado. Um exemplo de onde isso ocorre é se você tiver algo como:

    // 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);
    

    Nas regras antigas, isso desserializaria () , enquanto nas novas regras isso desserializaria ! .

  • O portão de recurso never_type é estável, embora alguns dos comportamentos que ele costumava bloquear agora estejam atrás do novo portão de recurso exhaustive_patterns (veja abaixo).

O que não está sendo estabilizado

devemos tentar resolver o # 46325 antes de nos estabilizarmos?

Por mais bom que fosse limpar cada ponta solta como essa, não parece realmente um bloqueador.

@canndrew

Algo assim?

Sim, obrigado! Isso é ótimo.

A principal coisa que está faltando são ponteiros para casos de teste mostrando como ! se comporta. O público deve ser a equipe de lang ou outras pessoas que os acompanham de perto, eu acho, não se trata realmente de pessoas de "propósito geral". Então, por exemplo, eu gostaria de alguns exemplos de lugares onde as coerções são legais, ou onde ! pode ser usado. Também gostaria de ver os testes que nos mostram que a correspondência de padrões exaustiva (sem a porta de recursos habilitada) ainda se comporta. Devem ser ponteiros para o repositório.

@canndrew

Por mais bom que fosse limpar cada ponta solta como essa, não parece realmente um bloqueador.

Bem, isso significa que vamos habilitar um novo código que vai mudar imediatamente o comportamento (por exemplo, let x: ! = ... acho que se comportará de maneira diferente para algumas expressões). Parece melhor resolvermos, se pudermos. Talvez você possa cometer um erro grave no PR que abriu e podemos combiná-lo com a cratera existente no PR?

@nikomatsakis Eu atualizei esse problema de resumo novamente com alguns links e exemplos. Além disso, lamento ter demorado um pouco para chegar a isso, estive muito ocupado na semana passada.

Talvez você possa cometer um erro grave no PR que abriu e podemos combiná-lo com a cratera existente no PR?

Feito.

@rfcbot fcp merge

Proponho que estabilizemos o tipo ! - ou pelo menos parte dele, conforme descrito aqui . O PR está aqui .

Um dado que eu gostaria é uma corrida de cratera. Estou no processo de rebaseamento https://github.com/rust-lang/rust/pull/47630 (já que @canndrew não está respondendo a pings agora) para que possamos obter esses dados.

O membro da equipe @nikomatsakis propôs fundir isso. A próxima etapa é revisada pelo restante das equipes marcadas:

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

Nenhuma preocupação listada atualmente.

Assim que a maioria dos revisores aprovar (e nenhum objeção), isso entrará em seu período final para comentários. Se você identificar uma questão importante que não foi levantada em algum momento deste processo, fale!

Consulte este documento para obter informações sobre quais comandos os membros da equipe marcados podem me dar.

Oh, algumas coisas que acabei de lembrar:

  • Devemos considerar a ideia de estabilizar isso apenas na nova época . Em particular, a mudança para regras de fallback são incompatíveis com versões anteriores - a execução da cratera nos dará algum tipo de limite inferior na precipitação, mas é apenas um limite inferior. Mas talvez possamos apenas manter as velhas regras de fallback, a menos que você esteja na nova época.
  • Em segundo lugar, acredito que parte do plano aqui também era ter algumas diretrizes sobre quando é apropriado implementar uma característica para ! . O TL; DR é que está tudo bem se os métodos na característica forem inutilizáveis ​​sem primeiro fornecer um ! - então implementar Clone para ! está ok, eu acho , mas a implementação de Default não é. Dito de outra forma, se a implementação do impl exigir que você panic! indiscriminadamente, isso é um mau sinal. =)

@nikomatsakis Podemos mudar a regra de fallback em uma nova época, mas ainda assim tornar ! um tipo disponível na época de 2015?

@nikomatsakis

Devemos considerar a ideia de estabilizar isso apenas na nova época .

A última vez que fizemos uma corrida na cratera (que foi há muito tempo), as consequências da mudança das regras de fallback foram mínimas. Também temos evitado código que pode ser afetado pela mudança por um tempo.

Em segundo lugar, acredito que parte do plano aqui também era ter algumas diretrizes sobre quando é apropriado implementar uma característica para ! .

Isso é mencionado na documentação por !

@SimonSapin

Poderíamos mudar a regra de fallback em uma nova época, mas ainda assim criar! como um tipo disponível na época de 2015?

sim

@canndrew

A última vez que fizemos uma corrida na cratera (que foi há muito tempo), as consequências da mudança das regras de fallback foram mínimas. Também temos evitado código que pode ser afetado pela mudança por um tempo.

Sim. Vamos ver o que diz a cratera. Mas, como eu disse, a cratera sempre nos dá um limite inferior - e isso é uma espécie de "mudança eletiva". Ainda assim, suspeito que você esteja certo e podemos "escapar" mudando isso sem muito efeito no código em estado selvagem.

O TL; DR é que está tudo bem se os métodos na característica forem inutilizáveis ​​sem primeiro fornecer um !

Essa é apenas a regra normal - você adiciona um impl quando pode implementá-lo de maneira sã, onde "implementá-lo de maneira sã" exclui o pânico, mas inclui "ex falso" UB na presença de dados inválidos.

@ arielb1 Sim, mas por alguma razão as pessoas tendem a se confundir com coisas como essa na presença de ! , então parece que vale a pena chamá-lo explicitamente.

Talvez ajudasse a ter um método seguro fn absurd(x: !) -> ! que seja documentado como uma forma segura de expressar código inacessível ou algo assim em libstd? Acho que havia um RFC para isso ... ou pelo menos um problema de RFC: https://github.com/rust-lang/rfcs/issues/1385

@RalfJung Essa função absurd é apenas identity ? Por que isso é útil?

Não é o mesmo que unsafe fn unreachable() -> ! intrínseco, que não aceita nenhum argumento e tem um comportamento muito indefinido.

Bem, sim, principalmente é. Eu estava usando o tipo de retorno ! no sentido de "não termina". (O tipo usual de absurd é fn absurd<T>(x: !) -> T , mas também não parece útil no Rust.)

Eu estava pensando que talvez isso ajudasse com "as pessoas tendem a ficar confusas sobre coisas como essa na presença de ! " - ter alguma documentação em algum lugar de que o raciocínio "ex falso" é uma coisa.

Parece que também entendi errado ... Achei que em algum lugar havia uma discussão sobre essa função "ex falso". Parece que estou errado.

fn absurd<T>(x: !) -> T também pode ser escrito match x {} , mas isso é difícil de inventar se você não o viu antes. Pelo menos vale a pena apontar no rustdoc e no livro.

fn absurdo(x:!) -> T também pode ser escrito match x {}, mas isso é difícil de inventar se você não viu antes.

Certo, é por isso que sugeri um método em algum lugar da libstd. Não tenho certeza de onde é o melhor lugar.

O bom de absurd é que você pode passá-lo para funções de ordem superior. Não tenho certeza se isso é necessário dado como! se comporta wrt. unificação, no entanto.

fn absurd<T>(x: !) -> T também pode ser escrito match x {}

Também pode ser escrito apenas como x (AFAIK) - você só precisa de match se tiver um enum vazio.

Uma função absurd(x: !) -> T seria útil para passar para funções de ordem superior. Eu precisei disso (por exemplo) ao encadear futuros, um dos quais tem tipo de erro ! e o outro tipo de erro T . Mesmo que uma expressão do tipo ! possa coagir em T isso não significa que ! e T irão unificar, então às vezes você ainda precisa fazer isso manualmente converter o tipo.

Da mesma forma, acho que tipos semelhantes a Result devem ter um método .infallible() que converte Result<T, !> em Result<T, E> .

Tipos semelhantes a resultados devem ter um método .infallible () que converte Resultresultar.

Eu esperava que tal método tivesse o tipo Result<T, !> -> T .

Eu esperava que tal método tivesse o tipo Result-> T.

Não é exatamente o mesmo que unwrap ? O pânico será otimizado, pois o caso Err está inacessível.

@Amanieu A questão é que não há pânico para começar. Eu adoraria ter um fiapo de pânico, e isso tropeçaria se dependêssemos da otimização. Mesmo sem o lint, usar unwrap adiciona um revés durante os refatores, a menos que o pânico se torne código ativo novamente.

unwrap lê como assert - "verificação em tempo de execução antecipada" (IIRC houve até propostas para renomeá-lo, mas chegaram tarde demais ... infelizmente). Se você não quiser e precisar de uma verificação de tempo de execução, infallible tornaria isso explícito.

: bell: Agora está entrando em seu período de comentários final , conforme a revisão acima . :Sino:

@rfcbot fcp cancel

@aturon e @pnkfelix não

Proposta @cramertj cancelada.

O membro da equipe @cramertj propôs fundir isso. A próxima etapa é revisada pelo restante das equipes marcadas:

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

Nenhuma preocupação listada atualmente.

Assim que a maioria dos revisores aprovar (e nenhum objeção), isso entrará em seu período final para comentários. Se você identificar uma questão importante que não foi levantada em algum momento deste processo, fale!

Consulte este documento para obter informações sobre quais comandos os membros da equipe marcados podem me dar.

: bell: Agora está entrando em seu período de comentários final , conforme a revisão acima . :Sino:

Discutimos isso na reunião de hoje. Continuo indeciso sobre se devo mudar o recuo agora ou apenas na nova época. Aqui estão os vários pontos.

O impacto esperado da mudança é pequeno. Então, realisticamente, isso não faz muita diferença de qualquer maneira. Você pode ver a análise completa aqui , mas o resumo é:

E isso deixa apenas as duas regressões reais: oplog-0.2.0 (na verdade, é sua dependência bson-0.3.2 que está quebrada) e rspec-1.0.0-beta.4. Ambos dependem do antigo comportamento de fallback, embora, felizmente, estejam quebrando em tempo de compilação, não em tempo de execução. Vou enviar PRs para consertar essas caixas.

Mas ainda é tecnicamente uma mudança significativa (e voluntária). Não há nenhuma razão particular para que tenhamos que alterar o fallback para variáveis ​​de tipo, exceto que () é uma escolha singularmente ruim e é muito raro que o código dependa dela. Também temos alertado sobre isso há muito tempo.

Acho que isso é mais uma questão de filosofia: no passado, fizemos pequenas mudanças importantes como essa por necessidade. Mas agora que temos o mecanismo de época, ele nos oferece uma maneira mais baseada em princípios de fazer essas transições sem quebrar nada tecnicamente. Pode valer a pena colocá-lo em uso, apenas por princípio. Por outro lado, isso significaria às vezes esperar anos para fazer uma mudança como essa. E, claro, temos que manter ambas as versões perpetuamente, tornando as especificações da linguagem, etc., muito mais complexas.

Estou um pouco indeciso, mas acho que no equilíbrio, tendo oscilado para frente e para trás, estou atualmente inclinado para "vamos apenas mudar isso universalmente", como planejado.

Eu sei que sou tendencioso, mas vejo a mudança no comportamento de fallback como mais uma correção de bug em comparação com coisas como dyn Trait e catch .

Além disso, se alguém quiser saltar sobre isso e dizer: "Ah ha! Rust quebra a compatibilidade com versões anteriores, afinal! Sua promessa de estabilidade quando atingiu 1.0 era uma mentira!" então, a discussão muito cuidadosa sobre esse assunto mostra que a decisão não foi tomada levianamente, mesmo tendo um impacto prático desprezível.

OK, eu digo vamos apenas mudar isso.

Como um compromisso, então, eu gostaria de dar uma nota útil para erros que informam as pessoas que o comportamento mudou - parece plausível. A ideia seria mais ou menos assim:

  • Se virmos um erro como !: Foo para alguma característica, e essa característica for implementada para (): Foo , e tivermos feito fallback (podemos dizer ao código de relatório de erro esse fato), então adicionamos um nó extra.

O período de comentários final agora está completo.

Essas caixas de seleção pendentes na postagem superior ainda não foram marcadas? Há algo desfeito nessa lista?

@earthengine Não acho que os dois que vejo sejam especialmente relevantes - quanto ao item de cima, sobre o conjunto de implantes, acho que agora está decidido por enquanto.

Basicamente, para ser um conjunto mínimo.

@nikomatsakis Acabei de criar o problema de resumo aqui: https://github.com/rust-lang/rust/issues/48950

Houve alguma consideração em fazer de ! um padrão que corresponda aos valores do tipo ! ? (Eu fiz um grep rápido sobre este problema e o problema RFC original, mas não encontrei nada relevante).

Eu acharia isso útil para tipos como StreamFuture que criam tipos de erros compostos a partir de tipos de erros potencialmente ! . Usando um padrão ! em map_err , se o tipo de erro em algum ponto no futuro mudar, então a chamada map_err pararia de compilar em vez de potencialmente fazer algo diferente.

Por exemplo, dado

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

isso permitiria escrever o mais explícito

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

em vez do potencialmente sujeito a erros

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

ou o um pouco menos sujeito a erros

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

EDITAR: reler https://github.com/rust-lang/rfcs/pull/1872 os comentários mencionam a introdução do padrão ! . No entanto, isso é puramente no contexto de match expressões, não tenho certeza se generaliza para argumentos de encerramento / função.

@ Nemo157

Houve alguma consideração de fazer! um padrão que corresponde aos valores do! modelo?

Interessante. Acho que o plano era apenas deixar você deixar de lado essas armas, caso em que - se o tipo mudasse no futuro - você ainda obteria um erro, porque sua combinação agora não seria mais exaustiva. O problema com um padrão ! é que, se corresponder, significa que o braço é impossível, o que é um pouco estranho; gostaríamos de explicar isso, imagino, não exigindo que você dê uma expressão para executar. Ainda assim, pode ser bom ter uma maneira de dizer explicitamente que esse caso não pode acontecer.

Na verdade, estender o tratamento de padrões inalcançáveis ​​pode ser uma forma alternativa de resolver isso. Se o braço de fósforo fosse comprovadamente impossível, talvez o código dentro do braço pudesse ser ignorado. Então, quando você tiver

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

isso ainda se expandiria como hoje (_Não estou 100% certo de que esta é a expansão correta, mas parece perto o suficiente_)

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)),
    })
}

mas o ramo Result::Err não teria o tipo verificado e você não obteria o erro the trait bound `std::string::String: std::convert::From<(!, std::string::String)>` is not satisfied que obtém hoje.

Não acho que seja compatível com o que a RFC 1872 estava propondo, porque há um braço explícito que está realizando uma correspondência superficial, não está necessariamente contando com a validade do ! e poderia potencialmente execute o branch associado "com segurança", desde que esse branch não use e.0 todo.

! é um tipo positivo, como bool , então é meio impossível de corresponder a um padrão em uma lista de argumentos de encerramento sem estender a sintaxe de encerramento para permitir vários ramos. por exemplo. talvez pudéssemos permitir que fechamentos bool -> u32 fossem escritos algo como (sintaxe hipotética feia) [~ |false| 23, |true| 45 ~] -> u32 , então um fechamento de qualquer tipo vazio para u32 poderia simplesmente ser escrito [~ ~] -> u32 .

Wrt seus exemplos originais, você pode escrever foo.map_err(|_: (!, _)| unreachable!()) embora eu prefira foo.map_err(|(e, _)| e) pois evita usar unreachable! .

! é um tipo positivo

O que você quer dizer com tipo positivo?

é meio impossível corresponder a um padrão em uma lista de argumentos de encerramento sem estender a sintaxe de encerramento para permitir vários ramos

As listas de argumentos já suportam subpadrões irrefutáveis, por exemplo

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

Eu consideraria o padrão ! como um padrão irrefutável que corresponde a todos os valores 0 do tipo ! , o mesmo que () é um padrão irrefutável que corresponde a todos os 1 valores de o tipo () .

Eu apenas consideraria o! padrão como um padrão irrefutável que corresponde a todos os valores 0 do! tipo, o mesmo que () é um padrão irrefutável que corresponde a todos os 1 valores do tipo ().

Bem, mas 0! = 1.;) É meio inútil escrever qualquer código depois de combinar um ! - então, por exemplo, se o tipo de argumento é (!, i32) , então |(!, x)| code_goes_here é bobo, pois o código está morto de qualquer maneira. Eu provavelmente escreveria |(x, _)| match x {} para deixar bem explícito que x é um elemento de ! . Ou, usando a função absurd que propus acima , |(x, _)| absurd(x) . Ou talvez |(x, _)| x.absurd() ?

Pode-se imaginar uma sintaxe diferente que não permite escrever nenhum código, como o que @canndrew escreveu acima. match tem qualquer número de ramos, portanto, é especialmente possível não ter

Pena que não podemos adicionar impl<T> From<!> for T agora. (Ele se sobrepõe a From<T> for T , a menos que seja algo especialização?) Provavelmente não poderemos adicioná-lo mais tarde. (Nos ciclos de lançamento após ! ter sido estabilizado, alguns engradados podem implementar From<!> for SomeConcreteType .)

@SimonSapin bom ponto! Este é um bloco de construção muito útil para muitas coisas, e eu odiaria perdê-lo para sempre. Eu consideraria seriamente um hack único para permitir as duas instâncias. Eles coincidem semanticamente no caso sobreposto, então não há "incoerência operacional".

Eu consideraria seriamente um hack único

Então, esse hack precisa ser feito nos próximos dias. (Ou será feito backport durante a versão beta.)

@SimonSapin Com que rapidez / facilidade esse hack pode ser adicionado? Seria realmente uma pena nunca poder ter From<!> for T .

Como alternativa, poderíamos rapidamente lançar um aviso contra a implementação de From<!> for SomeConcreteType .

Eu não sei, depende de qual é o hack. Acho que a especialização de traço pode permitir dois impls sobrepostos se houver um terceiro para a interseção, mas isso requer que o traço seja publicamente “especializável”?

Infelizmente, especialização:

  • Exigiria que From::from fosse alterado para default fn , que seria “visível” fora do padrão. Não sei se queremos fazer isso em características padrão, desde que a especialização seja instável.
  • Conforme aceito na RFC 1210, especificamente não oferece suporte ao recurso de linguagem que eu estava pensando (eliminar a ambigüidade de dois impls sobrepostos onde nenhum é mais específico do que o outro, escrevendo um terceiro impl para a interseção).

Aqui, os dois impls fazem exatamente a mesma coisa. Se apenas hackearmos e desligarmos a verificação de coerência, será difícil determinar qual impl será usado, o que normalmente não é sólido, mas, neste caso, está bom. Pode haver algum pânico na compilação esperando, por exemplo, um resultado, mas podemos contornar isso.

Em outras palavras, acho que isso é muito mais fácil do que acelerar qualquer especialização, felizmente.

Existe um RFC existente para formalizar esta ideia "permitir a sobreposição arbitrária se todos os impls envolvidos forem inacessíveis"? Parece uma abordagem interessante a se considerar.

Não que eu saiba. Para ser pendente, o impl em si não é inalcançável, mas em vez disso, os métodos internos são todos impossíveis de chamar (incluindo nenhum tipo ou constante associado, pois esses são sempre "chamáveis"). Suponho que poderia escrever um rapidamente se for considerado que o hack está bloqueado em tal coisa.

Basta mencionar o problema nº 49593 que pode causar! para ficar desestabilizado novamente para melhor localizabilidade.

Eu me pergunto quais outros impls além de T: From<!> não podem ser adicionados depois que ! estiver estabilizado. Provavelmente deveríamos tentar controlar todos os impls razoáveis ​​antes de estabilizar, diferentemente dos impls para ! em geral, onde não existe tal pressa.

Postagem cruzada deste comentário :

Eu estava me perguntando - qual é o exemplo clássico de por que o fallback para () "teria" que ser alterado? Ou seja, se continuarmos a retroceder para (), mas ainda adicionarmos!, Certamente evitaremos esse tipo de problema - e voltarmos para! não é o ideal, visto que há casos em que o fallback ocorre para uma variável de tipo que acaba influenciando o código "ativo" (a intenção, claro, é outra, mas é difícil evitar vazamentos, como descobrimos).

Houve várias regressões como resultado dessa mudança e devo dizer que não estou totalmente confortável com isso:

(Nomeação para discussão da equipe lang em torno deste ponto.)

Durante a reunião, recuamos um pouco algumas das opções aqui - estou me sentindo relutante em fazer alterações, principalmente porque isso altera a semântica do código existente em alguns casos extremos, e não está claro se as novas regras são melhores . Mas, principalmente, acho que decidimos que seria ótimo tentar coletar os prós / contras de cada design novamente para que possamos ter uma discussão mais completa. Gostaria de ver uma lista de casos de uso e também de armadilhas. @cramertj mencionou seu desejo de fallback em casos variantes - por exemplo, Ok(33) tendo um tipo que cai para Result<i32, !> , em vez de errar como faria hoje; Acho que eles estavam dizendo que manter o fallback de return como () seria inconsistente com essa mudança (embora sejam ortogonais) e pode criar conflitos no futuro. Isso é justo.

O desafio com Err feedback (# 40801) é que também existem casos extremos, como:

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

onde o tipo de x é finalmente inferido como Result<T, !> .

Lembro-me de um plano separado que eu tive que tentar e ver se poderíamos determinar (e talvez avisar? Ou errar?) Se ! substituto alguma vez "afetou" o código ativo - isso se provou muito difícil, embora pareça como se pudéssemos lint muitos casos na prática (por exemplo, aquele, e provavelmente o caso de @SimonSapin ).

Também foi discutido se poderíamos remover totalmente esse fallback na nova edição. Suspeito que quebrará muitas macros, mas valeria a pena tentar?

Para sua informação, esse recurso causou uma regressão em 1,26: # 49932

(Definindo rótulos por https://github.com/rust-lang/rust/issues/35121#issuecomment-368669041.)

O item da lista de verificação sobre coerção em ! deveria estar apontando para https://github.com/rust-lang/rust/issues/50350 em vez de fazer referência a esse problema?

Se eu quero que isso seja estabilizado o mais rápido possível, qual é o melhor lugar para direcionar minha energia?

@remexre Acho que a melhor descrição do problema que leva à reversão da estabilização está em https://github.com/rust-lang/rust/issues/49593

Então, uma solução viável seria uma caixa especial como um subtipo de
Caixa? Há algum traço seguro para objetos que não pode ser tratado dessa maneira?

No domingo, 8 de julho de 2018, 8h12, Ralf Jung [email protected] escreveu:

@remexre https://github.com/remexre Acho que a melhor descrição do
o problema que leva à reversão da estabilização está em # 49593
https://github.com/rust-lang/rust/issues/49593

-
Você está recebendo isso porque foi mencionado.

Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-403286892 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AEAJtcnsEaFmHrrlHhuQeVOkR8Djzt50ks5uEgVLgaJpZM4JYi9D
.

>

Obrigado,
Nathan

Essa discussão realmente deveria ir para https://github.com/rust-lang/rust/issues/49593 , mas uma parte importante dela é que Box::<T>::new requer T: Sized , mas new called é inferido como tendo T = Error (um traço DST) com base no tipo de retorno.

Além da deselegância de um caso especial, existem problemas para o caso especial Box::new ser:

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

ou

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

Em primeiro lugar, devemos pensar sobre qual deve ser o tamanho de ! . Os matemáticos irão propor algo como Inf , mas na realidade será usize::MAX , então estamos garantindo que qualquer tentativa de alocar espaço para este tipo irá falhar ou pelo menos panic .

Se ! é Sized então, não há nada que nos impeça de compilar Box::new(x as !) , mas esta é basicamente outra maneira de panic! , já que nenhum modelo de memória pode realmente alocar usize::MAX bytes.

Não me parece óbvio que ! deva ter tamanho infinito / usize::MAX em vez de ser um ZST?

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

atualmente é 0.

O reasonale foi explicado no texto processado.

bool : dois valores válidos => log (2) / log (2) = 1 bit
() : 1 valores válidos => log (1) / log (2) = 0 bit
! : 0 valores válidos => log (0) / log (2) = bits Inf

Como alguém não versado na teoria pertinente, vejo aderir à fórmula log(x)/log(y) no sentido de perseguir a elegância de um modelo teórico em detrimento do uso prático. (AKA sendo muito inteligente para o seu próprio bem)

Intuitivamente, parece que ! também deve ter tamanho zero porque:

  • bool : precisa de espaço para distinguir entre dois valores válidos => log (2) / log (2) = 1 bit além do sistema de tipo
  • () : precisa de espaço para distinguir um valor válido => nenhum espaço necessário além do sistema de tipo
  • ! : precisa de espaço para distinguir nenhum valor válido => nenhum espaço necessário além do sistema de tipo

Bem, é -infinity, na verdade, o que faz sentido, como colocar o! como um campo em uma estrutura, essencialmente transforma a estrutura em! também (c + -inf = -inf). Portanto, como estamos lidando com usize, 0 é o valor mais próximo que temos de representá-lo. (Eu acho que a implementação real em rustc até usa -inf).

Intuitivamente, parece que ! também deve ter tamanho zero

! não precisa de um tamanho, porque nunca haverá nenhum valor criado com esse tipo. É uma questão irrelevante.

0 valores válidos => log (0) / log (2) = bits Inf

log (0) é indefinido; não é infinito.

Por outro lado, ter um tamanho de usize::MAX é uma maneira prática de forçar a desabitância. Aceita?

@CryZe @varkor

Isso também se encaixa com o conceito que eu estava tentando raciocinar sobre a validade, em que minha intuição quer ver ! como sendo "ZST em um nível totalmente diferente". (ou seja, ! está para o sistema de tipo assim como () está para a alocação de memória.)

@CryZe
Sua fórmula -Inf + c == -Inf faz sentido, mas quando substitui -Inf por 0usize ela não se sustenta mais.

Por outro lado, se a aritemática for "caped": quaisquer overflows calculam para usize::MAX então usize::MAX se ajusta perfeitamente à fórmula.

O modelo do cérebro: se você tem um objeto do tipo ! , para alocar uma estrutura que o contém, você precisa de uma estrutura ainda maior do que ! , mas o maior tamanho de estrutura que você pode imaginar é usize::MAX . Portanto, o espaço de que você precisa ainda é usize::MAX .

Por que não apenas "prender" o tamanho de qualquer tipo que contenha um tipo vazio ( ! ou um enum Void {} definido pelo usuário) em size=0,alignment=0 ? Isso me parece muito mais semanticamente sensato ...

@remexre
Porque isso não funciona. Exemplo: sizeof(Result<usize,!>) == sizeof(usize) .

Bem, Result<usize, !> não contém ! diretamente; sizeof(Result::Ok(usize)) == 8 , sizeof(Result::Err(!)) == 0

Não, sizeof(Result::Err(!)) == usize::MAX em minha proposta, porque Result::Err(!) :: Result<!,!> .

A regra deve ser: em enum s, o tamanho de ! é considerado menor do que todos os outros valores de tamanho, mas em structs , é o tamanho máximo que uma imagem pode ser.

Conclusão:

  • ! não é DST. Os DSTs não são Sized não porque não tenham um tamanho, mas porque a informação do tamanho está oculta. Mas por ! sabemos tudo sobre ele.
  • ! deve ser dimensionado e, portanto, Box::new(x as !) deve ter permissão para pelo menos compilar.
  • struct s contém ! deve ser dimensionado como se fosse ! , enum s contém ! diretamente em uma variante deve ser dimensionado como se essa variante não existir.

Quer consideremos sizeof(!)==0 ou sizeof(!)==usize::MAX , temos que introduzir alguma aritemática especial para permitir o acima:

sizeof(!)==0 : em estruturas, 0 + n = 0 : preocupado :, em enums, max(0,n) = n : blush :
sizeof(!)==usize::MAX : em structs, usize::MAX + n =usize::MAX : neutral_face :, em enums, max(usize::MAX,n) = n : flushed :

Porém, há um benefício em usize::MAX : ele impede tecnocialmente qualquer tentativa, mesmo unsafe , de construir tal objeto, uma vez que não é possível em nenhum sistema real.

Há uma vantagem em usar o usize :: MAX: ele impede tecnocialmente qualquer tentativa, mesmo que não segura, de construir tal objeto, uma vez que não é possível em nenhum sistema real.

Quero dizer, se unsafe travessuras são permitidas: *transmute::<Box<usize>, Box<!>>(Box::new(0)) me dá um ! independentemente do seu tamanho. usize::MAX torna mais provável que o compilador errará se alguém tentar fazer std::mem::zeroed<!>() , suponho, mas colocaria o ônus sobre a pessoa que está escrevendo esse código inseguro, em vez da matemática estranheza acima.

Observe que ! tendo tamanho _atualmente_ 0 - não MAX nem -INF -saturado-to-0 - é necessário para lidar com a inicialização parcial, como era encontrado em https://github.com/rust-lang/rust/issues/49298#issuecomment -380844923 (e implementado em https://github.com/rust-lang/rust/pull/50622).

Se você quiser mudar a forma como isso funciona, faça uma nova edição ou RP para isso; este não é o lugar.

@remexre

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

Box é (principalmente) um tipo de biblioteca e seu método new é definido na sintaxe Rust em src/liballoc/boxed.rs . Sua descrição pressupõe um operador <: que não existe no Rust. Isso ocorre porque a subtipagem em Rust só existe por meio de parâmetros de vida útil. Se você gostaria de ler ou assistir mais:

! pode ser coagido a qualquer tipo, mas esta é mais fraca do que ser qualquer tipo. (Por exemplo, Vec<&'static Foo> também é Vec<&'a Foo> para qualquer 'a , mas não há conversão implícita de Vec<!> para Vec<Foo> : http: / /play.rust-lang.org/?gist=82d1c1e1fc707d804a57c483a3e0198f&version=nightly&mode=debug&edition=2015)

Acho que a ideia é que você pode fazer o que achar apropriado no modo unsafe , mas terá que enfrentar o UB se não fizer isso com sabedoria. Se o tamanho oficial de ! for usize::MAX , isso deve alertar o usuário o suficiente para que este tipo nunca seja instanciado.

Além disso, estava pensando sobre o alinhamento. Se os tipos desabitados têm o tamanho usize::MAX , é natural definir seu alinhamento também usize::MAX . Isso limita efetivamente um ponteiro válido para ser o ponteiro nulo. Mas, por definição, um ponteiro nulo é inválido, então nem temos um ponto válido para este tipo, o que é perfeito para este caso.

@ScottAbbey
Estou me concentrando no problema para garantir que Box::new(x as !) não tenha problemas, para que possamos continuar a estabilizá-lo. A forma proposta é fazer com que ! tenha um tamanho. Se ZST for o padrão, então não há problema. Eu não estou discutindo mais. Basta implementar de forma que sizeof(!)==0 seja o suficiente.

é natural definir seu alinhamento também usar :: MAX

usize::MAX não é uma potência de dois, então não é um alinhamento válido. Além disso, o LLVM não suporta alinhamentos de mais de 1 << 29 . E ! não deve ter grande alinhamento de qualquer maneira, porque let x: (!, i32); x.1 = 4; deve levar apenas 4 bytes de pilha, não gigabytes ou mais.

Por favor, crie um novo tópico para esta discussão, @earthengine , se você deseja continuar.

O problema que ocorre aqui é que Box::new(!) é um Box<$0> onde esperamos um Box<Error> . Com anotações de tipo suficientes, exigiríamos que Box<$0> fosse coagido para Box<Error> , o que mais tarde nos deixaria com $0 padronizado para ! .

No entanto, $0 é uma variável de tipo, então a coerção não é feita e então obtemos $0 = Error .

Uma opção hacky para consertar as coisas seria descobrir que temos uma restrição $0: Sized (seguindo subtipos?) E inserir uma coerção chave nela, de forma que Box ainda seja chamado no tipo.

Já fazemos algo assim para detectar Fn em fechamentos, então não será tão complicado. Ainda é feio.

É isso, se durante a coerção encontrarmos uma obrigação da forma $0: Unsize<Y> que Y definitivamente não tem o tamanho e $0 definitivamente tem o tamanho, não trate isso como uma ambigüidade, mas sim trate-o como um "ok" e prossiga com a coerção não dimensionada.

@ arielb1

Então, como isso é diferente da coerção de Box::new(()) a Box<Debug> ? std::error::Error é um traço como Debug , não um tipo como [u8] . std::io::Error por outro lado é um tipo, mas é Sized .

Nunca temos problemas com o seguinte:

use std::fmt::Debug;

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

fn main() {
}

@earthengine, a diferença entre esse problema e seu código é:

| Escrito | Box::new(!): Box<Debug> | Box::new(()): Box<Debug> |
| ------ | ------ | ------- |
| Realizado | Box::new(! as Debug): Debug | (Box::new(()) as Box<Debug>): Debug |

Essencialmente, porque ! pode coagir a qualquer coisa, ele é coagido a Debug antes da chamada a Box::new . Essa não é uma opção com () , então não há problema no segundo caso - a coerção aí acontece depois de Box::new .

@RalfJung

Meu modelo cerebral é que os DSTs não podem existir na natureza - eles têm que vir de alguns tipos mais concretos. Isso é verdade mesmo quando permitimos DSTs na posição do parâmetro - se não na posição de retorno. Dito isso, até que o valor real seja colocado atrás de um ponteiro, ele não deve ser capaz de se aproximar de um DST, mesmo que seja ! .

Por exemplo, o seguinte não deve ser compilado:

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

Simplesmente porque você não pode substituir ! por qualquer expressão Rust existente para fazê-la compilar.

Editar

Isso não é uma opção com ()

Alguém pode explicar por quê? Se () as dyn Debug não compilar, ! as dyn Debug não o fará e vice-versa. &() as &Debug compila, então faça &! as &Debug , sem problemas. Se () as dyn Debug algum dia compilar, o problema que temos hoje será repetido por () , portanto, os implementadores de RFC do DST terão que vencê-lo, e assim resolverá o mesmo problema que temos por ! .

! coage a qualquer coisa porque é impossível existir. "Ex falso quodlibet". Portanto, todos os seus exemplos devem ser compilados - não há nenhuma boa razão teórica para fazer uma exceção especial para tipos não dimensionados.

Isso nem mesmo viola "DTS não pode existir" porque ! também não pode existir. :)

() as dyn Debug não compila

Não sei quais são os planos para rvalues ​​não dimensionados, mas acho que eles poderiam fazer essa compilação.
O ponto é, fazer isso para ! não exige que implementemos rvalues ​​não dimensionados porque isso só pode acontecer em código morto.

No entanto, talvez você esteja apontando para uma possível solução aqui (e talvez seja isso que @ arielb1 também sugeriu acima): É possível restringir a coerção ! para aplicar apenas se o tipo de destino for (sabidamente) dimensionado ? Não há nenhuma boa razão teórica para fazer isso, mas uma razão prática. : D Ou seja, que pode ajudar a resolver este problema.

Quando eu disse "DTS não pode existir", quero dizer em sintático. Não é possível ter uma variável local digitada str , por exemplo.

Por outro lado, ! não pode existir é semântico. Você pode escrever

let v = exit(0);

ter v digitado sintaticamente ! no contexto, mas é claro que a ligação nem será executada e por isso não sairá do mundo real.

Portanto, aqui está o raciocínio: permitimos ! coerce para qualquer tipo, apenas quando você puder escrever uma expressão que tenha o mesmo tipo. Se um tipo nem mesmo pode existir sintaticamente, ele não deve ser permitido.

Isso também se aplica a rvalues ​​não dimensionados, portanto, antes de termos rvalues ​​não dimensionados, não é permitido forçar ! a tipos não dimensionados, até que saibamos como lidar com rvalues ​​não dimensionados. E só nesse ponto podemos começar a pensar sobre este problema (uma solução óbvia parece ser permitir que Box::new receba valores não dimensionados).

Não é possível ter uma variável local digitada str, por exemplo.

Essa é apenas uma limitação da implementação atual: https://github.com/rust-lang/rust/issues/48055

@RalfJung

Esse não é o problema aqui.

Novamente, suponha que estejamos na situação em que Box::new(!: $0): Box<dyn fmt::Debug> onde $0 é uma variável de tipo.

Então o compilador pode deduzir facilmente que $0: Sized (porque $0 é igual ao parâmetro de tipo de Box::new , que tem um limite T: Sized ).

O problema surge quando o compilador precisa descobrir que tipo de coerção usar para transformar Box<$0> em Box<dyn fmt::Debug> . De um POV "local", existem 2 soluções:

  1. Tenha uma coerção CoerceUnsized , exigindo Box<$0>: CoerceUnsized<Box<dyn fmt::Debug>> . Este é um programa válido para $0 = ! e o padrão fará com que seja compilado para isso.
  2. Tenha uma coerção de identidade, com $0 = dyn fmt::Debug . Isso é inconsistente com o requisito de $0: Sized .

O compilador não quer ter muitas coisas abertas ao considerar ambigüidades, pois isso pode causar problemas de desempenho e problemas difíceis de depurar, então ele escolhe qual coerção usar de uma maneira bastante estúpida (em particular, nós não não tem T: CoerceUnsized<T> , portanto, se o compilador escolher a opção 1, ele pode "travar" facilmente) e acaba escolhendo a opção 2, que falha. Tive uma ideia para torná-lo um pouco mais inteligente e escolher a opção 1.

Então, pensando nisso a partir deste ponto de vista, a ideia certa pode ser desencadear uma coerção não dimensionada se

  1. A coerção não dimensionada é autoconsistente (essas seriam as regras atuais, exceto que a ambigüidade está OK).
  2. não acionar a coerção

Tenha uma coerção de identidade, com $ 0 = dyn fmt :: Debug. Isso é inconsistente com o requisito $ 0: Sized.

Não consigo ver por que devemos permitir let v: str=! mas não let v: str; . Se você não pode nem mesmo declarar uma variável com um tipo específico, por que pode vincular um valor (talvez impossível) a ela?

Quando rvalue não dimensionado não é enviado, a maneira consistente é não permitir qualquer coerção para tipos não dimensionados, pois isso criará (talvez temporariamente) rvalues ​​não dimensionados, mesmo quando isso acontece apenas na imaginação.

Portanto, minha conclusão é que o problema Box::new(!) as Box<Error> é um problema de bloqueio para rvalues ​​não dimensionados, não para o tipo ! . As regras de coerção ! devem ser:

Você pode escrever let v: T , se e somente se você puder escrever let v: T = ! .

O significado real será então avaliado com a linguagem. Especialmente, depois de redefinir o tamanho dos rvalues, temos Box::new(! as dyn Debug) mas antes disso, eu diria que não.

Então, o que podemos fazer para seguir em frente?

Adicione uma porta de recurso no código que aciona a coerção não dimensionada; se rvalue não dimensionada estiver ativado, tente esta coerção; caso contrário, ignore-a. Se esta for a única coerção disponível, a mensagem de erro deve ser "o limite do traço str: std::marker::Sized não está satisfeito", como em casos semelhantes. Então

Temos que ver que isso pode ser calculado sem prejudicar o desempenho em funções maiores.

é abordado: apenas uma verificação de recurso simples.

Enquanto isso, adicione um novo problema para rvalues ​​não dimensionados e tenha o link adicionado ao problema de rastreamento, para garantir que esse problema seja tratado corretamente.

Interessante.

Esse

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

compila, mas

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

não. Isso nem mesmo está relacionado ao tipo ! . Devo criar um problema para isso?

Não sei se esse último é mesmo um bug. O segundo exemplo não compila porque, sem suporte a rvalue sem tamanho, o compilador deseja saber estaticamente quanto espaço de pilha alocar para a variável local v mas não pode porque é um DST.

No primeiro exemplo, o padrão _ é especial porque não apenas corresponde a qualquer coisa (como uma ligação de variável local), mas nem mesmo cria uma variável. Portanto, esse código é o mesmo que fn main() { *""; } sem let . Cancelar a referência de uma referência (mesmo DST) e, em seguida, não fazer nada com o resultado não é útil, mas parece ser válido e não estou convencido de que deveria ser inválido.

Direito. Mas é muito confuso, especialmente o seguinte

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

também não compila. Com sua teoria sobre _ isso deve ser o mesmo, exceto que chamamos a coisa não usada de _v vez de apenas _ .

Pelo que me lembro, a única diferença entre _v e v é que o sublinhado inicial suprime avisos sobre valores não usados.

Por outro lado, _ significa especificamente "descartá-lo" e é tratado especialmente para permitir que apareça em mais de um lugar em um padrão (por exemplo, descompactação de tupla, lista de argumentos, etc.) sem causar um erro sobre uma colisão de nomes.

Por outro lado, _ significa especificamente "descartar" e é tratado especialmente para permitir que apareça em mais de um lugar em um padrão (por exemplo, descompactação de tupla, lista de argumentos, etc.) sem causar um erro sobre um nome colisão.

Correto. Além do que você disse, let _ = foo() faz com que drop seja chamado imediatamente, enquanto _v só é descartado quando sai do escopo (como v seria )

Na verdade, é isso que quero dizer com “ _ é especial”.

Portanto, agora parece que negar todos os rvalues ​​não dimensionados (a menos que o recurso "rvalues ​​não dimensionados" esteja ativado) seria uma alteração significativa, embora o efeito seja pequeno, eu acho.

Então, eu ainda sugiro ter um portão de recurso para proibir a coersão de ! para rvalues ​​não dimensionados. Isso negaria um código como let _:str = return; , mas não acho que alguém vá usá-lo no código.

Contanto que o tipo ! seja instável, podemos fazer alterações significativas onde ele pode ou não ser coagido.

A questão é, se estabilizá-lo sem coerção para DSTs a fim de corrigir https://github.com/rust-lang/rust/issues/49593 , vamos querer restaurar tal coerção mais tarde, quando adicionarmos rvalues ​​não dimensionados? conseguir fazer isso sem quebrar https://github.com/rust-lang/rust/issues/49593 novamente?

Minha proposta anterior inclui isso. # 49593 deve ser um problema de bloqueio para rvalues ​​não dimensionados.

Minha sugestão pessoal para a solução final é permitir que Box::new (também pode incluir alguns outros métodos importantes) aceite parâmetros não dimensionados e permita ambigüidades em situações semelhantes.

FWIW, _ não é não faz nada para o local que está sendo correspondido. A correspondência de padrões funciona em lugares , não em valores .
O mais próximo que chega de uma vinculação de variável é ref _x , mas mesmo assim você provavelmente precisará do NLL para evitar empréstimos conflitantes resultantes dele. Isso funciona btw:

// 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

Você não entendeu meu ponto. Minha suposição é que no Rust não há atualmente nenhum rvalues ​​sem tamanho que possa aparecer no código. No entanto, eles aparecem em let _:str = *"" . Embora tal valor possa viver por nenhum tempo, em nível sintático ele vive. Algo como um fantasma ...

Em vez disso, seus exemplos são tecnicamente totalmente legais e não são o ponto. str tem tamanho, mas &str tem tamanho.

No entanto, eles aparecem em let _: str = * "". Embora tal valor possa viver por nenhum tempo, em nível sintático ele vive. Algo como um fantasma ...

Existe um "fantasma" semelhante em uma expressão como &*foo . Primeiro você desreferencia foo e então pega o endereço do resultado sem movê-lo . Portanto, não é o mesmo que & { *foo } , por exemplo.

@earthengine @SimonSapin Nenhum dimensionado ("expressão de valor") existe lá. Apenas lvalues ​​("expressões de lugar") o fazem. *"foo" é um lugar , e a correspondência de padrões não requer um valor, apenas um lugar.

Os nomes "lvalue" e "rvalue" são relíquias e um tanto enganosos em C também, mas ainda piores em Rust, porque o RHS de let é um "lvalue" (expressão de lugar), apesar do nome.
(As expressões de atribuição são o único lugar onde os nomes e regras C meio que fazem sentido no Rust)

Em seus exemplos, let x = *"foo"; , o valor é necessário para vinculá-lo a x .
Da mesma forma, &*"foo" cria uma expressão de lugar e a toma emprestada, sem nunca precisar criar um valor não dimensionado, enquanto {*"foo"} é sempre uma expressão de valor e, portanto, não permite tipos não dimensionados.

Em https://github.com/rust-lang/rust/pull/52420 eu acertei

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

não funciona mais. Isso é intencional?

AFAIK sim - a história do padrão infalível é complicada e tem uma porta de recursos separada de ! si. Consulte também https://github.com/rust-lang/rfcs/pull/1872 e https://github.com/rust-lang/rust/issues/48950.

@ Ericson2314 especificamente, o recurso que você precisa adicionar a liballoc é exhaustive_patterns .

Obrigado!!

Uma conversa interessante sobre internos:

Se você vai fazer isso, por que não tratar! em si como uma variável de inferência?

Basta fazer um invólucro em torno de ! com PhandomData para eliminar a ambiguidade do tipo de item.

https://github.com/rust-lang/rust/issues/49593 foi corrigido. Esse foi o motivo da reversão anterior da estabilização. O relatório de estabilização anterior está aqui . Vamos tentar de novo!

@rfcbot fcp merge

Acho que o rfcbot pode oferecer suporte a mais de um FCP no mesmo problema. Vamos abrir uma nova edição para esta rodada de estabilização?

https://github.com/rust-lang/rust/pull/50121 não apenas reverteu a estabilização, mas também a semântica de fallback. Isso é algo que queremos revisitar?

A caixa de seleção desmarcada restante na descrição do problema é:

Quais características devemos implementar para ! ? O PR # 35162 inicial inclui Ord e alguns outros. Este é provavelmente mais um problema de T-libs, então estou adicionando essa tag ao problema.

Podemos adicionar impls mais tarde, não podemos? Ou isso é um bloqueador?

@SimonSapin inaugurado https://github.com/rust-lang/rust/issues/57012. Eu esperaria que o fallback para ! fosse habilitado novamente como parte dessa mudança, sim (embora vamos discutir isso na questão da estabilização).

Aparentemente, há um erro / falha em ter o tipo nunca atrás de um portão de recursos e é possível consultá-lo no Estável: https://github.com/rust-lang/rust/issues/33417#issuecomment -467053097

Editar: arquivado https://github.com/rust-lang/rust/issues/58733.

Adicionando um uso do tipo no exemplo de código vinculado acima:

trait MyTrait {
    type Output;
}

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

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

fn main() {
    let _a: Void;
}

Isso compila no Rust 1.12.0 que eu acho que é o primeiro com https://github.com/rust-lang/rust/pull/35162. No 1.11.0, ocorre erros com:

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

Qual é o estado deste?

Pelo que eu sei, o status não mudou significativamente desde meu resumo em https://github.com/rust-lang/rust/issues/57012#issuecomment -467889706.

Qual é o estado deste?

@hosunrise : O está atualmente bloqueado em https://github.com/rust-lang/rust/issues/67225

Posso estar meio errado aqui, mas os problemas específicos que a discussão do lint (https://github.com/rust-lang/rust/issues/66173) menciona parecem resolvidos se cada enum tiver uma ramificação ! em o sistema de tipo?

Observarei que não se aplica ao problema mencionado no OP de https://github.com/rust-lang/rust/issues/67225 , que ainda seria um problema.

Posso estar meio errado aqui, mas os problemas específicos que a discussão do lint (# 66173) menciona parecem resolvidos se cada enum tiver uma ramificação ! no sistema de tipos?

Na verdade, cada enum pode ser ameaçado, pois havia um número infinito de variantes que nunca podem ocorrer e, portanto, nem todas merecem ser mencionadas.

Esta página foi útil?
0 / 5 - 0 avaliações