Comment imprimer le type d'une variable dans Rust?


240

J'ai ce qui suit:

let mut my_number = 32.90;

Comment imprimer le type de my_number?

Utilisation typeet type_ofn'a pas fonctionné. Existe-t-il une autre façon d'imprimer le type du numéro?

Réponses:


177

Si vous souhaitez simplement découvrir le type d'une variable et êtes prêt à le faire lors de la compilation, vous pouvez provoquer une erreur et demander au compilateur de la récupérer.

Par exemple, définissez la variable sur un type qui ne fonctionne pas :

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

Ou appelez une méthode non valide :

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

Ou accédez à un champ invalide :

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

Ceux-ci révèlent le type, qui dans ce cas n'est en fait pas entièrement résolu. Cela s'appelle «variable à virgule flottante» dans le premier exemple et « {float}» dans les trois exemples; il s'agit d'un type partiellement résolu qui pourrait finir f32ou f64, selon la façon dont vous l'utilisez. « {float}» Est pas un nom de type juridique, il est un espace réservé qui signifie « Je suis sûr que pas tout à fait ce que cela est », mais il est un nombre à virgule flottante. Dans le cas des variables à virgule flottante, si vous ne la contraignez pas, elle sera par défaut f64¹. (Un littéral entier non qualifié sera défini par défaut sur i32.)

Voir également:


¹ Il peut encore y avoir des moyens de dérouter le compilateur pour qu'il ne puisse pas décider entre f32et f64; Je ne suis pas sûr. Auparavant, c'était aussi simple que cela 32.90.eq(&32.90), mais cela traite à la fois comme f64maintenant et se déroule joyeusement, donc je ne sais pas.


4
:?est depuis longtemps mis en œuvre manuellement. Mais plus important encore, l' std::fmt::Debugimplémentation (car c'est ce qui est :?utilisé) pour les types de nombres n'inclut plus de suffixe pour indiquer de quel type il s'agit.
Chris Morgan

2
J'utilise beaucoup ces techniques pour essayer de trouver le type d'une expression, mais cela ne fonctionne pas toujours, surtout lorsqu'il y a des paramètres de type impliqués. Le compilateur me dira, par exemple, qu'il attend un ImageBuffer<_, Vec<_>>qui ne m'aide pas beaucoup lorsque j'essaie d'écrire une fonction qui prend l'une de ces choses comme paramètre. Et cela se produit dans du code qui autrement se compile jusqu'à ce que j'ajoute le :(). N'y a-t-il pas de meilleur moyen?
Christopher Armstrong

2
Cela semble un peu compliqué et peu intuitif. Serait-il très difficile pour l'éditeur de code, par exemple Emacs de fournir le type lorsque le curseur repose sur la variable, comme dans de nombreux autres langages? Si le compilateur peut dire le type en cas d'erreur, il devrait sûrement aussi déjà connaître le type lorsqu'il n'y a pas d'erreur?
xji

1
@JIXiang: le Rust Language Server consiste à fournir ces informations à un IDE, mais il n'est pas encore mûr - sa première version alpha était il y a seulement quelques jours. Oui, c'est une approche eldritch; oui, des moyens moins ésotériques d'atteindre le but arrivent régulièrement.
Chris Morgan

1
cela ressemble beaucoup à un hack. est-ce en fait la façon idiomatique de vérifier le type d'une variable?
confused00

109

Il existe une fonction instable std::intrinsics::type_namequi peut vous donner le nom d'un type, bien que vous deviez utiliser une version nocturne de Rust (cela ne fonctionnera probablement jamais dans Rust stable). Voici un exemple:

#![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: pas tant qu'il n'est pas stabilisé. Il est peu probable que quelque chose comme ça se stabilise pendant un certain temps, voire jamais - et cela ne m'étonnerait pas s'il ne se stabilise jamais; ce n'est pas le genre de chose que vous devriez vraiment faire.
Chris Morgan

2
Le soir, la rouille (1.3) ne fonctionnait que lors du changement de cette première ligne en#![feature(core_intrinsics)]
AT

1
@DmitriNesteruk: print_type_ofprend des références ( &T), pas des valeurs ( T), vous devez donc passer &&strplutôt que &str; c'est print_type_of(&"foo")plutôt que print_type_of("foo").
Chris Morgan

6
std::any::type_nameest stable depuis la rouille 1.38: stackoverflow.com/a/58119924
Tim Robinson

1
Obtenir le type de quelque chose lors de la compilation / exécution a des cas d'utilisation valides. Pour la sérialisation par exemple - ou simplement à des fins de débogage. Ceux qui écrivent «Vous ne devriez jamais faire une telle chose» n'ont tout simplement jamais rencontré ces cas d'utilisation eux-mêmes.
BitTickler

68

Vous pouvez utiliser la std::any::type_namefonction. Cela n'a pas besoin d'un compilateur nocturne ou d'une caisse externe, et les résultats sont tout à fait corrects:

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

Attention: comme indiqué dans la documentation, ces informations ne doivent être utilisées qu'à des fins de débogage:

Ceci est destiné à un usage diagnostique. Le contenu exact et le format de la chaîne ne sont pas spécifiés, à part une description du type au mieux.

Si vous voulez que votre représentation de type reste la même entre les versions du compilateur, vous devez utiliser un trait, comme dans la réponse du phicr .


1
meilleure réponse pour moi, car la plupart des développeurs veulent l'utiliser à des fins de débogage, comme l'impression d'échecs d'analyse
kaiser

Exactement ce dont j'avais besoin, je ne sais pas pourquoi ce n'est pas la réponse marquée!
James Poulose

1
@JamesPoulose Parce que cette fonction est récente, ma réponse est donc plus récente.
Boiethios

53

Si vous connaissez tous les types au préalable, vous pouvez utiliser des traits pour ajouter une type_ofméthode:

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

//...

Pas d'intrication ou rien, donc bien que plus limité, c'est la seule solution ici qui vous donne une chaîne et est stable. (voir la réponse du français Boiethios ) Cependant, c'est très laborieux et ne tient pas compte des paramètres de type, donc nous pourrions ...

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

Utilisons-le:

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

production:

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

Aire de jeux de rouille


Cette réponse pourrait être décomposée en deux réponses distinctes afin d'éviter de confondre les deux.
Prajwal Dhatwalia

2
@PrajwalDhatwalia J'ai réfléchi à ce que vous avez dit et je me sens satisfait de la complémentarité des versions. La version trait montre une simplification de ce que la version macro fait sous le capot, rendant ses objectifs plus clairs. La version macro, d'autre part, montre comment rendre la version trait plus généralement utilisable; ce n'est pas la seule façon de le faire, mais même montrer que c'est possible est avantageux. En résumé, cela pourrait être deux réponses mais je pense que l'ensemble est supérieur à la somme de ses parties.
phicr

19

UPD Ce qui suit ne fonctionne plus. Vérifiez la réponse de Shubham pour correction.

Découvrez std::intrinsics::get_tydesc<T>(). Il est actuellement dans un état "expérimental", mais ce n'est pas grave si vous piratez simplement le système de type.

Découvrez l'exemple suivant:

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

C'est ce qui est utilisé en interne pour implémenter le célèbre {:?}formateur.


15

** MISE À JOUR ** Cela n'a pas été vérifié pour fonctionner à tout moment récemment.

J'ai mis en place une petite caisse pour le faire en fonction de la réponse de vbo. Il vous donne une macro pour retourner ou imprimer le type.

Mettez ceci dans votre fichier Cargo.toml:

[dependencies]
t_bang = "0.1.2"

Ensuite, vous pouvez l'utiliser comme ceci:

#[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 dit que sa solution ne fonctionne plus. Le vôtre fonctionne-t-il?
Antony Hatchkins du

ne fonctionne pas «erreur [E0554]: #![feature]ne peut pas être utilisé sur le canal de sortie stable»
Muhammed Moussa

7

Vous pouvez également utiliser l'approche simple consistant à utiliser la variable dans println!("{:?}", var). Si Debugn'est pas implémenté pour le type, vous pouvez voir le type dans le message d'erreur du compilateur:

mod some {
    pub struct SomeType;
}

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

( parc )

C'est sale mais ça marche.


8
Si Debugn'est pas implémenté - c'est un cas assez improbable cependant. L'une des premières choses que vous devriez faire pour la plupart des structures est d'ajouter #[derive(Debug)]. Je pense que les moments où vous ne voulez pas Debugsont très petits.
Shepmaster

1
pouvez-vous expliquer ce qui se passe en println!("{:?}", unknown_var);?? Est-ce une interpolation de chaîne mais pourquoi l' :?intérieur des accolades? @DenisKolodin
Julio Marins

Je provoque une erreur. L'idée de laisser le compilateur fournir des informations de type avec erreur. J'ai utilisé Debugcar il n'est pas implémenté, mais vous pouvez également l'utiliser {}.
DenisKolodin

4

Il y a une réponse @ChrisMorgan pour obtenir le type approximatif ("float") dans la rouille stable et il y a une réponse @ShubhamJain pour obtenir le type précis ("f64") grâce à la fonction instable dans la rouille nocturne.

Maintenant, voici un moyen d'obtenir un type précis (c'est-à-dire de choisir entre f32 et f64) dans la rouille stable:

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

résulte 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)

Mettre à jour

La variation du turbofish

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

est légèrement plus court mais un peu moins lisible.


Si vous le savez déjàfloat , dire entre f32et f64peut être accompli avecstd::mem::size_of_val(&a)
Antony Hatchkins

1

D'autres réponses ne fonctionnent pas, mais je trouve que le typename fonctionne caisse.

  1. Créez un nouveau projet:

    cargo new test_typename
  2. Modifier le Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. Modifiez votre code source

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

La sortie est:

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

J'ai suivi les étapes que vous avez décrites. À ce jour, typenamene fonctionne pas avec des variables sans type explicite dans la déclaration. L'exécuter avec my_number la question donne l'erreur suivante "impossible d'appeler la méthode type_name_ofsur un type numérique ambigu {float}. Aide: vous devez spécifier un type pour cette liaison, comme f32"
Antony Hatchkins

Je essai 0.65et il fonctionne bien: type of c 0.65 0.65 is f64. voici ma version:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq

1

Si vous voulez juste connaître le type de votre variable pendant le développement interactif, je vous recommande fortement d'utiliser rls (rust language server) dans votre éditeur ou ide. Vous pouvez ensuite simplement activer ou basculer de manière permanente la capacité de survol et placer simplement votre curseur sur la variable. Une petite boîte de dialogue devrait fournir des informations sur la variable, y compris le type.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.