¿Lanzar una referencia de función que produce un puntero no válido?

9

Estoy rastreando un error en el código de un tercero y lo reduje a algo similar.

use libc::c_void;

pub unsafe fn foo() {}

fn main() {
    let ptr = &foo as *const _ as *const c_void;
    println!("{:x}", ptr as usize);
}

Ejecutado en estable 1.38.0 esto imprime el puntero de función, pero beta (1.39.0-beta.6) y nocturno devuelven '1'. ( Área de juegos )

¿A qué se _infiere y por qué ha cambiado el comportamiento?

Supongo que la forma correcta de emitir esto sería simplemente foo as *const c_void, pero este no es mi código.

Maciej Goszczycki
fuente
No puedo responder el "por qué ha cambiado", pero estoy de acuerdo con usted en que el código es incorrecto para empezar. fooya es un puntero de función, por lo que no debe tomar una dirección. Eso crea una doble referencia, aparentemente a un tipo de tamaño cero (por lo tanto, el valor mágico 1).
Shepmaster
Esto no responde exactamente a su pregunta, pero probablemente quiera:let ptr = foo as *const fn() as *const c_void;
Peter Hall

Respuestas:

3

Esta respuesta se basa en las respuestas en el informe de error motivado por esta pregunta .

Cada función en Rust tiene su tipo de elemento de función individual , que es distinto del tipo de elemento de función de cualquier otra función. Por esta razón, una instancia del tipo de elemento de función no necesita almacenar ninguna información; la función a la que apunta está clara a partir de su tipo. Entonces la variable x en

let x = foo;

es una variable de tamaño 0.

Los tipos de elementos de función obligan implícitamente a los tipos de puntero de función cuando sea necesario. La variable

let x: fn() = foo;

es un puntero genérico a cualquier función con firma fn()y, por lo tanto, necesita almacenar un puntero a la función a la que apunta realmente, por lo que el tamaño de xes el tamaño de un puntero.

Si toma la dirección de una función, en &foorealidad está tomando la dirección de un valor temporal de tamaño cero. Antes de comprometerse con el rustrepositorio , los temporales de tamaño cero solían crear una asignación en la pila y &foodevolvían la dirección de esa asignación. Desde esta confirmación, los tipos de tamaño cero ya no crean asignaciones, y en su lugar usan la dirección mágica 1. Esto explica la diferencia entre las diferentes versiones de Rust.

Sven Marnach
fuente
Esto tiene sentido, pero no estoy convencido de que sea un comportamiento generalmente deseado porque se basa en una suposición precaria. Dentro del código Rust seguro, no hay razón para distinguir los punteros a un valor de ZST, porque solo hay un posible valor que se conoce en el momento de la compilación. Esto se rompe una vez que necesita usar un valor ZST fuera del sistema de tipo Rust, como aquí. Es probable que solo afecte los fntipos de elementos y los cierres que no capturan, y para aquellos hay una solución alternativa, como en mi respuesta, ¡pero sigue siendo una pistola de pie!
Peter Hall
Ok, no había leído las respuestas más recientes sobre el tema de Github. Podría obtener una segfault con ese código pero, si el código puede causar una segfault, entonces supongo que el nuevo comportamiento está bien.
Peter Hall
Gran respuesta. @PeterHall Estaba pensando lo mismo, y todavía no estoy al 100% en el tema, pero al menos para los temporales y otras variables de pila, no debería haber ningún problema al poner todos los valores de tamaño cero en 0x1 porque el compilador no hace garantiza el diseño de la pila, y de todos modos no puede garantizar la unicidad de los punteros a las ZST. Esto es diferente de, por ejemplo, emitir un papel *const i32al *const c_voidque, según tengo entendido, todavía se garantiza que preservará la identidad del puntero.
trentcl
2

¿A qué se _infiere y por qué ha cambiado el comportamiento?

Cada vez que realiza una conversión de puntero sin formato, solo puede cambiar una pieza de información (referencia o puntero sin formato; mutabilidad; tipo). Por lo tanto, si haces este reparto:

let ptr = &foo as *const _

Como ha cambiado de una referencia a un puntero sin formato, el tipo inferido para no _ debe modificarse y, por lo tanto, es el tipo de foo, que es un tipo inexpresable para la función foo.

En lugar de hacerlo, puede convertir directamente a un puntero de función, que se puede expresar en la sintaxis de Rust:

let ptr = foo as *const fn() as *const c_void;

En cuanto a por qué ha cambiado, eso es difícil de decir. Podría ser un error en la construcción nocturna. Vale la pena informarlo , incluso si no es un error, es probable que obtenga una buena explicación del equipo del compilador sobre lo que realmente está sucediendo.

Peter Hall
fuente
1
Gracias, lo he informado github.com/rust-lang/rust/issues/65499
Maciej Goszczycki
@MaciejGoszczycki ¡Gracias por informar! Las respuestas realmente aclararon las cosas para mí: publicaré una respuesta basada en las respuestas allí.
Sven Marnach