¿Cómo imprimo el tipo de una variable en Rust?

240

Tengo lo siguiente:

let mut my_number = 32.90;

¿Cómo imprimo el tipo de my_number?

Usando typey type_ofno funcionó. ¿Hay alguna otra forma en que pueda imprimir el tipo de número?

usuario2431012
fuente

Respuestas:

177

Si simplemente desea averiguar el tipo de una variable y está dispuesto a hacerlo en el momento de la compilación, puede causar un error y hacer que el compilador lo recoja.

Por ejemplo, establezca la variable en un tipo que no funciona :

let mut my_number: () = 32.90;
// let () = x; would work too
error[E0308]: mismatched types
 --> src/main.rs:2:29
  |
2 |     let mut my_number: () = 32.90;
  |                             ^^^^^ expected (), found floating-point number
  |
  = note: expected type `()`
             found type `{float}`

O llame a un método no válido :

let mut my_number = 32.90;
my_number.what_is_this();
error[E0599]: no method named `what_is_this` found for type `{float}` in the current scope
 --> src/main.rs:3:15
  |
3 |     my_number.what_is_this();
  |               ^^^^^^^^^^^^

O acceda a un campo no válido :

let mut my_number = 32.90;
my_number.what_is_this
error[E0610]: `{float}` is a primitive type and therefore doesn't have fields
 --> src/main.rs:3:15
  |
3 |     my_number.what_is_this
  |               ^^^^^^^^^^^^

Estos revelan el tipo, que en este caso en realidad no está completamente resuelto. Se llama "variable de punto flotante" en el primer ejemplo y " {float}" en los tres ejemplos; Este es un tipo parcialmente resuelto que podría terminar f32o f64, dependiendo de cómo lo use. " {float}" No es un nombre de tipo legal, es un marcador de posición que significa "No estoy completamente seguro de qué es", pero es un número de coma flotante. En el caso de las variables de punto flotante, si no lo restringe, el valor predeterminado será f64¹. (Un literal entero no calificado estará predeterminado en i32).

Ver también:


¹ Todavía puede haber formas de desconcertar al compilador para que no pueda decidir entre f32y f64; No estoy seguro. Solía ​​ser tan simple como 32.90.eq(&32.90)eso, pero eso trata tanto como f64ahora y suena feliz, así que no lo sé.

Chris Morgan
fuente
44
:?ha sido implementado manualmente durante bastante tiempo. Pero lo más importante, la std::fmt::Debugimplementación (para eso es lo que :?usa) para los tipos de números ya no incluye un sufijo para indicar de qué tipo es.
Chris Morgan
2
Utilizo mucho estas técnicas para tratar de encontrar el tipo de una expresión, pero no siempre funciona, especialmente cuando hay parámetros de tipo involucrados. El compilador, por ejemplo, me dirá que espera algo ImageBuffer<_, Vec<_>>que no me ayuda mucho cuando estoy tratando de escribir una función que tome una de estas cosas como parámetro. Y esto sucede en el código que de lo contrario se compila hasta que agregue el :(). ¿No hay mejor manera?
Christopher Armstrong
2
Esto parece ser un poco complicado y poco intuitivo. ¿Sería muy difícil para el editor de código, por ejemplo, Emacs proporcionar el tipo cuando el cursor descansa sobre la variable, como en muchos otros idiomas? Si el compilador puede decir el tipo en caso de error, ¿seguramente también debería saber el tipo cuando no hay ningún error?
xji
1
@JIXiang: el Rust Language Server se trata de proporcionar esta información a un IDE, pero aún no está maduro: su primer lanzamiento alfa fue solo hace un par de días. Sí, este es un enfoque eldritch; sí, constantemente se están presentando formas menos esotéricas de lograr el objetivo.
Chris Morgan
1
Esto suena muy parecido a un truco. ¿Es esta realmente la forma idiomática de verificar el tipo de una variable?
confundido00
109

Hay una función inestable std::intrinsics::type_nameque puede obtener el nombre de un tipo, aunque debe usar una compilación nocturna de Rust (es poco probable que funcione en Rust estable). Aquí hay un ejemplo:

#![feature(core_intrinsics)]

fn print_type_of<T>(_: &T) {
    println!("{}", unsafe { std::intrinsics::type_name::<T>() });
}

fn main() {
    print_type_of(&32.90);          // prints "f64"
    print_type_of(&vec![1, 2, 4]);  // prints "std::vec::Vec<i32>"
    print_type_of(&"foo");          // prints "&str"
}
Shubham Jain
fuente
@vbo: no hasta que se estabilice. Es poco probable que algo como esto se estabilice durante bastante tiempo, si es que lo hace, y no me sorprendería si nunca se estabiliza; no es el tipo de cosas que realmente deberías hacer.
Chris Morgan
2
En Rust-nightly (1.3) solo funcionaba cuando se cambiaba la primera línea a#![feature(core_intrinsics)]
AT
1
@DmitriNesteruk: print_type_ofestá tomando referencias ( &T), no valores ( T), por lo que debe pasar en &&strlugar de &str; es decir, en print_type_of(&"foo")lugar de print_type_of("foo").
Chris Morgan
66
std::any::type_namees estable desde el óxido 1.38: stackoverflow.com/a/58119924
Tim Robinson
1
Obtener el tipo de algo en compilación / tiempo de ejecución tiene casos de uso válidos. Para la serialización, por ejemplo, o simplemente para fines de depuración. Aquellos que escriben "Nunca deberías hacer tal cosa" simplemente nunca se encontraron con esos casos de uso.
BitTickler
68

Puedes usar la std::any::type_namefunción. Esto no necesita un compilador nocturno o una caja externa, y los resultados son bastante correctos:

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}

fn main() {
    let s = "Hello";
    let i = 42;

    print_type_of(&s); // &str
    print_type_of(&i); // i32
    print_type_of(&main); // playground::main
    print_type_of(&print_type_of::<i32>); // playground::print_type_of<i32>
    print_type_of(&{ || "Hi!" }); // playground::main::{{closure}}
}

Tenga cuidado: como se dice en la documentación, esta información debe usarse solo para fines de depuración:

Esto está destinado para uso diagnóstico. El contenido exacto y el formato de la cadena no se especifican, aparte de ser una descripción del mejor esfuerzo del tipo.

Si desea que su representación de tipo permanezca igual entre las versiones del compilador, debe usar un rasgo, como en la respuesta del phicr .

Boiethios
fuente
1
la mejor respuesta para mí, ya que la mayoría de los desarrolladores quieren usar esto para fines de depuración, como imprimir fallas de análisis
kaiser
¡Exactamente lo que necesitaba, no sé por qué esta no es la respuesta marcada!
James Poulose
1
@JamesPoulose Debido a que esta función es reciente, mi respuesta es más nueva.
Boiethios
53

Si conoce todos los tipos de antemano, puede usar rasgos para agregar un type_ofmétodo:

trait TypeInfo {
    fn type_of(&self) -> &'static str;
}

impl TypeInfo for i32 {
    fn type_of(&self) -> &'static str {
        "i32"
    }
}

impl TypeInfo for i64 {
    fn type_of(&self) -> &'static str {
        "i64"
    }
}

//...

Sin intrínsecos ni nada, por lo que, aunque más limitada, esta es la única solución aquí que te da una cadena y es estable. (vea la respuesta de French Boiethios ) Sin embargo, es muy laborioso y no tiene en cuenta los parámetros de tipo, por lo que podríamos ...

trait TypeInfo {
    fn type_name() -> String;
    fn type_of(&self) -> String;
}

macro_rules! impl_type_info {
    ($($name:ident$(<$($T:ident),+>)*),*) => {
        $(impl_type_info_single!($name$(<$($T),*>)*);)*
    };
}

macro_rules! mut_if {
    ($name:ident = $value:expr, $($any:expr)+) => (let mut $name = $value;);
    ($name:ident = $value:expr,) => (let $name = $value;);
}

macro_rules! impl_type_info_single {
    ($name:ident$(<$($T:ident),+>)*) => {
        impl$(<$($T: TypeInfo),*>)* TypeInfo for $name$(<$($T),*>)* {
            fn type_name() -> String {
                mut_if!(res = String::from(stringify!($name)), $($($T)*)*);
                $(
                    res.push('<');
                    $(
                        res.push_str(&$T::type_name());
                        res.push(',');
                    )*
                    res.pop();
                    res.push('>');
                )*
                res
            }
            fn type_of(&self) -> String {
                $name$(::<$($T),*>)*::type_name()
            }
        }
    }
}

impl<'a, T: TypeInfo + ?Sized> TypeInfo for &'a T {
    fn type_name() -> String {
        let mut res = String::from("&");
        res.push_str(&T::type_name());
        res
    }
    fn type_of(&self) -> String {
        <&T>::type_name()
    }
}

impl<'a, T: TypeInfo + ?Sized> TypeInfo for &'a mut T {
    fn type_name() -> String {
        let mut res = String::from("&mut ");
        res.push_str(&T::type_name());
        res
    }
    fn type_of(&self) -> String {
        <&mut T>::type_name()
    }
}

macro_rules! type_of {
    ($x:expr) => { (&$x).type_of() };
}

Vamos a usarlo:

impl_type_info!(i32, i64, f32, f64, str, String, Vec<T>, Result<T,S>)

fn main() {
    println!("{}", type_of!(1));
    println!("{}", type_of!(&1));
    println!("{}", type_of!(&&1));
    println!("{}", type_of!(&mut 1));
    println!("{}", type_of!(&&mut 1));
    println!("{}", type_of!(&mut &1));
    println!("{}", type_of!(1.0));
    println!("{}", type_of!("abc"));
    println!("{}", type_of!(&"abc"));
    println!("{}", type_of!(String::from("abc")));
    println!("{}", type_of!(vec![1,2,3]));

    println!("{}", <Result<String,i64>>::type_name());
    println!("{}", <&i32>::type_name());
    println!("{}", <&str>::type_name());
}

salida:

i32
&i32
&&i32
&mut i32
&&mut i32
&mut &i32
f64
&str
&&str
String
Vec<i32>
Result<String,i64>
&i32
&str

Patio de óxido

phicr
fuente
Esta respuesta podría dividirse en dos respuestas separadas para evitar mezclar las dos.
Prajwal Dhatwalia
2
@PrajwalDhatwalia He estado pensando en lo que dijiste y siento que estoy satisfecho con la forma en que las versiones se complementan entre sí. La versión de rasgo muestra una simplificación de lo que la versión macro está haciendo bajo el capó, lo que hace que sus objetivos sean más claros. La versión macro, por otro lado, muestra cómo hacer que la versión del rasgo sea más generalmente utilizable; No es la única forma de hacerlo, pero incluso demostrar que es posible es ventajoso. En resumen, esto podría ser dos respuestas, pero creo que el todo es mayor que la suma de sus partes.
phicr
19

UPD Lo siguiente ya no funciona. Verifique la respuesta de Shubham para su corrección.

Echa un vistazo std::intrinsics::get_tydesc<T>(). Está en estado "experimental" en este momento, pero está bien si solo está pirateando el sistema de tipos.

Mira el siguiente ejemplo:

fn print_type_of<T>(_: &T) -> () {
    let type_name =
        unsafe {
            (*std::intrinsics::get_tydesc::<T>()).name
        };
    println!("{}", type_name);
}

fn main() -> () {
    let mut my_number = 32.90;
    print_type_of(&my_number);       // prints "f64"
    print_type_of(&(vec!(1, 2, 4))); // prints "collections::vec::Vec<int>"
}

Esto es lo que se usa internamente para implementar el famoso {:?}formateador.

vbo
fuente
15

** ACTUALIZACIÓN ** Esto no se ha verificado que funcione recientemente.

Junté una pequeña caja para hacer esto basada en la respuesta de vbo. Le da una macro para devolver o imprimir el tipo.

Ponga esto en su archivo Cargo.toml:

[dependencies]
t_bang = "0.1.2"

Entonces puedes usarlo así:

#[macro_use] extern crate t_bang;
use t_bang::*;

fn main() {
  let x = 5;
  let x_type = t!(x);
  println!("{:?}", x_type);  // prints out: "i32"
  pt!(x);                    // prints out: "i32"
  pt!(5);                    // prints out: "i32"
}
mpiccolo
fuente
@vbo dice que su solución ya no funciona. ¿El tuyo funciona?
Antony Hatchkins
no funciona `error [E0554]: #![feature]no se puede utilizar en el canal de liberación estable`
Muhammed Moussa
7

También puede usar el enfoque simple de usar la variable en println!("{:?}", var). Si Debugno se implementa para el tipo, puede ver el tipo en el mensaje de error del compilador:

mod some {
    pub struct SomeType;
}

fn main() {
    let unknown_var = some::SomeType;
    println!("{:?}", unknown_var);
}

( parque infantil )

Está sucio pero funciona.

DenisKolodin
fuente
8
Si Debugno se implementa , este es un caso bastante poco probable. Una de las primeras cosas que debe hacer para la mayoría de las estructuras es agregar #[derive(Debug)]. Creo que los tiempos en los que no quieres Debugson muy pequeños.
Shepmaster
1
¿Puedes explicar qué está pasando println!("{:?}", unknown_var);? ¿Es una interpolación de cuerdas pero por qué :?dentro de las llaves? @DenisKolodin
Julio Marins
Provoco error. La idea de dejar que el compilador proporcione información de tipo con error. Lo usé Debugporque no está implementado, pero también puedes usarlo {}.
DenisKolodin
4

Hay una respuesta @ChrisMorgan para obtener un tipo aproximado ("flotación") en óxido estable y hay una respuesta @ShubhamJain para obtener un tipo preciso ("f64") a través de una función inestable en óxido nocturno.

Ahora aquí hay una forma en que uno puede obtener el tipo preciso (es decir, decidir entre f32 y f64) en óxido estable:

fn main() {
    let a = 5.;
    let _: () = unsafe { std::mem::transmute(a) };
}

resultados en

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
 --> main.rs:3:27
  |
3 |     let _: () = unsafe { std::mem::transmute(a) };
  |                           ^^^^^^^^^^^^^^^^^^^
  |
  = note: source type: `f64` (64 bits)
  = note: target type: `()` (0 bits)

Actualizar

La variación del turbocebo

fn main() {
    let a = 5.;
    unsafe { std::mem::transmute::<_, ()>(a) }
}

es ligeramente más corto pero algo menos legible.

Antony Hatchkins
fuente
Si ya sabe que es float, decir entre f32y f64se puede lograr constd::mem::size_of_val(&a)
Antony Hatchkins
1

Algunas otras respuestas no funcionan, pero me parece que el nombre de tipo cajón funciona.

  1. Crea un nuevo proyecto:

    cargo new test_typename
  2. Modificar el Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. Modifica tu código fuente

    use typename::TypeName;
    
    fn main() {
        assert_eq!(String::type_name(), "std::string::String");
        assert_eq!(Vec::<i32>::type_name(), "std::vec::Vec<i32>");
        assert_eq!([0, 1, 2].type_name_of(), "[i32; 3]");
    
        let a = 65u8;
        let b = b'A';
        let c = 65;
        let d = 65i8;
        let e = 65i32;
        let f = 65u32;
    
        let arr = [1,2,3,4,5];
        let first = arr[0];
    
        println!("type of a 65u8  {} is {}", a, a.type_name_of());
        println!("type of b b'A'  {} is {}", b, b.type_name_of());
        println!("type of c 65    {} is {}", c, c.type_name_of());
        println!("type of d 65i8  {} is {}", d, d.type_name_of());
        println!("type of e 65i32 {} is {}", e, e.type_name_of());
        println!("type of f 65u32 {} is {}", f, f.type_name_of());
    
        println!("type of arr {:?} is {}", arr, arr.type_name_of());
        println!("type of first {} is {}", first, first.type_name_of());
    }

El resultado es:

type of a 65u8  65 is u8
type of b b'A'  65 is u8
type of c 65    65 is i32
type of d 65i8  65 is i8
type of e 65i32 65 is i32
type of f 65u32 65 is u32
type of arr [1, 2, 3, 4, 5] is [i32; 5]
type of first 1 is i32
Flyq
fuente
He seguido los pasos que describiste. A partir de hoy, typenameno funciona con variables sin tipo explícito en la declaración. Ejecutarlo my_number desde la pregunta da el siguiente error "no se puede llamar al método type_name_ofen tipo numérico ambiguo {float}. Ayuda: debe especificar un tipo para este enlace, como f32"
Antony Hatchkins
Me prueba 0.65y funciona bien: type of c 0.65 0.65 is f64. Aquí está mi versión:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq
1

Si solo quiere saber el tipo de su variable durante el desarrollo interactivo, le recomiendo usar rls (servidor de lenguaje de óxido) dentro de su editor o ide. Luego, puede habilitar o alternar permanentemente la capacidad de desplazamiento y simplemente colocar el cursor sobre la variable. Debería aparecer un pequeño cuadro de diálogo con información sobre la variable, incluido el tipo.

nrdxp
fuente