Come si stampa il tipo di una variabile in Rust?


240

Ho il seguente:

let mut my_number = 32.90;

Come si stampa il tipo di my_number?

Utilizzo typee type_ofnon ha funzionato. C'è un altro modo in cui posso stampare il tipo di numero?

Risposte:


177

Se desideri semplicemente scoprire il tipo di una variabile e sei disposto a farlo al momento della compilazione, puoi causare un errore e far sì che il compilatore lo raccolga.

Ad esempio, imposta la variabile su un tipo che non funziona :

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

Oppure chiama un metodo non valido :

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 accedi a un campo non valido :

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

Questi rivelano il tipo, che in questo caso in realtà non è stato completamente risolto. Si chiama "variabile in virgola mobile" nel primo esempio e " {float}" in tutti e tre gli esempi; questo è un tipo parzialmente risolto che potrebbe finire f32o f64, a seconda di come lo usi. “ {float}” Non è un nome di tipo legale, è un segnaposto che significa “Non sono del tutto sicuro di cosa si tratti”, ma è un numero in virgola mobile. Nel caso di variabili a virgola mobile, se non lo si vincola, l'impostazione predefinita è f64¹. (Un valore intero intero non qualificato verrà automaticamente impostato su i32.)

Guarda anche:


¹ Potrebbero esserci ancora modi per confondere il compilatore in modo che non possa decidere tra f32e f64; Non ne sono sicuro. In passato era semplice 32.90.eq(&32.90), ma questo tratta sia come f64ora che in allegria, quindi non lo so.


4
:?ormai da molto tempo è stato implementato manualmente. Ma soprattutto, l' std::fmt::Debugimplementazione (per questo è ciò che :?utilizza) per i tipi di numero non include più un suffisso per indicare di quale tipo è.
Chris Morgan,

2
Uso queste tecniche molto per cercare di trovare il tipo di un'espressione, ma non sempre funziona, specialmente quando sono coinvolti parametri di tipo. Il compilatore, ad esempio, mi dirà che si aspetta qualcosa ImageBuffer<_, Vec<_>>che non mi aiuta molto quando sto provando a scrivere una funzione che accetta una di queste cose come parametro. E questo accade nel codice che altrimenti viene compilato fino a quando non aggiungo il file :(). Non c'è modo migliore?
Christopher Armstrong,

2
Questo sembra essere un po 'contorto e poco intuitivo. Sarebbe molto difficile per l'editor di codice, ad esempio Emacs fornire il tipo quando il cursore poggia sulla variabile, come in molte altre lingue? Se il compilatore è in grado di dire il tipo in caso di errore, sicuramente dovrebbe già conoscere il tipo in assenza di errori?
xji,

1
@JIXiang: il Rust Language Server consiste nel fornire queste informazioni a un IDE, ma non è ancora maturo: la sua prima versione alpha è stata solo un paio di giorni fa. Sì, questo è un approccio eldritch; sì, stanno arrivando costantemente modi meno esoterici per raggiungere l'obiettivo.
Chris Morgan,

1
questo suona molto come un trucco. è davvero questo il modo idiomatico di controllare il tipo di una variabile?
confused00

109

C'è una funzione instabile std::intrinsics::type_nameche può darti il ​​nome di un tipo, anche se devi usare una build notturna di Rust (è improbabile che funzioni mai in Rust stabile). Ecco un esempio:

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

@vbo: non fino a quando non si sarà stabilizzato. È improbabile che qualcosa del genere si stabilizzi per un po 'di tempo, se non mai, e non mi sorprenderebbe se non fosse mai stabilizzato; non è il genere di cose che dovresti mai fare davvero.
Chris Morgan,

2
Su rust-nightly (1.3) ha funzionato solo cambiando quella prima riga in#![feature(core_intrinsics)]
AT

1
@DmitriNesteruk: print_type_ofsta prendendo referenze ( &T), non valori ( T), quindi devi passare &&strpiuttosto che &str; cioè print_type_of(&"foo")piuttosto che print_type_of("foo").
Chris Morgan,

6
std::any::type_nameè stabile dalla ruggine 1,38: stackoverflow.com/a/58119924
Tim Robinson

1
Ottenere il tipo di qualcosa in fase di compilazione / runtime ha casi d'uso validi. Ad esempio per la serializzazione - o semplicemente per scopi di debug. Coloro che scrivono "Non dovresti mai fare una cosa del genere" semplicemente non si sono mai imbattuti in quei casi d'uso.
BitTickler il

68

Puoi usare la std::any::type_namefunzione. Ciò non richiede un compilatore notturno o una cassa esterna e i risultati sono abbastanza corretti:

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

Attenzione: come indicato nella documentazione, queste informazioni devono essere utilizzate solo a scopo di debug:

Questo è inteso per uso diagnostico. Il contenuto esatto e il formato della stringa non sono specificati, oltre ad essere una descrizione del tipo più efficace.

Se vuoi che la tua rappresentazione del tipo rimanga la stessa tra le versioni del compilatore, dovresti usare un tratto, come nella risposta di phicr .


1
la migliore risposta per me, poiché la maggior parte degli sviluppatori vuole usarlo per scopi di debug, come stampare errori di analisi
kaiser

Esattamente quello di cui avevo bisogno, non so perché questa non sia la risposta contrassegnata!
James Poulose,

1
@JamesPoulose Perché questa funzione è recente, quindi la mia risposta è più recente.
Boiethios,

53

Se conosci in anticipo tutti i tipi, puoi utilizzare i tratti per aggiungere un type_ofmetodo:

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

//...

Nessun intrisics o niente, quindi anche se più limitato, questa è l'unica soluzione che ti dà una stringa ed è stabile. (vedi la risposta di Boiethios in francese ) Tuttavia, è molto laborioso e non tiene conto dei parametri del tipo, quindi potremmo ...

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

Usiamolo:

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

produzione:

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

Rust Playground


Questa risposta potrebbe essere suddivisa in due risposte separate per evitare di confondere le due.
Prajwal Dhatwalia,

2
@PrajwalDhatwalia Ho pensato a quello che hai detto e mi sento soddisfatto di come le versioni si completano a vicenda. La versione del tratto mostra una semplificazione di ciò che la versione macro sta facendo sotto il cofano, rendendo più chiari i suoi obiettivi. La versione macro, d'altra parte, mostra come rendere la versione del tratto più generalmente utilizzabile; non è l'unico modo per farlo, ma anche mostrare che è possibile è vantaggioso. In sintesi, potrebbero essere due risposte ma ritengo che il tutto sia maggiore della somma delle sue parti.
phicr,

19

UPD Quanto segue non funziona più. Controlla la risposta di Shubham per la correzione.

Partenza std::intrinsics::get_tydesc<T>(). È nello stato "sperimentale" in questo momento, ma va bene se stai solo hackerando il sistema di tipi.

Guarda il seguente esempio:

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

Questo è ciò che viene utilizzato internamente per implementare il famoso {:?}formattatore.


15

** AGGIORNAMENTO ** Non è stato verificato che funzioni recentemente.

Ho messo insieme una piccola cassa per farlo in base alla risposta di Vbo. Ti dà una macro per restituire o stampare il tipo.

Inserisci questo nel tuo file Cargo.toml:

[dependencies]
t_bang = "0.1.2"

Quindi puoi usarlo in questo modo:

#[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"
}

@vbo dice che la sua soluzione non funziona più. Il tuo funziona?
Antony Hatchkins,

non funzionante `errore [E0554]: #![feature]non può essere utilizzato sul canale di rilascio stabile`
Muhammed Moussa

7

Puoi anche usare il semplice approccio dell'uso della variabile in println!("{:?}", var). Se Debugnon è implementato per il tipo, è possibile visualizzare il tipo nel messaggio di errore del compilatore:

mod some {
    pub struct SomeType;
}

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

( box )

È sporco ma funziona.


8
Se Debugnon è implementato , questo è un caso abbastanza improbabile. Una delle prime cose che dovresti fare per quasi tutte le strutture è aggiungere #[derive(Debug)]. Penso che i tempi in cui non vuoi Debugsiano molto piccoli.
Shepmaster,

1
puoi spiegare cosa sta succedendo println!("{:?}", unknown_var);?? È un'interpolazione di stringhe ma perché :?all'interno delle parentesi graffe? @DenisKolodin
Julio Marins

Provoco errore. L'idea di consentire al compilatore di fornire informazioni sul tipo con errori. L'ho usato Debugperché non è implementato, ma puoi anche usarlo {}.
DenisKolodin,

4

C'è una risposta @ChrisMorgan per ottenere un tipo approssimativo ("float") in ruggine stabile e c'è una risposta @ShubhamJain per ottenere un tipo preciso ("f64") attraverso una funzione instabile nella ruggine notturna.

Ora ecco un modo in cui si può ottenere un tipo preciso (cioè decidere tra f32 e f64) in ruggine stabile:

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

risultati in

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)

Aggiornare

La variazione turbofish

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

è leggermente più corto ma un po 'meno leggibile.


Se lo sai giàfloat , dire tra f32e f64può essere realizzato constd::mem::size_of_val(&a)
Antony Hatchkins,

1

Alcune altre risposte non funzionano, ma trovo che la cassa del nome tipografico funzioni .

  1. Crea un nuovo progetto:

    cargo new test_typename
  2. Modifica il Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. Modifica il tuo codice sorgente

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

L'output è:

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

Ho seguito i passaggi che hai descritto. Ad oggi, typenamenon funziona con variabili senza tipo esplicito nella dichiarazione. Eseguendolo con my_number dalla domanda si ottiene il seguente errore "impossibile chiamare il metodo type_name_ofsu un tipo numerico ambiguo {float}. f32
Aiuto

I test di 0.65e funziona bene: type of c 0.65 0.65 is f64. ecco la mia versione:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq,

1

Se vuoi solo conoscere il tipo di variabile durante lo sviluppo interattivo, ti consiglio caldamente di usare rls (rust language server) all'interno del tuo editor o ide. È quindi possibile semplicemente abilitare permanentemente o attivare l'abilità hover e posizionare il cursore sulla variabile. Una piccola finestra di dialogo dovrebbe fornire informazioni sulla variabile incluso il tipo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.