Rust: Problema de seguimiento para promover `!` A un tipo (RFC 1216)

Creado en 29 jul. 2016  ·  259Comentarios  ·  Fuente: rust-lang/rust

Problema de seguimiento para rust-lang / rfcs # 1216, que promueve ! a un tipo.

Asuntos pendientes por resolver

Eventos y enlaces interesantes

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

Comentario más útil

@petrochenkov Olvídese de ! y mire las enumeraciones.

Si tengo una enumeración con dos variantes, puedo combinarla con dos casos:

enum Foo {
    Flim,
    Flam,
}

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

Esto funciona para cualquier n, no solo para dos. Entonces, si tengo una enumeración con cero variantes, puedo coincidir con cero casos.

enum Void {
}

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

Hasta aquí todo bien. Pero mire lo que sucede, tratamos de hacer coincidir patrones anidados. Aquí está la coincidencia en una enumeración de dos variantes dentro de un Result .

enum Foo {
    Flim,
    Flam,
}

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

Podemos expandir el patrón interno dentro del externo. Hay dos variantes Foo , por lo que hay dos casos para Err . No necesitamos declaraciones de concordancia separadas para hacer coincidir en el Result y en el Foo . Esto funciona para enumeraciones con cualquier número de variantes ... _excepto cero_.

enum Void {
}

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

¿Por qué no debería funcionar esto? No llamaría arreglar esto agregando "soporte especial" para tipos deshabitados, lo llamaría arreglar una inconsistencia.

Todos 259 comentarios

¡Hurra!

Hay una implementación de WIP de esto aquí: https://github.com/canndrew/rust/tree/bang_type_coerced

Su estado actual es: se compila con old-trans y se puede usar, pero tiene un par de pruebas fallidas. Algunas pruebas fallan debido a un error que hace que un código como if (return) {} bloquee durante la transferencia. Las otras pruebas tienen que ver con la optimización del tiempo de enlace y siempre han tenido errores para mí, así que no sé si tienen algo que ver con mis cambios.

Mi hoja de ruta actual es:

  • Hágalo funcionar con MIR. Con suerte, esto no será demasiado difícil, ya que así es como comencé a implementarlo, pero he tenido un problema en el que MIR construye segfault durante la compilación.
  • Purgue el material de divergencia obsoleto del compilador ( FnOutput , FnDiverging y relacionados).
  • Oculte el nuevo tipo detrás de una puerta de función. Esto significaría, cuando la función está desactivada:

    • ! solo se puede analizar como un tipo en la posición de retorno.

    • Las variables de tipo divergente están predeterminadas en () .

  • Descubra cómo podemos generar advertencias de compatibilidad cuando un () predeterminado se usa para resolver un rasgo. Una forma de hacer esto podría ser agregar un nuevo tipo al AST llamado DefaultedUnit . Este tipo se comporta como () y se convertirá en () en algunas circunstancias, pero genera una advertencia cuando resuelve un rasgo (como () ). El problema con este enfoque es que creo que será difícil detectar y corregir todos los errores con la implementación; terminaríamos rompiendo el código de las personas para evitar que su código se rompa.

¿Hay algo que deba agregarse a esta lista? ¿Solo voy a ser yo trabajando en esto? ¿Y debería trasladarse esta rama al repositorio principal?

Descubra cómo podemos generar advertencias de compatibilidad cuando un () predeterminado se usa para resolver un rasgo. Una forma de hacer esto podría ser agregar un nuevo tipo al AST llamado DefaultedUnit . Este tipo se comporta como () y se convertirá en () en algunas circunstancias, pero genera una advertencia cuando resuelve un rasgo (como () ). El problema con este enfoque es que creo que será difícil detectar y corregir todos los errores con la implementación; terminaríamos rompiendo el código de las personas para evitar que su código se rompa.

@eddyb , @ arielb1 , @anyone_else : ¿Pensamientos sobre este enfoque? Estoy bastante a la altura de esta etapa (sin un par de pruebas fallidas que estoy tratando de corregir (muy lentamente)).

¿Para qué rasgos deberíamos implementar? El PR # 35162 inicial incluye a Ord y algunos otros.

¿No debería ! implementar automáticamente _todos_ los_ rasgos?

Este tipo de código es bastante común:

trait Baz { ... }

trait Foo {
    type Bar: Baz;

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

Esperaría que ! pueda usar para Foo::Bar para indicar que un Bar nunca puede existir:

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

Pero esto solo es posible si ! implementa todos los rasgos.

@tomaka Hay RFC al respecto: https://github.com/rust-lang/rfcs/pull/1637

El problema es que si ! implementa Trait también debería implementar !Trait ...

El problema es que si! implementa Trait ¡también debería implementar! Trait ...

Entonces, ¿caso especial ! para que ignore cualquier requisito de rasgo?

@tomaka ! no puede implementar automáticamente _todos_ rasgos porque los rasgos pueden tener métodos estáticos y tipos / constas asociados. Sin embargo, puede implementar automáticamente rasgos que solo tienen métodos no estáticos (es decir, métodos que toman Self ).

En cuanto a !Trait , alguien sugirió que ! podría implementar automáticamente tanto Trait _y_ !Trait . No estoy seguro de si eso es bueno, pero sospecho que los rasgos negativos no lo son en absoluto.

Pero sí, sería bueno si ! pudiera implementar automáticamente Baz en el ejemplo que dio precisamente para este tipo de casos.

¿Cuándo exactamente establecemos por defecto las variables de tipo divergente en () / ! y cuándo arrojamos un error sobre no poder inferir suficiente información de tipo? ¿Está esto especificado en alguna parte? Me gustaría poder compilar el siguiente código:

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

Pero el primer error que obtengo es " unable to infer enough type information about _ ". En este caso, creo que tendría sentido que _ por defecto sea ! . Sin embargo, cuando estaba escribiendo pruebas sobre el comportamiento predeterminado, me resultó sorprendentemente difícil hacer que una variable de tipo sea predeterminada. Por eso estas pruebas son tan complicadas.

Me gustaría tener una idea clara de exactamente por qué tenemos este comportamiento predeterminado y cuándo se supone que debe invocarse.

Pero el primer error que obtengo es "no puedo inferir suficiente información de tipo sobre _". En este caso, creo que tendría sentido que _ se estableciera de forma predeterminada en!. Sin embargo, cuando estaba escribiendo pruebas sobre el comportamiento predeterminado, me resultó sorprendentemente difícil hacer que una variable de tipo sea predeterminada. Por eso estas pruebas son tan complicadas.

Esa es una muy buena idea en mi opinión. Lo mismo para None que por defecto sería Option<!> por ejemplo.

@carllerche

La prueba unit_fallback es ciertamente una forma extraña de demostrarlo. Una versión menos macro-ey es

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

Solo la variable de tipo creada por return / break / panic!() predeterminado.

¿Cuándo exactamente establecemos las variables de tipo divergente por defecto en () /! y ¿cuándo arrojamos un error sobre no poder inferir suficiente información de tipo? ¿Está esto especificado en alguna parte?

Defina "especificado". :) La respuesta es que ciertas operaciones, que no están escritas afaik en ningún lugar fuera del código, requieren que se conozca el tipo en ese punto. El caso más común es el acceso al campo ( .f ) y el envío de métodos ( .f() ), pero otro ejemplo es deref ( *x ), y probablemente haya uno o dos más. En su mayoría, existen razones decentes para que esto sea necesario; en términos generales, existen múltiples formas divergentes de proceder y no podemos avanzar sin saber cuál tomar. (Sería posible, potencialmente, refactorizar el código para que esta necesidad pueda registrarse como una especie de "obligación pendiente", pero es complicado hacerlo).

Si llega hasta el final de fn, ejecutamos todas las operaciones de selección de rasgos pendientes hasta que se alcanza un estado estable. Este es el punto donde se aplican los valores predeterminados (por ejemplo, i32, etc.). Esta última parte se describe en el RFC hablando de los parámetros de tipo predeterminados especificados por el usuario (aunque ese RFC en general necesita trabajo).

@canndrew, esos se parecen un poco a https://github.com/rust-lang/rust/issues/12609

¡Vaya, eso es un error viejo! Pero sí, diría que mi # 36038 es un engaño de eso (pensé que lo había visto en alguna parte antes). No creo que ! realmente se pueda considerar para el horario de máxima audiencia hasta que se solucione.

¿Está previsto que ! afecte la exhaustividad de la coincidencia de patrones? Ejemplo de comportamiento actual, posiblemente incorrecto:

#![feature(never_type)]

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

@tikue sí, es uno de los errores enumerados anteriormente.

@lfairy whoops , no lo vi porque no estaba en las casillas de verificación en la parte superior. ¡Gracias!

¿Hay planes para implementar From<!> for T y Add<T> for ! (con un tipo de salida de ! )? Sé que es una solicitud realmente extrañamente específica. Estoy tratando de usar ambos en este PR .

From<!> for T definitivamente. Add<T> for ! probablemente lo decida el equipo de bibliotecas, pero personalmente creo que ! debería implementar todos los rasgos para los que tiene una implementación lógica y canónica.

@canndrew ¡Gracias! Estoy acostumbrado al rasgo Nothing scala, que es un subtipo de cada tipo y, por lo tanto, se puede usar prácticamente en cualquier lugar donde pueda aparecer un valor. Sin embargo, definitivamente simpatizo con el deseo de comprender los efectos que impl All for ! o similar tendría en el sistema de tipos de rust, especialmente en lo que respecta a los límites de rasgos negativos y similares.

Según https://github.com/rust-lang/rfcs/issues/1723#issuecomment -241595070 From<!> for T tiene problemas de coherencia.

Ah, claro, sí. Necesitamos hacer algo al respecto.

Sería bueno si los rasgos implícitos pudieran declarar explícitamente que son reemplazados por otros rasgos implícitos. Algo como:

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

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

¿No está esto cubierto por la especialización? Editar: Creo que esta es la regla implícita de celosía.

¿Es una alternativa viable para evitar el apoyo especial para tipos deshabitados tanto como sea posible?

Todos estos match (res: Res<A, !>) { Ok(a) /* No Err */ } , métodos especiales para Result parecen muy artificiales, como características por el bien de las características, y no parecen valer la pena el esfuerzo y la complejidad.
Entiendo que ! es una característica favorita de

@petrochenkov # 12609 no es una característica especial para el tipo Never. Es solo una corrección de errores para detectar un código claramente inalcanzable.

@petrochenkov Olvídese de ! y mire las enumeraciones.

Si tengo una enumeración con dos variantes, puedo combinarla con dos casos:

enum Foo {
    Flim,
    Flam,
}

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

Esto funciona para cualquier n, no solo para dos. Entonces, si tengo una enumeración con cero variantes, puedo coincidir con cero casos.

enum Void {
}

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

Hasta aquí todo bien. Pero mire lo que sucede, tratamos de hacer coincidir patrones anidados. Aquí está la coincidencia en una enumeración de dos variantes dentro de un Result .

enum Foo {
    Flim,
    Flam,
}

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

Podemos expandir el patrón interno dentro del externo. Hay dos variantes Foo , por lo que hay dos casos para Err . No necesitamos declaraciones de concordancia separadas para hacer coincidir en el Result y en el Foo . Esto funciona para enumeraciones con cualquier número de variantes ... _excepto cero_.

enum Void {
}

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

¿Por qué no debería funcionar esto? No llamaría arreglar esto agregando "soporte especial" para tipos deshabitados, lo llamaría arreglar una inconsistencia.

@petrochenkov Tal vez

(0) ¿Debería permitirse la compilación de este código?

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

(1) ¿Debe permitirse la compilación de este código?

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

Tal como se implementa actualmente, las respuestas son "no" y "sí", respectivamente. # 12609 habla de (1) específicamente en el tema en sí, pero estaba pensando en (0) cuando respondí. En cuanto a cuáles deberían ser las respuestas, creo que (0) definitivamente debería ser "sí", pero para (1) tampoco estoy seguro.

@canndrew
Puede ser razonable hacer (1), es decir, patrones inalcanzables, una pelusa y no un error grave, independientemente de los tipos deshabitados, RFC 1445 contiene más ejemplos de por qué esto puede ser útil.

Respecto a (0) estoy más o menos convencido de tu explicación. Estaré totalmente feliz si este esquema se basa naturalmente en la implementación de verificación de patrones en el compilador y elimina más código especial del que agrega.

Por cierto, hice un PR para intentar arreglar (0) aquí: https://github.com/rust-lang/rust/pull/36476

Estaré totalmente feliz si este esquema se basa naturalmente en la implementación de verificación de patrones en el compilador y elimina más código especial del que agrega.

No es así, curiosamente. Pero eso es probablemente más un artefacto de la forma en que lo pirateé en el código existente en lugar de que no haya una forma elegante de hacerlo.

Creo que para las macros es útil que (1) no sea un error grave.

Creo que (1) debería compilarse de forma predeterminada, pero dar una advertencia del mismo lint que aquí:

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

Ya que estamos en eso, ¿tiene sentido hacer que algunos patrones sean irrefutables frente a ! ?

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

Estoy de acuerdo en que hacer coincidencias no exhaustivas es un error, pero parece tener sentido los patrones inalcanzables, es decir, redundantes, solo una advertencia.

He tenido algunos RP sentados por un tiempo acumulando pudrición. ¿Hay algo que pueda hacer para ayudar a que se revisen? Probablemente sea necesario que haya alguna discusión sobre ellos. Estoy hablando de # 36476, # 36449 y # 36489.

Estoy en contra de la idea de pensar que el tipo divergente (o el tipo "inferior" en la teoría de tipos) es el mismo que el tipo enum vacío (o el tipo "cero" en la teoría de tipos). Son criaturas diferentes, aunque ambas no pueden tener ningún valor o instancia.

En mi opinión, un tipo de fondo solo puede aparecer en cualquier contexto que represente un tipo de retorno. Por ejemplo,

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

Pero no deberías decir

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

o

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

o incluso

type ID=fn(!)->!;

ya que ninguna variable debe tener el tipo ! , por lo que ninguna variable de entrada debe tener el tipo ! .

Empty enum es diferente en este caso, puede decir

enum Empty {}

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

luego

 match Empty::new() {}

Es decir, hay una diferencia fundamental entre ! y Empty : no puede declarar que ninguna variable tenga el tipo ! pero puede hacerlo por Empty .

@earthengine ¿Cuál es la ventaja de introducir esta distinción (en mi opinión, completamente artificial) entre dos tipos de tipos inhabitables?

Se han planteado numerosas razones para no tener tal distinción, por ejemplo, poder escribir Result<!, E> , hacer que esto interactúe bien con funciones divergentes, sin dejar de poder usar las operaciones monádicas en Result , como map y map_err .

En la programación funcional, el tipo vacío ( zero ) se usa con frecuencia para codificar el hecho de que una función no regresa o que no existe un valor. Cuando dices bottom type, no me queda claro a qué concepto de teoría de tipos te refieres; generalmente bottom es el nombre de un tipo que es un subtipo de todos los tipos, pero en ese sentido, ni ! ni una enumeración vacía son bottom en Rust. Pero de nuevo, zero no es un tipo bottom no es infrecuente en la teoría de tipos.

Es decir, hay una diferencia fundamental entre ! y Empty : no puede declarar que ninguna variable tenga el tipo ! pero puede hacerlo por Empty .

Eso es lo que está arreglando este RFC. ! no es realmente un "tipo" si no puede usarlo como un tipo.

@RalfJung
Esos conceptos son de lógica lineal https://en.wikipedia.org/wiki/Linear_logic

Como hay algunos errores en el texto anterior de esta publicación, los eliminé. Una vez que lo haga bien, actualizaré esto.

top es un valor que se puede usar de todas las formas posibles

¿Qué significa esto en la subtipificación? ¿Que puede convertirse en cualquier tipo? Porque eso es bottom entonces.

@eddyb Cometí algunos errores, espere mis nuevas actualizaciones.

Este PR , que se fusionó en un día, entra en conflicto bastante con mi PR de coincidencia de patrones, que ha estado en revisión durante más de un mes. Lo siento, mi segundo patrón de relaciones públicas de coincidencia desde el primero se volvió imposible de fusionar y tuvo que ser reescrito después de estar en revisión durante dos meses.

chicos ffs

@canndrew argh, lo siento. = (Yo también estaba planeando hacer r +'ing en su relaciones públicas hoy ...

Lo siento, no quiero quejarme, pero esto se ha estado prolongando. Dejé de intentar mantener varios RP a la vez y ahora he estado intentando conseguir este cambio desde septiembre. Creo que parte del problema es la diferencia de zona horaria: todos están en línea mientras yo estoy en la cama, por lo que es difícil conversar sobre estas cosas.

Dado que las excepciones son un agujero lógico y necesario en el sistema de tipos, me preguntaba si alguien ha pensado seriamente en avanzar hacia un manejo más formal con!.

Usando https://is.gd/4EC1Dk como ejemplo y punto de referencia, ¿qué pasa si pasamos eso y.

1) Trate cualquier función que pueda entrar en pánico pero que no devuelva un error o resultado haga que su firma de tipo cambie implícitamente de -> Foo a -> Result<Foo,!> 2) any Resultadotypes would have their Error types implicitly be converted to enumeración AnonMyErrWrapper {Die (!), Error} `` `
3) ¡Desde! es un anuncio de tamaño cero inhabitable. La conversión entre los tipos tendría un costo cero y se podría agregar una conversión implícita para que sea compatible con versiones anteriores.

El beneficio, por supuesto, es que las excepciones se incorporan de manera efectiva al sistema de tipos y sería posible razonar sobre ellas, realizar análisis estáticos en ellas, etc.

Me doy cuenta de que esto sería ... no trivial desde el punto de vista de la comunidad, si no desde el punto de vista técnico. :)

Además, esto probablemente se superponga con un posible sistema de efectos futuros.

@tupshin eso es un cambio radical, al menos sin mucha gimnasia. Recomiendo deshabilitar el desenrollado y usar "Resultado" manualmente si desea esa claridad. [Y por cierto, este problema no es realmente el lugar para mencionar algo así --- el diseño de ! no es algo que esté cuestionando, por lo que este es un trabajo puramente futuro].

Me doy cuenta de lo rompedor que sería, al menos sin gimnasia significativa, y tengo razón en que se trata de un trabajo totalmente futuro. Y sí, ¡estoy bastante contento con lo planeado! hasta ahora, hasta donde llega. :)

@nikomatsakis En cuanto a los temas pendientes, estos dos se pueden tachar

  • Limpieza de código de # 35162, reorganice typeck un poco para los tipos de hilo a través de la posición de retorno en lugar de llamar a expr_ty
  • Resolver el tratamiento de los tipos deshabitados en los partidos

Voy a empezar otra grieta en este:

  • ¿Cómo implementar advertencias para las personas que confían en (): Trait fallback donde ese comportamiento podría cambiar en el futuro?

Estoy planeando agregar una bandera a TyTuple para indicar que se creó mediante la configuración predeterminada de una variable de tipo divergente y luego verificando esa bandera en la selección de rasgos.

@canndrew

Estoy planeando agregar una bandera a TyTuple para indicar que se creó mediante la configuración predeterminada de una variable de tipo divergente y luego verificando esa bandera en la selección de rasgos.

¡Vale genial!

Bueno, tal vez genial. =) Suena un poco complejo tener dos tipos semánticamente equivalentes ( () vs () ) que son distintos de esa manera, pero no puedo pensar en una mejor manera. = (Y una gran parte del código debería estar preparado de todos modos gracias a las regiones.

Lo que quiero decir es que estoy agregando un bool a TyTuple

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

lo que indica que debemos generar advertencias en esta tupla (unidad) si intentamos seleccionar ciertos rasgos en ella. Esto es más seguro que mi enfoque original de agregar otro TyDefaultedUnit .

Con suerte, solo necesitamos mantener ese bool durante un ciclo de advertencia.

Con respecto al problema uninitialized / transmute / MaybeUninitialized , creo que un curso de acción sensato sería:

  • Agregue MaybeUninitialized a la biblioteca estándar, debajo de mem
  • Agregue un cheque en uninitialized que se sabe que su tipo está habitado. De lo contrario, envíe una advertencia con una nota que diga que esto será un error grave en el futuro. Sugiera usar MaybeUninitialized lugar.
  • Agregue una marca en transmute que su tipo "a" solo está deshabitado si su tipo "desde" también lo está. Del mismo modo, genera una advertencia que se convertirá en un error grave.

¿Pensamientos?

Parece haber cierto desacuerdo sobre la semántica de &! . Después de # 39151, con la puerta de función never_type habilitada, se trata como deshabitada en los partidos.

Si las implicaciones automáticas de rasgos para ! no estarán allí, al menos implementar los rasgos apropiados de std sería muy útil. Una gran limitación de void::Void es que vive fuera de std y eso significa que la manta impl<T> From<Void> for T es imposible y eso limita el manejo de errores.

Creo que al menos From<!> debería implementarse para todos los tipos y Error , Display y Debug deberían implementarse para ! .

Creo que se debería implementar al menos From<!> para todos los tipos

Desafortunadamente, esto entra en conflicto con la From<T> for T impl.

Desafortunadamente, esto entra en conflicto con la From<T> for T impl.

Al menos hasta que se implemente la especialización implícita de celosía.

Buenos puntos. Espero verlo hecho algún día.

¿Debería [T; 0] subtipo un [!; 0] y &[T] subtipo un &[!] ? Me parece intuitivo que la respuesta debería ser "sí", pero la implementación actual piensa lo contrario.

[!; 0] está habitado y también &[!] así que diría que no. Además, esta vez no usaremos subtipos.

Tanto [!; 0] como &[!] no deben estar deshabitados, ambos tipos pueden aceptar el valor [] (o &[] ).

Nadie dijo que están o deberían estar deshabitados.

let _: u32 = unsafe { mem::transmute(return) };
En principio, esto podría estar bien, pero el compilador se queja de "transmutar entre 0 bits y 32 bits".

Sin embargo, se permite la transmutación de ! -> () . No tengo ninguna razón en particular para señalar esto; es una inconsistencia, pero no puedo pensar en un problema práctico o teórico con eso.

No esperaría subtipos y me opondría a ello por evitar complicar todo tipo de lógica en el compilador. No quiero subtipos que no estén relacionados con las regiones o la unión de regiones, básicamente.

¿Es posible implementar Fn , FnMut y FnOnce para ! de forma genérica para todos los tipos de entrada y salida?

En mi caso, tengo un constructor genérico que puede tomar una función, que se invocará al compilar:

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

Como func es un Option , un constructor que use None no puede inferir el tipo de F . Por lo tanto, una implementación fn new debería usar Bang:

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

La función func del constructor debería verse así para ser invocada tanto en Builder<!> como en Builder<F> :

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

Ahora el problema: actualmente los rasgos Fn* no se implementan para ! . Además, no puede tener un tipo asociado genérico para rasgos (por ejemplo, Output en Fn ). Entonces, ¿es posible implementar la familia de rasgos Fn* por ! de manera que sea genérica para todos los tipos de entrada y salida?

Eso suena contradictorio. ¿No es necesario que <! as Fn>::Output resuelva en un solo tipo?

<! as Fn>::Output es ! , ¿no es así?

En teoría, <! as Fn>::Output debería ser ! , pero el verificador de tipo actual quiere que los tipos asociados coincidan exactamente: https://is.gd/4Mkxfm

@SimonSapin Es necesario que se resuelva en un solo tipo, por lo que planteo mi pregunta. Desde el punto de vista del lenguaje, es un comportamiento completamente correcto. Pero desde el punto de vista de la biblioteca, el uso de ! en contextos de funciones genéricas sería muy limitado si se mantiene el comportamiento actual.

¿Debería el siguiente 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 ? Ahora mismo no imprime nada. ¿O alguna otra solución alternativa que retrasará el return hasta la impresión?

Quiero decir, como dije antes, en realidad hay dos tipos diferentes de tipo ! : uno es perezoso y el otro está ansioso. La notación habitual denota un ansioso, pero en algún momento podríamos necesitar el perezoso.


Como @RalfJung está interesado en una versión anterior que usa &return , ajusté el código nuevamente. Y de nuevo, mi punto es que deberíamos poder retrasar los return más tarde. Podríamos usar cierres aquí, pero solo puedo usar return , no break o continue etc.

Por ejemplo, sería bueno si pudiéramos escribir

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

con el descanso retrasado hasta que se imprima 5 .

@earthengine No, definitivamente no debería imprimir nada. ! está deshabitado, lo que significa que no se pueden crear valores de tipo ! . Entonces, si tiene una referencia a un valor de tipo ! , está dentro de un bloque de código inactivo.

La razón por la que return tiene el tipo ! es porque diverge. El código que usa el resultado de una expresión return nunca se puede ejecutar porque return nunca "termina".

oO No sabía que se podía escribir &return . Eso no tiene sentido, ¿por qué debería poder tomar la dirección de return ? ^^

Pero, de todos modos, @earthengine en su código, los argumentos se evalúan antes de que se llame a quit_with_print . Al evaluar el segundo argumento se ejecuta return , que finaliza el programa. Esto es como escribir algo como foo({ return; 2 }) - foo nunca se ejecuta.

@RalfJung return es una expresión de tipo ! , como break o continue . Tiene un tipo, pero su tipo está deshabitado.

! se ha implementado y funciona todas las noches desde hace un año. Me gustaría saber qué se debe hacer para avanzar hacia la estabilización.

Una cosa que no se ha resuelto es qué hacer con los elementos intrínsecos que pueden devolver ! (es decir, uninitialized , ptr::read y transmute ). Por uninitialized , lo último que escuché fue que había un consenso de que debería estar en desuso a favor de un tipo MaybeUninit y un posible futuro &in , &out , &uninit referencias. No hay RFC para esto, aunque hay un RFC anterior en el que propuse desaprobar uninitialized solo para los tipos que no implementan un nuevo rasgo Inhabited . ¿Quizás ese RFC debería ser descartado y reemplazado con RFCs "desaprobar uninitialized " y "agregar MaybeUninit "?

Por ptr::read , creo que está bien dejarlo como UB. Cuando alguien llama a ptr::read está afirmando que los datos que está leyendo son válidos, y en el caso de ! definitivamente no lo es. ¿Quizás alguien tiene una opinión más matizada sobre esto?

Arreglar transmute es fácil: solo conviértalo en un error para transmutar a un tipo deshabitado (en lugar de ICEing como lo hace actualmente). Hubo un PR para solucionar esto, pero se cerró con el motivo de que todavía necesitamos una mejor idea de cómo tratar los datos no inicializados.

Entonces, ¿dónde estamos con estos en este momento? (/ cc @nikomatsakis)? ¿Estaríamos listos para seguir adelante con la depreciación de uninitialized y la adición de un tipo MaybeUninit ? Si hiciéramos que estos intrínsecos entraran en pánico cuando se les llamara con ! , ¿sería esa una medida provisional adecuada que nos permitiría estabilizar ! ?

También se enumeran los problemas pendientes:

¿Qué características deberíamos implementar por ! ? El PR # 35162 inicial incluye Ord y algunos otros. Esto probablemente sea más un problema de T-libs, por lo que agregaré esa etiqueta al problema.

Actualmente hay una selección bastante básica: PartialEq , Eq , PartialOrd , Ord , Debug , Display , Error . Además de Clone , que definitivamente debería agregarse a esa lista, no puedo ver ningún otro que sea de vital importancia. ¿Necesitamos bloquear la estabilización en esto? Podríamos agregar más implicaciones más adelante si lo consideramos oportuno.

¿Cómo implementar advertencias para las personas que dependen de (): Trait fallback donde ese comportamiento podría cambiar en el futuro?

La advertencia resolve_trait_on_defaulted_unit está implementada y ya está estable.

Semántica deseada para ! en coerción (# 40800)
Qué tipo de variables deben retroceder a ! (# 40801)

Estos parecen los verdaderos bloqueadores. @nikomatsakis : ¿Tiene alguna idea concreta sobre qué hacer con estos que están esperando ser implementados?

¿Tiene sentido que ! también implemente Sync y Send ? Hay varios casos en los que los tipos de error tienen límites Sync y / o Send , donde sería útil usar ! como "no puede fallar".

@canndrew No estoy de acuerdo con prohibirlo en un código inseguro. La caja unreachable usa transmutar para crear un tipo deshabitado y así insinuar al optimizador que cierta rama de código nunca puede ocurrir. Esto es útil para optimizaciones. Planeo hacer mi propia caja usando tal técnica de una manera interesante.

@Kixunil, ¿no sería más explícito usar https://doc.rust-lang.org/beta/std/intrinsics/fn.unreachable.html aquí?

@RalfJung lo haría, pero eso es inestable. Me recuerda que prohibir la transmutación en tipos deshabitados sería un cambio radical.

@jsgf Lo sentimos, sí, ! también implica todos los rasgos del marcador ( Send , Sync , Copy , Sized ).

@Kixunil Hmm, es una pena. Sin embargo, sería mucho mejor estabilizar el unreachable intrínseco.

No es un bloqueador de estabilización (porque es perf, no semántica), pero parece que Result<T, !> no está usando la deshabituación de ! en el diseño de enumeración o codegen de coincidencia: https://github.com / rust-lang / rust / issues / 43278

Esta es una idea tan vaga que casi me siento culpable por dejarla caer aquí, pero:

¿Podría la tiza ayudar a responder algunas de las preguntas más difíciles con respecto a las implementaciones de !: Trait ?

@ExpHP No lo creo. La implementación automática de !: Trait para rasgos que no tienen tipos / consts asociados y solo se sabe que los métodos no estáticos son sólidos. Es solo una cuestión de si queremos hacerlo o no. La implementación automática de otros rasgos realmente no tiene sentido porque el compilador necesitaría insertar valores arbitrarios para los tipos / consts asociados e inventar sus propias implicaciones de métodos estáticos arbitrarios.

Ya hay una pelusa resolve-trait-on-defaulted-unit en rustc. ¿Se debe marcar el elemento correspondiente?

@canndrew ¿ correcto establecer los tipos asociados en ! ?

@taralx no es incorrecto, pero evitaría que funcione un código válido. Tenga en cuenta que el siguiente código se compila hoy:

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 de su opinión sobre lo que es el código "válido". Para mí, con mucho gusto acepto que el código que proporcionó es "inválido", ya que no debería poder obtener nada de ! .

@earthengine No veo el punto que estás tratando de hacer. Puede llamar a un método estático en un tipo sin tener una instancia de él. Si puede o no "obtener algo" de ! no tiene nada que ver con eso.

No hay ninguna razón por la cual no debería ser capaz de conseguir algo de ! del tipo. Sin embargo, no puede obtener nada de un ! , por lo que los métodos no estáticos en ! podrían derivarse sin forzar ninguna decisión al programador.

Creo que la regla más fácil de recordar sería implementar cualquier rasgo para el que haya exactamente 1 implementación válida (excluyendo aquellos que agregan más divergencia que ! s en el alcance que ya causan).

Cualquier cosa con la que vayamos debe ser igual o más conservadora con eso (lo que tampoco encaja con las implicaciones automáticas).

@ Ericson2314 No entiendo lo que eso significa, ¿podría dar algunos ejemplos?

@SimonSapin La panic!() o loop { } , pero un v: ! que ya está dentro del alcance está bien. ¡Con expresiones como las descartadas, muchos rasgos solo tienen una posible implicación para! ese tipo comprueba. (Otros pueden tener un contrato informal que descarta todas menos una implícita, pero no podemos ocuparnos de ellos automáticamente).

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

Matemáticamente hablando, ! es un "elemento inicial", lo que significa que para una firma de tipo que tiene ! en sus argumentos, hay exactamente una implementación, es decir, todas las implementaciones son iguales - cuando observado desde el exterior. Esto se debe a que no se pueden llamar a todos.

¿Qué está realmente bloqueando esto de la estabilización? ¿Es solo la situación mem::uninitialized o algo más?

@ arielb1 : Creo que es # 40800 y # 40801 también. ¿Sabes cuál es el estado de estos?

¿Qué documentos se han escrito sobre esto, o la gente planea escribir para esto? Con las recientes adiciones a las páginas de tipo primitivo en los documentos estándar (# 43529, # 43560), ¡debería el! escriba obtener una página allí? La referencia probablemente también debería actualizarse, si aún no lo ha hecho.

¿Hay algún plan que permita especificar que ASM es divergente para que podamos escribir cosas como?

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

¿Sin tener que usar loop{} para apaciguar al verificador de tipos?

Editar: Aunque supongo que usar core::intrinsics::unreachable puede ser aceptable en un código de muy bajo nivel.

@ Eroc33 cuando ! es un tipo, puede hacer esto:

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

Hasta entonces puedes hacer esto:

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

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

@Kixunil ! es un tipo de noche que debe usar asm! . Esto generalmente se hace como:

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

@Kixunil Se puede hacer más fácilmente usando std::intrinics::unreachable() que especifica exactamente que el código anterior es divergente.

@Kixunil ¿ De la discusión anterior std::mem::uninitialized poder devolver ! parece estar sujeto a cambios? ¿A menos que lo haya entendido mal?

¿De la discusión anterior std::mem::uninitialized poder devolver ! parece estar sujeto a cambios? ¿A menos que lo haya entendido mal?

Creo que uninitialized eventualmente se desaprobará por completo y, como medida provisional, entrará en pánico en tiempo de ejecución cuando se use con ! (aunque eso no afectaría este caso).

También creo que tendremos que estabilizar std::intrinsics::unreachable en algún lugar de libcore y nombrarlo unchecked_unreachable o algo para distinguirlo de la macro unreachable! . Tenía la intención de escribir un RFC para eso.

@eddyb Ah, sí, olvidé que asm!() es inestable, por lo que std::intrinsics::unreachable() podría usarse.

@ tbu- Claro, prefiero ese en lugar de crear un tipo deshabitado. El problema es que es inestable, por lo que si de alguna manera fuera imposible marcar de manera insegura una rama de código como inalcanzable, no solo rompería el código existente, sino que también lo haría irreparable. Creo que tal cosa dañaría gravemente la reputación de Rust (especialmente considerando que los desarrolladores principales afirman con orgullo que es estable). Por mucho que me guste Rust, tal cosa me haría reconsiderar que es un lenguaje útil.

@ Eroc33 tengo entendido que algunas personas sugieren hacerlo, pero es una mera sugerencia, no una decisión definitiva. Y me opongo a tal sugerencia porque rompería la compatibilidad con versiones anteriores, lo que obligaría a Rust a convertirse en 2.0. No creo que haya ningún daño que lo respalde. Si la gente está preocupada por los errores, la pelusa sería más apropiada.

@canndrew ¿Por qué crees que uninitialized quedará obsoleto? No puedo creer tal cosa. Lo encuentro extremadamente útil, así que a menos que exista un reemplazo muy bueno, no veo ninguna razón para hacerlo.

Finalmente, me gustaría reiterar que estoy de acuerdo en que intrinsics::unreachable() es mejor que los hacks actuales. Dicho esto, me opongo a prohibir o incluso desaprobar esos trucos hasta que haya un reemplazo suficiente.

El reemplazo de no inicializado es union {!, T}. Puede llegar a ser inalcanzable por
ptr :: read :: <* const!> () ing y muchas otras formas similares.

El 8 de agosto de 2017 a las 15:58, "Martin Habovštiak" [email protected]
escribió:

@eddyb https://github.com/eddyb Ah, sí, olvidé que asm! () es
inestable, por lo que std :: intrinsics :: unreachable () también podría usarse.

@ tbu- https://github.com/tbu- Claro, prefiero ese en lugar de
creando un tipo deshabitado. El problema es que es inestable, así que si fuera
de alguna manera imposible marcar de manera insegura una rama de código como inalcanzable, no sería
solo rompe el código existente, pero también lo hace irreparable. Yo pienso tal cosa
dañaría gravemente la reputación de Rust (especialmente considerando los principales desarrolladores
afirmando con orgullo que es estable). Por mucho que ame a Rust, tal cosa
hazme reconsiderar que sea un lenguaje útil.

@ Eroc33 https://github.com/eroc33 tengo entendido que algunas personas
Sugiero hacerlo, pero es una mera sugerencia, no una decisión definitiva. Y yo
oponerse a tal sugerencia porque rompería la compatibilidad con versiones anteriores,
obligando a Rust a convertirse en 2.0. No creo que haya ningún daño que lo respalde.
Si la gente está preocupada por los errores, la pelusa sería más apropiada.

@canndrew https://github.com/canndrew ¿Por qué crees que no está inicializado?
quedará obsoleto? No puedo creer tal cosa. Lo encuentro extremadamente útil,
así que a menos que exista un muy buen reemplazo, no veo ninguna razón para
haciéndolo.

Finalmente, me gustaría reiterar que estoy de acuerdo en que intrínsecos :: inalcanzable ()
es mejor que los hacks actuales. Dicho esto, me opongo a prohibir o incluso
desaprobar esos trucos hasta que haya un reemplazo suficiente.

-
Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-320948013 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AApc0iEK3vInreO03Bt6L3EAByBHQCv9ks5sWFt3gaJpZM4JYi9D
.

@nagisa ¡Gracias! Parece que solucionaría el problema a nivel técnico.

¿Sería posible tallar un subconjunto de esto para que ! pudiera usarse como un parámetro de tipo en los tipos de retorno? Ahora mismo si tienes una función estable

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

Y desea usar ? , no puede realizar la transformación habitual a

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

¿Afectan a ese caso las preguntas espinosas sobre coerción y el parámetro de tipo?

40801 Se puede marcar.

Deberíamos intentar arreglar # 43061 antes de estabilizar ! .

No vi ningún problema de I-incorrecto abierto que mencionara never_type, así que presenté uno nuevo para esto: # 47563. Las cosas parecen asumir que [!; 0] está deshabitado, pero puedo crear uno con un simple [] .

@dtolnay agregado al encabezado del problema

@canndrew ¿cómo estás por tiempo? Me gustaría que se estabilizara un subconjunto del uso de ! (excluyendo las reglas sobre lo que es exhaustivo). Sin embargo, he perdido el hilo de dónde estamos y creo que se necesita un campeón. ¿Tienes tiempo para ser esa persona?

@nikomatsakis Estoy seguro de que puedo encontrar algo de tiempo para trabajar en esto. Sin embargo, ¿qué hay que hacer exactamente? ¿Son solo los errores relacionados con este problema?

Parece que @varkor ya tiene PR abiertos para solucionar los problemas restantes (¡increíble!). Por lo que puedo ver, lo único que queda por hacer es decidir si estamos contentos con los rasgos que están implementados actualmente y mover / cambiar el nombre de la puerta de características para que solo cubra los cambios exhaustivos de coincidencia de patrones.

Aunque otra cosa: ¿queremos hacer que mem::uninitialized::<!>() pánico en tiempo de ejecución (actualmente causa UB)? ¿O deberíamos dejar ese tipo de cambios por ahora? No estoy al tanto de lo que está sucediendo con las pautas de código inseguro.

Creo que el plan sigue siendo https://github.com/rust-lang/rfcs/pull/1892 , que acabo de notar que escribiste. :)

@RalfJung ¿Hay algo en particular que bloquee eso? Podría escribir un PR hoy que agregue MaybeUninit y deprecie uninitialized .

@canndrew Dado que muchos proyectos quieren admitir los tres canales de lanzamiento y uninitialized está disponible en estable, es preferible comenzar a emitir advertencias de desaprobación en Nightly una vez que el reemplazo esté disponible en el canal estable. La desaprobación suave a través de comentarios de documentos está bien.

Creé un PR para estabilizar ! : https://github.com/rust-lang/rust/pull/47630. No sé si estamos listos para fusionarlo todavía. Al menos deberíamos esperar para ver si los RP de

También creo que deberíamos volver a visitar https://github.com/rust-lang/rfcs/pull/1699 para que la gente pueda empezar a escribir elegantes rasgos implícitos por ! .

@canndrew : Aunque no se aceptó esa RFC, se ve muy similar a la propuesta en https://github.com/rust-lang/rust/issues/20021.

https://github.com/rust-lang/rust/issues/36479 probablemente también debería agregarse al encabezado del problema.

@varkor es muy similar. Con su trabajo de código inalcanzable, ¿ha notado algún problema con un código como este que ahora se marca como inalcanzable ?:

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

Porque para eso sería necesario aceptar algún tipo de propuesta como esas.

@canndrew, algo que he estado defendiendo últimamente, aunque todavía tenemos que desarrollar una plantilla formal, es una especie de "problema de resumen" que intenta delinear claramente lo que estamos estabilizando. Puede considerarlo como un informe posterior al RFC, tratando de resumir en una especie de lista de viñetas las características más destacadas de lo que se está estabilizando pero también de lo que no .

Parte de esto serían sugerencias para probar casos que demuestren el comportamiento en cuestión.

¿Crees que podrías intentar redactar tal cosa? Podemos charlar un poco juntos si lo desea sobre una especie de "esquema", o tal vez pueda intentar esbozarlo.

Pregunta relacionada: ¿deberíamos intentar resolver https://github.com/rust-lang/rust/issues/46325 antes de estabilizarnos? Quizás no importa.

@nikomatsakis Voto por no esperar a que se resuelvan los problemas de advertencia. Eso es inofensivo. Si no aparecen más preocupaciones reales, creo que deberíamos seguir adelante y estabilizarlo.

@canndrew : No creo que haya analizado realmente ningún caso como ese, aunque definitivamente creo que poder omitir implementaciones imposibles será muy necesario cuando ! esté estabilizado.

@nikomatsakis

¿Crees que podrías intentar redactar tal cosa? Podemos charlar un poco juntos si lo desea sobre una especie de "esquema", o tal vez pueda intentar esbozarlo.

Podría redactar un borrador al menos y tú podrías decirme si es lo que tienes en mente. Intentaré hacerlo en los próximos días.

@nikomatsakis ¿ Algo como esto?

Problema resumido: estabilización de !

Que se esta estabilizando

  • ! ahora es un tipo completo y ahora se puede usar en cualquier posición de tipo (por ejemplo, RFC 1216 ). El tipo ! puede coaccionar a cualquier otro tipo, consulte https://github.com/rust-lang/rust/tree/master/src/test/run-fail/adjust_never.rs para ver un ejemplo.
  • La inferencia de tipo ahora establecerá de forma predeterminada las variables de tipo sin restricciones en ! lugar de () . La pelusa resolve_trait_on_defaulted_unit se ha retirado. Un ejemplo de dónde surge esto es si tiene 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);
    

    Bajo las reglas anteriores esto deserializaría un () , mientras que bajo las nuevas reglas deserializaría un ! .

  • La puerta de función never_type es estable, aunque algunos de los comportamientos que solía usar ahora viven detrás de la nueva puerta de función exhaustive_patterns (ver más abajo).

Que no esta siendo estabilizado

¿Deberíamos intentar resolver # 46325 antes de estabilizarnos?

Por agradable que sea limpiar todos los cabos sueltos de esta forma, no parece un obstáculo.

@canndrew

¿Algo como esto?

¡Sí, gracias! Eso es genial.

Lo principal que falta son punteros para probar casos que muestran cómo se comporta ! . La audiencia debería ser el equipo de lang u otras personas que lo sigan de cerca, creo que no está realmente dirigido a personas de "propósito general". Por ejemplo, me gustaría algunos ejemplos de lugares donde las coacciones son legales, o donde se puede usar ! . También me gustaría ver los testículos que nos muestran que la coincidencia de patrones exhaustiva (sin la función de puerta habilitada) todavía se comporta. Estos deberían ser indicadores del repositorio.

@canndrew

Por agradable que sea limpiar todos los cabos sueltos de esta forma, no parece un obstáculo.

Bueno, significa que habilitaremos un nuevo código que cambiará inmediatamente el comportamiento (por ejemplo, let x: ! = ... creo que se comportará de manera diferente para algunas expresiones). Parece que es mejor resolverlo si podemos. ¿Quizás puede convertirlo en un error grave en el PR que tenía abierto y podemos agruparlo con el cráter existente en el PR?

@nikomatsakis He actualizado ese problema de resumen nuevamente con algunos enlaces y ejemplos. Además, lamento que me haya tomado un tiempo llegar a esto, estuve muy ocupado la semana pasada.

¿Quizás puede convertirlo en un error grave en el PR que tenía abierto y podemos agruparlo con el cráter existente en el PR?

Hecho.

@rfcbot fcp fusionar

Propongo que estabilicemos el tipo ! , o al menos parte de él, como se describe aquí . El PR está aquí .

Un dato que me gustaría es un cráter. Estoy en el proceso de reajustar https://github.com/rust-lang/rust/pull/47630 (ya que @canndrew no responde a los pings en este momento) para que podamos obtener esos datos.

El miembro del equipo @nikomatsakis ha propuesto fusionar esto. El siguiente paso es la revisión por parte del resto de equipos etiquetados:

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

No hay preocupaciones actualmente enumeradas.

Una vez que la mayoría de los revisores aprueben (y ninguno objete), entrará en su período de comentarios final. Si detecta un problema importante que no se ha planteado en ningún momento de este proceso, ¡hable!

Consulte este documento para obtener información sobre los comandos que pueden darme los miembros del equipo etiquetados.

Oh, un par de cosas que acabo de recordar:

  • Deberíamos considerar la idea de estabilizar esto solo en la nueva época . En particular, el cambio a las reglas de respaldo son incompatibles hacia atrás: la carrera del cráter nos dará algún tipo de límite inferior en la lluvia, pero es solo un límite inferior. Pero tal vez podamos mantener las viejas reglas de respaldo a menos que estés en la nueva época.
  • En segundo lugar, creo que parte del plan aquí también era tener algunas pautas sobre cuándo es apropiado implementar un rasgo por ! . El TL; DR es que está bien si los métodos en el rasgo no se pueden usar sin primero proporcionar un ! , por lo que implementar Clone por ! está bien, creo , pero implementar Default no lo es. Dicho de otra manera, si implementar el impl va a requerir que panic! indiscriminadamente, esa es una mala señal. =)

@nikomatsakis ¿Podríamos cambiar la regla de reserva en una nueva época pero aún hacer que ! como tipo esté disponible en la época de 2015?

@nikomatsakis

Deberíamos considerar la idea de estabilizar esto solo en la nueva época .

La última vez que hicimos una carrera en el cráter (que fue hace mucho tiempo), las consecuencias de cambiar las reglas de reserva fueron bastante mínimas. También hemos estado luchando contra el código que podría verse afectado por el cambio durante un tiempo.

En segundo lugar, creo que parte del plan aquí también era tener algunas pautas sobre cuándo es apropiado implementar un rasgo por ! .

Esto se menciona en los documentos por !

@SimonSapin

¿Podríamos cambiar la regla de reserva en una nueva época pero aún así lograrlo? como un tipo disponible en la época de 2015?

@canndrew

La última vez que hicimos una carrera en el cráter (que fue hace mucho tiempo), las consecuencias de cambiar las reglas de reserva fueron bastante mínimas. También hemos estado luchando contra el código que podría verse afectado por el cambio durante un tiempo.

Sí. Veamos qué dice el cráter. Pero, como dije, el cráter solo nos da un límite inferior , y esto es una especie de "cambio electivo". Aún así, sospecho que tiene razón y podemos "salirse con la suya" cambiando esto sin mucho efecto en el código en la naturaleza.

El TL; DR es que está bien si los métodos del rasgo no se pueden utilizar sin antes proporcionar un !

Esa es solo la regla normal: agrega un impl cuando puede implementarlo de una manera sensata, donde "implementarlo de una manera sensata" excluye el pánico, pero incluye UB "ex falso" en presencia de datos no válidos.

@ arielb1 Sí, pero por alguna razón la gente tiende a confundirse con cosas como esta en presencia de ! , por lo que parece que vale la pena llamarlo explícitamente.

¿Quizás ayudaría tener un método seguro fn absurd(x: !) -> ! que esté documentado como una forma segura de expresar código inalcanzable o algo así en algún lugar de libstd? Creo que había un RFC para eso ... o al menos un problema de RFC: https://github.com/rust-lang/rfcs/issues/1385

@RalfJung ¿No es esa función absurd solo identity ? ¿Por qué es útil?

No es lo mismo que el unsafe fn unreachable() -> ! intrínseco que no toma ningún argumento y tiene un comportamiento muy indefinido.

Bueno, sí, en su mayor parte lo es. Estaba usando el tipo de retorno ! en el sentido de "no termina". (El tipo habitual de absurd es fn absurd<T>(x: !) -> T , pero eso tampoco parece útil en Rust).

Estaba pensando que tal vez esto ayudaría con "la gente tiende a confundirse con cosas como esta en presencia de ! " - tener algo de documentación en algún lugar de que el razonamiento "ex falso" es una cosa.

Parece que también tengo el problema equivocado ... Pensé que en algún lugar había una discusión sobre esa función "ex falso". Parece que estoy equivocado.

fn absurd<T>(x: !) -> T también se puede escribir match x {} , pero eso es difícil de encontrar si no lo ha visto antes. Al menos vale la pena señalarlo en rustdoc y en el libro.

fn absurdo(x:!) -> T también se puede escribir coincidiendo con x {}, pero eso es difícil de encontrar si no lo ha visto antes.

Bien, es por eso que sugerí un método en algún lugar de libstd. Sin embargo, no estoy seguro de cuál es el mejor lugar.

Lo bueno de absurd es que puede pasarlo a funciones de orden superior. ¡No estoy seguro de que esto sea necesario dado cómo! se comporta wrt. aunque la unificación.

fn absurd<T>(x: !) -> T también se puede escribir match x {}

También se puede escribir como x (AFAIK); solo necesita match si tiene un enum vacío.

Una función absurd(x: !) -> T sería útil para pasar a funciones de orden superior. Necesitaba esto (por ejemplo) al encadenar futuros, uno de los cuales tiene el tipo de error ! y el otro tipo de error T . Aunque una expresión del tipo ! puede coaccionar en T eso no significa que ! y T se unificarán, por lo que a veces aún es necesario hacerlo manualmente. convertir el tipo.

De manera similar, creo que los tipos Result -like deberían tener un método .infallible() que convierta Result<T, !> en Result<T, E> .

Los tipos similares a resultados deben tener un método .infallible () que convierta Resultal resultado.

Hubiera esperado que tal método tuviera el tipo Result<T, !> -> T .

Hubiera esperado que tal método tuviera el tipo Result-> T.

¿No es lo mismo que unwrap ? El pánico se optimizará ya que el caso Err es inalcanzable.

@Amanieu El punto es que, para empezar, no hay pánico. Me encantaría tener una pelusa de pánico, y eso se tropezaría si confiáramos en la optimización. Incluso sin la pelusa, usar unwrap agrega una pistola durante los refactores, menos el pánico se convierte en código vivo una vez más.

unwrap lee como assert - "verificación de tiempo de ejecución por adelantado" (IIRC incluso hubo propuestas para cambiarle el nombre, pero llegaron demasiado tarde ... lamentablemente). Si no desea y necesita una verificación de tiempo de ejecución, infallible lo haría explícito.

: bell: Esto ahora está entrando en su período de comentarios final , según la revisión anterior . :campana:

@rfcbot fcp cancelar

@aturon y @pnkfelix no han marcado sus casillas.

Propuesta de @cramertj cancelada.

El miembro del equipo @cramertj ha propuesto fusionar esto. El siguiente paso es la revisión por parte del resto de equipos etiquetados:

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

No hay preocupaciones actualmente enumeradas.

Una vez que la mayoría de los revisores aprueben (y ninguno objete), entrará en su período de comentarios final. Si detecta un problema importante que no se ha planteado en ningún momento de este proceso, ¡hable!

Consulte este documento para obtener información sobre los comandos que pueden darme los miembros del equipo etiquetados.

: bell: Esto ahora está entrando en su período de comentarios final , según la revisión anterior . :campana:

Hablamos de esto en la reunión de hoy. Sigo dudando sobre si cambiar la alternativa ahora o solo en la nueva época. Aquí están los distintos puntos.

El impacto esperado del cambio es pequeño. De manera realista, esto no hace mucha diferencia de ninguna manera. Puede ver el análisis completo aquí , pero el resumen es:

Y eso solo deja las dos regresiones reales: oplog-0.2.0 (en realidad es su dependencia bson-0.3.2 la que está rota) y rspec-1.0.0-beta.4. Ambos se basan en el antiguo comportamiento alternativo, aunque afortunadamente se rompen en tiempo de compilación, no en tiempo de ejecución. Enviaré RP para arreglar estas cajas.

Pero técnicamente sigue siendo un cambio radical (y voluntario). No hay ninguna razón particular que tenemos que cambiar la reserva para las variables de tipo, excepto que () es una mala elección singular y es bastante raro para el código de confiar en él. También hemos estado advirtiendo sobre esto durante mucho tiempo.

Creo que esto es más una cuestión de filosofía: en el pasado, hicimos cambios menores como este por necesidad. Pero ahora que tenemos el mecanismo de época, nos ofrece una forma más basada en principios para hacer tales transiciones sin romper técnicamente nada. Puede valer la pena ponerlo en práctica, solo por principio. Por otro lado, eso significaría a veces esperar años para hacer un cambio como este. Y, por supuesto, tenemos que mantener ambas versiones a perpetuidad, lo que hace que la especificación del idioma, etc., sea mucho más compleja.

Estoy un poco desgarrado, pero creo que en equilibrio, después de haberme tambaleado de un lado a otro, actualmente me estoy inclinando hacia "vamos a cambiarlo universalmente", como estaba planeado.

Sé que soy parcial, pero veo que cambiar el comportamiento de reserva es más una corrección de errores en comparación con cosas como dyn Trait y catch .

Además, si alguien quiere saltar sobre esto y decir, "¡Ah, ja! ¡Rust rompe la compatibilidad con versiones anteriores después de todo! ¡Su promesa de estabilidad cuando llegó a 1.0 era una mentira!" luego, la discusión muy reflexiva sobre este tema muestra que la decisión no se tomó a la ligera, incluso teniendo en cuenta que hubo un impacto práctico insignificante.

OK, digo que lo cambiemos.

Entonces, como compromiso, me gustaría dar una nota útil para los errores que informa a las personas que el comportamiento ha cambiado, parece plausible. La idea sería algo como esto:

  • Si vemos un error como !: Foo para algún rasgo, y ese rasgo se implementa para (): Foo , y hemos hecho una reserva (podemos decirle al código de informe de errores este hecho), entonces agregamos un nodo extra.

El período de comentarios final ahora está completo.

¿Las casillas de verificación pendientes en la publicación superior todavía no están marcadas? ¿Hay algo sin hacer en esa lista?

@earthengine No creo que los dos que veo sean especialmente relevantes; en cuanto al elemento superior, sobre el conjunto de implicaciones, supongo que ahora está decidido por el momento.

Básicamente para ser un conjunto mínimo.

@nikomatsakis Acabo de crear el problema de resumen aquí: https://github.com/rust-lang/rust/issues/48950

¿Ha habido alguna consideración de hacer ! un patrón que coincida con los valores del tipo ! ? (He hecho un grep rápido a través de este problema y el problema de RFC original, pero no encontré nada relevante).

Esto me parecería útil para tipos como StreamFuture que crean tipos de error compuestos a partir de tipos de error potencialmente ! . Al usar un patrón ! en map_err , si el tipo de error en algún momento en el futuro cambia, entonces la llamada map_err dejaría de compilar en lugar de potencialmente hacer algo diferente.

Como ejemplo, dado

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

esto permitiría escribir el más explícito

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

en lugar del potencialmente propenso a errores

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

o el un poco menos propenso a errores

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

EDITAR: Releyendo https://github.com/rust-lang/rfcs/pull/1872 los comentarios mencionan la introducción del patrón ! . Sin embargo, eso es puramente en el contexto de las expresiones match , no estoy seguro de si se generaliza a los argumentos de cierre / función.

@ Nemo157

¿Ha habido alguna consideración de hacer? un patrón que coincide con los valores de! ¿escribe?

Interesante. Creo que el plan era simplemente dejarte fuera de esos brazos, en cuyo caso, si el tipo cambiara en el futuro, aún obtendrías un error, porque tu combinación ahora ya no sería exhaustiva. El problema con un patrón ! es que, si coincide, significa que el brazo es imposible, lo cual es un poco incómodo; nos gustaría dar cuenta de eso, me imagino, al no pedirle que dé una expresión para ejecutar. Aún así, sería bueno tener una forma de decir explícitamente que este caso no puede suceder.

En realidad, extender el manejo de patrones inalcanzables podría ser una forma alternativa de resolver esto. Si el brazo de fósforo era demostrablemente imposible, tal vez se podría ignorar el código dentro del brazo. Entonces cuando tengas

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

esto aún se expandiría como hoy (_No estoy 100% seguro de que sea la expansión correcta, pero parece lo suficientemente cerca_)

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

pero no se verificará el tipo de la rama Result::Err y no obtendrá el error the trait bound `std::string::String: std::convert::From<(!, std::string::String)>` is not satisfied que recibe hoy.

Sin embargo, no creo que sea compatible con lo que proponía RFC 1872, porque hay un brazo explícito que está realizando una coincidencia superficial, no se basa necesariamente en la validez del ! y podría potencialmente ejecute la rama asociada "de forma segura" siempre que esa rama no utilice e.0 en absoluto.

! es un tipo positivo, como bool , por lo que es un poco imposible hacer coincidir el patrón en una lista de argumentos de cierre sin extender la sintaxis de cierre para permitir múltiples ramas. p.ej. tal vez podríamos permitir que los cierres bool -> u32 se escriban algo como (sintaxis hipotética fea) [~ |false| 23, |true| 45 ~] -> u32 , luego un cierre de cualquier tipo vacío a u32 podría simplemente escribirse [~ ~] -> u32 .

Con sus ejemplos originales, puede escribir foo.map_err(|_: (!, _)| unreachable!()) aunque prefiero foo.map_err(|(e, _)| e) ya que evita usar unreachable! .

! es un tipo positivo

¿Qué quiere decir con tipo positivo?

es un poco imposible hacer coincidir el patrón en una lista de argumentos de cierre sin extender la sintaxis de cierre para permitir múltiples ramas

Las listas de argumentos ya admiten subpatrones irrefutables, por ejemplo

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

Solo consideraría el patrón ! como un patrón irrefutable que coincide con todos los valores 0 del tipo ! , lo mismo que () es un patrón irrefutable que coincide con todos los valores 1 de el tipo () .

¡Solo consideraría el! patrón como un patrón irrefutable que coincide con todos los valores 0 de! type, lo mismo que () es un patrón irrefutable que coincide con todos los valores 1 del tipo ().

Bueno, pero 0! = 1.;) Es inútil incluso escribir cualquier código después de hacer coincidir un ! , por ejemplo, si el tipo de argumento es (!, i32) , entonces |(!, x)| code_goes_here es una tontería ya que ese código está muerto de todos modos. Probablemente escribiría |(x, _)| match x {} para dejar muy explícito que x es un elemento de ! . O, usando la función absurd que propuse anteriormente , |(x, _)| absurd(x) . ¿O quizás |(x, _)| x.absurd() ?

Uno podría imaginar una sintaxis diferente que permite una escritura ningún código, como lo @canndrew escribió anteriormente. match tiene cualquier número de ramas, por lo que en particular es posible no tener

Lástima que no podamos agregar impl<T> From<!> for T ahora. (¿Se superpone con From<T> for T , a menos que haya algo de especialización?) Probablemente no podamos agregarlo más tarde. (En los ciclos de lanzamiento después de que ! se haya estabilizado, algunas cajas podrían implementar From<!> for SomeConcreteType ).

@SimonSapin ¡ buen punto! Este es un bloque de construcción muy útil para muchas cosas, y no me gustaría perderlo para siempre. Consideraría seriamente un truco único para permitir ambas instancias. Coinciden semánticamente en el caso de superposición, por lo que no hay "incoherencia operativa".

Consideraría seriamente un truco único

Entonces ese truco debe aterrizar en los próximos días. (O ser respaldado durante la versión beta).

@SimonSapin ¿ From<!> for T .

Alternativamente, podríamos lanzar rápidamente una advertencia contra la implementación de From<!> for SomeConcreteType .

No lo sé, depende de cuál sea el truco. Creo que la especialización de rasgos puede habilitar dos implicaciones superpuestas si hay una tercera para la intersección, pero ¿eso requiere que el rasgo sea "especializable" públicamente?

Desafortunadamente, especialización:

  • Requeriría que From::from se cambie a default fn , que sería "visible" fuera de std. No sé si queremos hacer eso en los rasgos de ETS siempre que la especialización sea inestable.
  • Como se acepta en RFC 1210, específicamente no es compatible con la característica del lenguaje en la que estaba pensando (eliminar la ambigüedad de dos implicaciones superpuestas donde ninguna es más específica que la otra escribiendo una tercera implicación para la intersección).

Aquí las dos implicaciones hacen exactamente lo mismo. Si simplemente desactivamos la comprobación de coherencia, será difícil determinar qué impl se utiliza, lo que normalmente no es muy sólido, pero en este caso está bien. Podría provocar algunos pánicos en la compilación esperando, por ejemplo, un resultado, pero podemos solucionarlo.

En otras palabras, creo que esto es mucho más fácil que acelerar cualquier especialización, afortunadamente.

¿Existe un RFC para formalizar esta idea de "permitir la superposición arbitraria si todas las implicaciones involucradas son inalcanzables"? Parece un enfoque interesante a considerar.

No que yo sepa. Para ser pendiente, el impl en sí mismo no es inalcanzable, sino que los métodos internos son incalculables (incluidos los tipos o constantes asociados, ya que siempre son "invocables"). Supongo que podría escribir uno rápidamente si se considera que el truco está bloqueado en tal cosa.

¡Solo menciono el problema # 49593 que podría causar! volver a desestabilizarse para una mejor búsqueda.

Me pregunto qué otras implicaciones además de T: From<!> no se pueden agregar después de que ! se estabilice. Probablemente deberíamos tratar de manejar todas estas implicaciones razonables antes de estabilizarnos, a diferencia de las implicaciones para ! en general, donde no hay tanta prisa.

Publicación cruzada de este comentario :

Me preguntaba: ¿cuál es el ejemplo clásico de por qué el retroceso a () "tendría" que cambiarse? Es decir, si continuamos recurriendo a (), pero aún así agregamos!, Ciertamente evitaría ese tipo de problema, ¡y volver a! no es óptimo, dado que hay casos en los que se produce el retroceso a una variable de tipo que termina influyendo en el código "en vivo" (la intención, por supuesto, es otra, pero es difícil evitar fugas, como hemos descubierto).

Ha habido varias regresiones como resultado de ese cambio y debo decir que no me siento del todo cómodo con él:

(Nominación para la discusión del equipo de idiomas sobre este punto).

Durante la reunión, retiramos un poco algunas de las opciones aquí; me siento reacio a hacer cambios, principalmente porque cambia la semántica del código existente en algunos casos de esquina, y no está claro que las nuevas reglas sean mejores . Pero sobre todo creo que decidimos que sería genial intentar recopilar los pros / contras de cada diseño nuevamente para que podamos tener una discusión más completa. Me gustaría ver una lista de casos de uso, así como de las dificultades. @cramertj mencionó su deseo de retroceso en casos variantes, por ejemplo, Ok(33) tiene un tipo que retrocede a Result<i32, !> , en lugar de cometer errores como lo haría hoy; Creo que entonces decían que mantener el respaldo de return como () sería inconsistente con tal cambio (aunque son ortogonales) y podría crear conflictos en el futuro. Esto es justo.

El desafío con los comentarios de Err (# 40801) es que también hay casos extremos, como:

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

donde el tipo de x se infiere en última instancia a Result<T, !> .

Me recuerda un plan separado que tenía que probar y ver si podíamos determinar (¿y quizás advertir? ¿O error?) Si ! fallback alguna vez "afectó" el código en vivo; esto resultó bastante difícil, aunque parece como si pudiéramos lint muchos casos en la práctica (por ejemplo, ese, y probablemente el caso de @SimonSapin ).

También se discutió si podríamos eliminar este respaldo por completo en la nueva edición. Sospecho que romperá muchas macros, pero ¿quizás valdría la pena intentarlo?

Para su información, esta característica provocó una regresión en 1.26: # 49932

(Configuración de etiquetas según https://github.com/rust-lang/rust/issues/35121#issuecomment-368669041.)

¿Debería el elemento de la lista de verificación sobre la coerción en ! apuntar a https://github.com/rust-lang/rust/issues/50350 en lugar de hacer referencia a este problema?

Si quiero que esto se estabilice lo antes posible, ¿cuál es el mejor lugar para dirigir mi energía?

@remexre Creo que la mejor descripción del problema que lleva a revertir la estabilización está en https://github.com/rust-lang/rust/issues/49593

Entonces, una solución viable sería la caja de casos especiales como un subtipo de
Caja? ¿Hay alguna característica segura para los objetos que no se pueda manejar de esta manera?

El domingo, 8 de julio de 2018 a las 8:12 a.m., Ralf Jung [email protected] escribió:

@remexre https://github.com/remexre Creo que la mejor descripción del
El problema que lleva a revertir la estabilización está en # 49593
https://github.com/rust-lang/rust/issues/49593

-
Recibes esto porque te mencionaron.

Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-403286892 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AEAJtcnsEaFmHrrlHhuQeVOkR8Djzt50ks5uEgVLgaJpZM4JYi9D
.

>

Gracias,
Nathan

Esa discusión realmente debería ir a https://github.com/rust-lang/rust/issues/49593 , pero una parte clave es que Box::<T>::new requiere T: Sized , pero que new Se infiere que T = Error (un rasgo DST) según el tipo de devolución.

Aparte de la falta de elegancia de un caso especial, ¿hay algún problema con la carcasa especial Box::new ?

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

o

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

En primer lugar, debemos pensar cuál es el tamaño de ! . Los matemáticos propondrán algo como Inf , pero en realidad será usize::MAX , por lo que nos aseguramos de que cualquier intento de asignar espacio para este tipo fallará o al menos panic .

Si ! es Sized entonces, no hay nada que nos detenga para compilar Box::new(x as !) , pero esta es básicamente otra forma de panic! , ya que ningún modelo de memoria puede en realidad ubicar usize::MAX bytes.

¿No me parece obvio que ! debería tener un tamaño infinito / usize::MAX en lugar de ser un ZST?

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

actualmente es 0.

La razón se explicó en el texto renderizado.

bool : dos 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 alguien que no está versado en la teoría relevante, veo que adherirme a la fórmula log(x)/log(y) persigue la elegancia de un modelo teórico en detrimento del uso práctico. (También conocido como ser demasiado inteligente para el propio bien)

Intuitivamente, parece que ! también debería tener un tamaño cero porque:

  • bool : necesita espacio para distinguir entre dos valores válidos => log (2) / log (2) = 1 bit además del sistema de tipos
  • () : necesita espacio para distinguir un valor válido => no se necesita espacio más allá del sistema de tipos
  • ! : necesita espacio para distinguir valores no válidos => no se necesita espacio más allá del sistema de tipos

Bueno, es infinito en realidad, lo que tiene sentido, ya que colocar el! como un campo en una estructura esencialmente convierte la estructura en! también (c + -inf = -inf). Entonces, dado que estamos tratando con usize, 0 es el valor más cercano que tenemos para representar eso. (Creo que la implementación real en rustc incluso usa -inf).

Intuitivamente, parece que ! también debería ser de tamaño cero

! no necesita un tamaño en absoluto, porque nunca se crearán valores con ese tipo. Es una pregunta irrelevante.

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

log (0) no está definido; no es infinito.

Por otro lado, tener un tamaño de usize::MAX es una forma tenichcal de imponer la inhabitancia. ¿De acuerdo?

@CryZe @varkor

Eso también encaja con el concepto del que estaba tratando de razonar la validez, donde mi intuición quiere ver ! como "ZST en un nivel completamente diferente". (es decir, ! es para el sistema de tipos como () es para la asignación de memoria).

@CryZe
Su fórmula -Inf + c == -Inf tiene sentido, pero cuando reemplaza -Inf con 0usize ya no se mantiene.

Por otro lado, si la aritemática tiene "capa": cualquier desbordamiento se calcula en usize::MAX entonces usize::MAX ajusta perfectamente a la fórmula.

El modelo del cerebro: si tiene un objeto de tipo ! , para asignar una estructura que lo contenga, necesita una estructura aún mayor que ! , pero el tamaño de estructura más grande que puede imaginar es usize::MAX . Entonces, el espacio que necesita sigue siendo usize::MAX .

¿Por qué no simplemente "sujetar" el tamaño de cualquier tipo que contenga un tipo vacío ( ! , o un enum Void {} definido por el usuario) a size=0,alignment=0 ? Esto me parece mucho más sensato semánticamente ...

@remexre
Porque esto no funciona. Ejemplo: sizeof(Result<usize,!>) == sizeof(usize) .

Bueno, Result<usize, !> no contiene ! directamente; sizeof(Result::Ok(usize)) == 8 , sizeof(Result::Err(!)) == 0

No, sizeof(Result::Err(!)) == usize::MAX en mi propuesta, porque Result::Err(!) :: Result<!,!> .

La regla debería ser: en enum s, el tamaño de ! se considera menor que todos los demás valores de tamaño, pero en structs , es el tamaño máximo que puede tener una imagen.

Conclusión:

  • ! no es DST. Los DST no son Sized no porque no tengan un tamaño, sino porque la información del tamaño está oculta. Pero por ! sabemos todo al respecto.
  • ! debe tener un tamaño y, por lo tanto, Box::new(x as !) debe poder compilar al menos.
  • struct s contiene ! debe tener el tamaño de ! , enum s contiene ! directamente en una variante debe tener el tamaño si esa variante no existe.

O consideramos sizeof(!)==0 o sizeof(!)==usize::MAX , tenemos que introducir algunas aritemáticas especiales para permitir lo anterior:

sizeof(!)==0 : en estructuras, 0 + n = 0 : preocupado :, en enumeraciones, max(0,n) = n : rubor :
sizeof(!)==usize::MAX : en estructuras, usize::MAX + n =usize::MAX : neutral_face :, en enumeraciones, max(usize::MAX,n) = n : flushed :

Sin embargo, hay un beneficio que favorece usize::MAX : evita técnicamente cualquier intento, incluso unsafe , de construir tal objeto, ya que no es posible en ningún sistema real.

Sin embargo, hay una ventaja que favorece a usize :: MAX: evita tecnológicamente cualquier intento, incluso los inseguros, de construir tal objeto, ya que no es posible en ningún sistema real.

Quiero decir, si se permiten unsafe travesuras: *transmute::<Box<usize>, Box<!>>(Box::new(0)) me consigue un ! independientemente de su tamaño. usize::MAX hace que sea más probable que el compilador falle si alguien intenta hacer std::mem::zeroed<!>() , supongo, pero yo pondría la responsabilidad en la persona que está escribiendo ese código inseguro, en lugar de las matemáticas rareza arriba.

Tenga en cuenta que ! tiene el tamaño _actualmente_ 0 - no MAX ni -INF -saturated-to-0 - es necesario para manejar la inicialización parcial, como fue que se encuentra en https://github.com/rust-lang/rust/issues/49298#issuecomment -380844923 (e implementado en https://github.com/rust-lang/rust/pull/50622).

Si desea cambiar cómo funciona esto, cree un nuevo número o relaciones públicas para eso; Este no es el lugar.

@remexre

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

Box es (principalmente) un tipo de biblioteca y su método new está definido en la sintaxis de Rust en src/liballoc/boxed.rs . Su descripción asume un operador <: que no existe en Rust. Esto se debe a que la subtipificación en Rust solo existe a través de parámetros de por vida. Si desea leer o ver más:

! se puede forzar en cualquier tipo, pero esto es más débil que ser cualquier tipo. (Por ejemplo, Vec<&'static Foo> también es Vec<&'a Foo> para cualquier 'a , pero no hay conversión implícita de Vec<!> a Vec<Foo> : http: / /play.rust-lang.org/?gist=82d1c1e1fc707d804a57c483a3e0198f&version=nightly&mode=debug&edition=2015)

Creo que la idea es que puedes hacer lo que creas apropiado en el modo unsafe , pero tienes que enfrentarte a la UB si no lo haces con prudencia. Si el tamaño oficial de ! es usize::MAX , esto debería alertar al usuario lo suficiente de que este tipo nunca debería ser instanciado.

Además, estaba pensando en la alineación. Si los tipos deshabitados tienen el tamaño usize::MAX , es natural establecer su alineación también usize::MAX . Esto limita efectivamente que un puntero válido sea el puntero nulo. Pero, por definición, un puntero nulo no es válido, por lo que ni siquiera tenemos un punto válido para este tipo, que es perfecto para este caso.

@ScottAbbey
Me estoy enfocando en el problema para asegurarme de que Box::new(x as !) no tenga problemas para que podamos continuar estabilizando esto. La forma propuesta es hacer que ! tenga un tamaño. Si ZST es el estándar, debería estar bien. No estoy discutiendo más. Simplemente impleméntelo de manera que sizeof(!)==0 sea ​​suficiente.

es natural establecer su alineación también usize :: MAX

usize::MAX no es una potencia de dos, por lo que no es una alineación válida. Además, LLVM no admite alineaciones de más de 1 << 29 . Y ! no debería tener una gran alineación de todos modos, porque let x: (!, i32); x.1 = 4; solo debería tomar 4 bytes de pila, no gigabytes o más.

Cree un nuevo hilo para esta discusión,

El problema que ocurre aquí es que Box::new(!) es un Box<$0> donde esperamos un Box<Error> . Con suficientes anotaciones de tipo, requeriríamos que Box<$0> sea ​​forzado a Box<Error> , lo que luego nos dejaría con $0 defecto a ! .

Sin embargo, $0 es una variable de tipo, por lo que no se realiza coerción y luego obtenemos $0 = Error .

Una opción engañosa para arreglar las cosas sería encontrar que tenemos una restricción $0: Sized (¿siguiendo los subtipos?) E insertar una coerción con clave en eso, de modo que Box todavía se llame en el tipo.

Ya hacemos algo como esto para detectar Fn en cierres, por lo que no será tan difícil. Aún feo.

Eso es todo, si durante la coerción nos encontramos con una obligación de la forma $0: Unsize<Y> donde Y definitivamente no tiene tamaño y $0 definitivamente tiene el tamaño, no lo trate como una ambigüedad sino más bien trátelo como un "ok" y proceda con la coerción sin tamaño.

@ arielb1

Entonces, ¿en qué se diferencia esto de la coerción a Box::new(()) a Box<Debug> ? std::error::Error es un rasgo como Debug , no un tipo como [u8] . std::io::Error por otro lado es un tipo, pero es Sized .

Nunca tenemos problemas con lo siguiente:

use std::fmt::Debug;

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

fn main() {
}

@earthengine, la diferencia entre este problema y su código es:

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

Esencialmente, debido a que ! puede coaccionar a cualquier cosa, se coacciona a Debug antes de la llamada a Box::new . Esa no es una opción con () , por lo que no hay problema en el segundo caso: la coerción ocurre después de Box::new .

@RalfJung

Mi modelo cerebral es que las DST no pueden existir en la naturaleza, tienen que provenir de algunos tipos más concretos. Esto es incluso cierto cuando permitimos DST en la posición del parámetro, si no en la posición de retorno. Dicho esto, hasta que el valor real ya se coloque detrás de un puntero, no debería poder coincidir con un DST, incluso si es ! .

Por ejemplo, lo siguiente no debería compilarse:

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

Simplemente porque no puede reemplazar ! con ninguna expresión de Rust existente para compilarlo.

Editar

Esa no es una opción con ()

Entonces, ¿alguien puede explicar por qué? Si () as dyn Debug no se compila, ! as dyn Debug no lo hará y viceversa. &() as &Debug compila, también &! as &Debug , sin problemas. Si () as dyn Debug puede compilarse algún día, el problema que tenemos hoy se repetirá por () , por lo que los implementadores de DST RFC tendrán que hacerlo, y así resolverá el mismo problema que tenemos. por ! .

! coacciona a cualquier cosa porque es imposible existir. "Ex falso quodlibet". Por lo tanto, todos sus ejemplos deben compilarse; no hay una buena razón teórica para hacer una excepción especial para los tipos sin tamaño.

Esto ni siquiera viola "DTS no puede existir" porque ! tampoco puede existir. :)

() as dyn Debug no se compila

No sé cuáles son los planes para rvalues ​​sin tamaño, pero supongo que podrían hacer esta compilación.
El punto es que hacer esto por ! no requiere que implementemos rvalues ​​sin tamaño porque esto solo puede suceder en código muerto.

Sin embargo, tal vez esté apuntando a una posible solución aquí (y tal vez eso es lo que @ arielb1 también sugirió anteriormente): ¿Es posible restringir la coerción ! para que solo se aplique si el tipo de destino es (se sabe que tiene) tamaño ? No hay una buena razón teórica para hacer esto, sino una práctica. : D Es decir, que podría ayudar a resolver este problema.

Cuando dije "DTS no puede existir" me refiero en sintáctico. No es posible tener una variable local que se escriba str , por ejemplo.

Por otro lado, ! no puede existir es semántico. Puedes escribir

let v = exit(0);

tener v tecleado sintácticamente ! en el contexto, pero debido a que el enlace ni siquiera se ejecutará y, por lo tanto, no sale en el mundo real.

Así que aquí está la razón: permitimos ! coaccionar a cualquier tipo, solo cuando puede escribir una expresión que tenga el mismo tipo. Si un tipo ni siquiera puede existir sintácticamente, no debería permitirse.

Esto también se aplica a valores r sin tamaño, por lo que antes de tener rvalores sin tamaño, no se permite coaccionar ! a tipos sin tamaño, hasta que sepamos cómo manejar los valores r sin tamaño. Y solo en ese punto podemos comenzar a pensar en este problema (una solución obvia parece ser permitir que Box::new reciban valores sin tamaño).

No es posible tener una variable local que se escriba como str, por ejemplo.

Eso es solo una limitación de la implementación actual: https://github.com/rust-lang/rust/issues/48055

@RalfJung

Ese no es el problema aquí.

Nuevamente, suponga que estamos en la situación en la que Box::new(!: $0): Box<dyn fmt::Debug> donde $0 es una variable de tipo.

Entonces, el compilador puede deducir con bastante facilidad que $0: Sized (porque $0 es igual al parámetro de tipo de Box::new , que tiene un límite de T: Sized ).

El problema surge cuando el compilador necesita averiguar qué tipo de coerción usar para transformar Box<$0> en Box<dyn fmt::Debug> . Desde un punto de vista "local", existen 2 soluciones:

  1. Tener una coerción CoerceUnsized , requiriendo Box<$0>: CoerceUnsized<Box<dyn fmt::Debug>> . Este es un programa válido por $0 = ! y, por defecto, se compilará para eso.
  2. Tener una coacción de identidad, con $0 = dyn fmt::Debug . Esto es incompatible con el requisito de $0: Sized .

El compilador no quiere tener demasiadas cosas abiertas al considerar ambigüedades, ya que eso puede causar problemas de rendimiento y problemas difíciles de depurar, por lo que elige qué coerción usar de una manera bastante estúpida (en particular, no no tiene T: CoerceUnsized<T> , por lo que si el compilador elige la opción 1 puede "atascarse" con bastante facilidad), y termina eligiendo la opción 2, que falla. Tuve una idea para hacerlo un poco más inteligente y elegir la opción 1.

Entonces, pensando en esto desde este punto de vista, la idea correcta podría ser desencadenar una coerción sin tamaño si

  1. La coerción sin tamaño es autoconsistente (esas serían las reglas actuales, excepto que la ambigüedad está bien).
  2. no desencadenar la coerción sin tamaño es inconsistente. Tenemos que ver que esto se puede calcular sin arruinar el rendimiento en funciones más grandes.

Tener una coerción de identidad, con $ 0 = dyn fmt :: Debug. Esto es incompatible con el requisito de $ 0: tamaño.

Apenas puedo ver por qué deberíamos permitir let v: str=! pero no let v: str; . Si ni siquiera puede declarar una variable con un tipo específico, ¿por qué puede vincularle un valor (tal vez imposible)?

Cuando no se envía un rvalue sin tamaño, la forma consistente es no permitir ninguna coerción a los tipos sin tamaño, ya que esto creará (tal vez temporalmente) rvalue sin tamaño, incluso cuando esto suceda solo en la imaginación.

Entonces, mi conclusión es que el problema Box::new(!) as Box<Error> es un problema de bloqueo para valores r sin tamaño, no para el tipo ! . Las reglas de coerción ! deben ser:

Puede escribir let v: T , si y solo si puede escribir let v: T = ! .

El significado real luego se evaluará con el lenguaje. Especialmente, después de tener valores r sin tamaño, tenemos Box::new(! as dyn Debug) pero antes de eso, diría que no.

Entonces, ¿qué podemos hacer para seguir adelante?

Agregue una puerta de función en el código que desencadena la coerción sin tamaño, si rvalue sin tamaño está activado, pruebe esta coerción; de lo contrario, omítala. Si esta es la única coerción disponible, el mensaje de error debería ser "el rasgo vinculado str: std::marker::Sized no está satisfecho", como en casos similares. Entonces

Tenemos que ver que esto se puede calcular sin arruinar el rendimiento en funciones más grandes.

se aborda: solo una simple verificación de características.

Mientras tanto, agregue un nuevo problema para rvalues ​​sin tamaño y agregue el enlace al problema de seguimiento, para asegurarse de que este problema se resuelva correctamente.

Interesante.

Esta

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

compila, pero

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

no. Esto ni siquiera está relacionado con el tipo ! . ¿Debo crear un problema para esto?

No sé si ese último es realmente un error. El segundo ejemplo no se compila porque, sin soporte rvalue sin tamaño, el compilador quiere saber estáticamente cuánto espacio de pila asignar para la variable local v pero no puede porque es un DST.

En el primer ejemplo, el patrón _ es especial porque no solo coincide con algo (como un enlace de variable local), sino que ni siquiera crea una variable en absoluto. Entonces ese código es el mismo que fn main() { *""; } sin let . Desreferenciar una referencia (incluso DST) y luego no hacer nada con el resultado no es útil, pero parece ser válido y no estoy convencido de que deba ser inválido.

Derecha. Pero es realmente confuso, especialmente lo siguiente

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

tampoco compila. Con su teoría sobre _ esto debería ser lo mismo, excepto que llamamos a la cosa no utilizada _v lugar de solo _ .

Por lo que recuerdo, la única diferencia entre _v y v es que el subrayado inicial suprime las advertencias sobre valores no utilizados.

Por otro lado, _ significa específicamente "descartarlo" y se maneja especialmente para permitir que aparezca en más de un lugar en un patrón (por ejemplo, desempaquetar tupla, lista de argumentos, etc.) sin causar un error sobre una colisión de nombres.

Por otro lado, _ significa específicamente "descartarlo" y se maneja especialmente para permitir que aparezca en más de un lugar en un patrón (por ejemplo, desempaquetar tupla, lista de argumentos, etc.) sin causar un error sobre un nombre colisión.

Correcto. Además de lo que dijiste, let _ = foo() lleva a que drop se llame inmediatamente, mientras que _v solo se elimina cuando sale del alcance (como v ).

De hecho, esto es lo que quise decir con " _ es especial".

Así que ahora parece que negar todos los valores r sin tamaño (a menos que la función "valores r sin tamaño" esté activada) en la naturaleza sería un cambio rotundo, aunque creo que el efecto sería pequeño.

Entonces todavía sugiero tener una puerta de función para no permitir la coersión de ! a valores r sin tamaño. Esto negaría un código como let _:str = return; , pero no creo que nadie lo use en el código.

Siempre que el tipo ! sea ​​inestable, podemos realizar cambios importantes en los lugares donde puede o no ser coaccionado.

La pregunta es, si lo estabilizamos sin coerción a los DST para arreglar https://github.com/rust-lang/rust/issues/49593 , ¿querremos restaurar dicha coerción más adelante cuando agreguemos rvalues ​​sin tamaño? ser capaz de hacerlo sin romper https://github.com/rust-lang/rust/issues/49593 de nuevo?

Mi propuesta anterior incluye esto. # 49593 debería ser un problema de bloqueo para rvalues ​​sin tamaño.

Mi sugerencia personal para la solución final es permitir que Box::new (también puede incluir algunos otros métodos importantes) acepte parámetros sin tamaño y permita ambigüedades en situaciones similares.

FWIW, _ no se parece en nada a un nombre de variable especial. Es una sintaxis de patrón completamente separada que no afecta al lugar que se está haciendo coincidir. La coincidencia de patrones funciona en lugares , no en valores .
Lo más cercano a un enlace variable es ref _x , pero incluso entonces probablemente necesite NLL para evitar un préstamo conflictivo resultante. Esto funciona por cierto:

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

No entendiste mi punto. Mi suposición es que en Rust actualmente no hay valores r sin tamaño que puedan aparecer en el código. Sin embargo, resulta que aparecen en let _:str = *"" . Aunque tal valor puede vivir por poco tiempo, a nivel sintético vive. Algo como un fantasma ...

En cambio, sus ejemplos son técnicamente totalmente legales y no es el punto. str tiene tamaño, pero &str tiene tamaño.

Sin embargo, resulta que aparecen en let _: str = * "". Aunque tal valor puede vivir por poco tiempo, a nivel sintético vive. Algo como un fantasma ...

Hay un "fantasma" similar en una expresión como &*foo . Primero desreferencia foo y luego toma la dirección del resultado sin moverlo . Entonces no es lo mismo que & { *foo } , por ejemplo.

@earthengine @SimonSapin No existen valores r sin tamaño ("expresión de valor") allí. Sólo lvalues ​​("expresiones de lugar") lo hacen. *"foo" es un lugar , y la coincidencia de patrones no requiere un valor, solo un lugar.

Los nombres "lvalue" y "rvalue" son reliquias y algo engañoso también en C, pero aún peor en Rust, porque el RHS de let es un "lvalue" (expresión de lugar), a pesar del nombre.
(Las expresiones de asignación son el único lugar donde los nombres y las reglas de C tienen sentido en Rust)

En sus ejemplos, let x = *"foo"; , se requiere el valor para vincularlo a x .
De manera similar, &*"foo" crea una expresión de lugar y luego la toma prestada, sin necesidad de crear un valor sin tamaño, mientras que {*"foo"} es siempre una expresión de valor y, por lo tanto, no permite tipos sin tamaño.

En https://github.com/rust-lang/rust/pull/52420 le di a eso

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

ya no funciona. ¿Es eso intencional?

AFAIK, sí, la historia del patrón infalible es complicada y tiene una puerta de función separada de ! . Consulte también https://github.com/rust-lang/rfcs/pull/1872 y https://github.com/rust-lang/rust/issues/48950.

@ Ericson2314 específicamente, la función que necesitaría agregar a liballoc es exhaustive_patterns .

¡¡Gracias!!

Una conversación interesante sobre los aspectos internos:

Si vas a hacer eso, ¿por qué no regalarte? sí mismo como una variable de inferencia?

Simplemente haga un envoltorio alrededor de ! con PhandomData para eliminar la ambigüedad del tipo de elemento.

https://github.com/rust-lang/rust/issues/49593 ahora se ha corregido. Esta fue la razón de la reversión anterior de la estabilización. El informe de estabilización anterior está aquí . ¡Intentemos esto de nuevo!

@rfcbot fcp fusionar

Creo que rfcbot podría admitir más de un FCP en el mismo problema. ¿Abramos un nuevo tema para esta ronda de estabilización?

https://github.com/rust-lang/rust/pull/50121 no solo revirtió la estabilización sino también la semántica de respaldo. ¿Es esto algo que queremos volver a visitar?

La casilla de verificación que queda sin marcar en la descripción del problema es:

¿Qué características deberíamos implementar para ! ? El PR # 35162 inicial incluye Ord y algunos otros. Esto probablemente sea más un problema de T-libs, por lo que agregaré esa etiqueta al problema.

Podemos agregar impls más tarde, ¿no? ¿O es esto un bloqueador?

@SimonSapin Abrió https://github.com/rust-lang/rust/issues/57012. Esperaría que el retroceso a ! se habilitara nuevamente como parte de este cambio, sí (aunque hablemos de eso en el tema de la estabilización).

Aparentemente, hay un error / agujero en tener el tipo nunca detrás de una puerta de función, y es posible consultarlo en Stable: https://github.com/rust-lang/rust/issues/33417#issuecomment -467053097

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

Agregando un uso del tipo en el ejemplo de código vinculado arriba:

trait MyTrait {
    type Output;
}

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

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

fn main() {
    let _a: Void;
}

Esto se compila en Rust 1.12.0, que creo que es el primero con https://github.com/rust-lang/rust/pull/35162. En 1.11.0, se produce un error con:

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

¿Cuál es el estado de éste?

Hasta donde yo sé, el estado no ha cambiado significativamente desde mi resumen en https://github.com/rust-lang/rust/issues/57012#issuecomment -467889706.

¿Cuál es el estado de éste?

@hosunrise : actualmente está bloqueado en https://github.com/rust-lang/rust/issues/67225

Puede que esté muy lejos de aquí, pero los problemas específicos que menciona la discusión de lint (https://github.com/rust-lang/rust/issues/66173) parecen resolubles si cada enumeración tiene una rama ! en el sistema de tipos?

Notaré que no se aplica al problema mencionado en el OP de https://github.com/rust-lang/rust/issues/67225 , que aún sería un problema.

Puede que esté muy lejos de aquí, pero los problemas específicos que menciona la discusión de lint (# 66173) parecen resolubles si cada enumeración tiene una rama ! en el sistema de tipos.

En realidad, cada enumeración puede ser amenazada, ya que había un número infinito de variantes que nunca pueden ocurrir y, por lo tanto, no vale la pena mencionarlas.

¿Fue útil esta página
0 / 5 - 0 calificaciones