Wie drucke ich den Typ einer Variablen in Rust?


237

Ich habe folgendes:

let mut my_number = 32.90;

Wie drucke ich den Typ von my_number?

Verwenden typeund type_ofhat nicht funktioniert. Gibt es eine andere Möglichkeit, den Typ der Nummer auszudrucken?

Antworten:


177

Wenn Sie lediglich den Typ einer Variablen herausfinden möchten und dies zur Kompilierungszeit tun möchten , können Sie einen Fehler verursachen und den Compiler veranlassen, ihn abzurufen.

Zum Beispiel setzen Sie die Variable auf eine Art , die nicht funktioniert :

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

Oder rufen Sie eine ungültige Methode auf :

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

Oder greifen Sie auf ein ungültiges Feld zu :

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

Diese zeigen den Typ, der in diesem Fall tatsächlich nicht vollständig aufgelöst ist. Im ersten Beispiel heißt es "Gleitkommavariable" und {float}in allen drei Beispielen " ". Dies ist ein teilweise aufgelöster Typ, der enden kann f32oder f64je nachdem, wie Sie ihn verwenden. " {float}" Ist kein legaler Typname, sondern ein Platzhalter, der "Ich bin mir nicht ganz sicher, was das ist" bedeutet, aber es ist eine Gleitkommazahl. Wenn Sie bei Gleitkommavariablen diese nicht einschränken, wird standardmäßig f64¹ verwendet. (Ein unqualifiziertes Integer-Literal wird standardmäßig verwendet i32.)

Siehe auch:


¹ Möglicherweise gibt es immer noch Möglichkeiten, den Compiler zu verwirren, sodass er sich nicht zwischen f32und entscheiden kann f64. Ich bin mir nicht sicher. Früher war es so einfach wie jetzt 32.90.eq(&32.90), aber das behandelt sowohl f64jetzt als auch tuckert glücklich mit, also weiß ich es nicht.


4
:?wurde schon lange manuell implementiert. Noch wichtiger ist jedoch, dass die std::fmt::DebugImplementierung (für die dies :?verwendet wird) für Zahlentypen kein Suffix mehr enthält, das angibt, um welchen Typ es sich handelt.
Chris Morgan

2
Ich verwende diese Techniken häufig, um den Typ eines Ausdrucks zu finden, aber es funktioniert nicht immer, insbesondere wenn Typparameter beteiligt sind. Der Compiler sagt mir zum Beispiel, dass er eine erwartet, die mir ImageBuffer<_, Vec<_>>nicht sehr hilft, wenn ich versuche, eine Funktion zu schreiben, die eines dieser Dinge als Parameter verwendet. Und dies geschieht in Code, der ansonsten kompiliert wird, bis ich den hinzufüge :(). Gibt es keinen besseren Weg?
Christopher Armstrong

2
Dies scheint ein bisschen verworren und nicht intuitiv zu sein. Wäre es für den Code-Editor sehr schwierig, z. B. Emacs den Typ bereitzustellen, wenn der Cursor auf der Variablen ruht, wie in vielen anderen Sprachen? Wenn der Compiler den Typ bei einem Fehler erkennen kann, sollte er den Typ sicherlich auch schon kennen, wenn kein Fehler vorliegt?
xji

1
@JIXiang: Beim Rust Language Server geht es darum, diese Informationen einer IDE bereitzustellen, aber sie sind noch nicht ausgereift - ihre erste Alpha-Version war erst vor ein paar Tagen. Ja, dies ist ein eltritischer Ansatz. Ja, es kommen immer weniger esoterische Wege, um das Ziel zu erreichen.
Chris Morgan

1
Das klingt sehr nach einem Hack. Ist dies tatsächlich die idiomatische Methode, um den Typ einer Variablen zu überprüfen?
verwirrt00

109

Es gibt eine instabile Funktion std::intrinsics::type_name, mit der Sie den Namen eines Typs erhalten können, obwohl Sie einen nächtlichen Build von Rust verwenden müssen (dies wird wahrscheinlich nie in stabilem Rust funktionieren). Hier ist ein Beispiel:

#![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: erst wenn es stabilisiert ist. Es ist unwahrscheinlich, dass sich so etwas für einige Zeit stabilisiert, wenn überhaupt - und es würde mich nicht überraschen, wenn es niemals stabilisiert wird. Es ist nicht die Art von Dingen, die Sie jemals wirklich tun sollten.
Chris Morgan

2
Auf Rostnacht (1.3) funktionierte es nur, wenn diese erste Zeile auf#![feature(core_intrinsics)]
AT

1
@DmitriNesteruk: print_type_ofnimmt Referenzen ( &T), nicht Werte ( T), also müssen Sie übergeben &&stranstatt &str; das heißt, print_type_of(&"foo")eher als print_type_of("foo").
Chris Morgan

Sie hatten Recht, 3 Jahre sind vergangen und es ist immer noch nicht stabilisiert.
Anton Kochkov

5
std::any::type_nameist stabil seit Rost 1.38: stackoverflow.com/a/58119924
Tim Robinson

66

Sie können die std::any::type_nameFunktion verwenden. Dies erfordert keinen nächtlichen Compiler oder eine externe Kiste, und die Ergebnisse sind ganz richtig:

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

Seien Sie gewarnt: Wie in der Dokumentation angegeben, dürfen diese Informationen nur für Debug-Zwecke verwendet werden:

Dies ist für diagnostische Zwecke vorgesehen. Der genaue Inhalt und das Format der Zeichenfolge werden nicht angegeben, außer dass es sich um eine Best-Effort-Beschreibung des Typs handelt.

Wenn Sie möchten, dass Ihre Typdarstellung zwischen den Compilerversionen gleich bleibt, sollten Sie ein Merkmal verwenden, wie in der Antwort des Phicr .


1
Die beste Antwort für mich, da die meisten Entwickler dies für Debugging-Zwecke verwenden möchten, z. B. zum Drucken von Parsing-Fehlern
Kaiser

Genau das, was ich brauchte, ich weiß nicht, warum dies nicht die markierte Antwort ist!
James Poulose

1
@JamesPoulose Da diese Funktion neu ist, ist meine Antwort neuer.
Boiethios

53

Wenn Sie alle Typen im Voraus kennen, können Sie mithilfe von Merkmalen eine type_ofMethode hinzufügen :

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

//...

Keine Feinheiten oder nichts, obwohl dies eingeschränkter ist, ist dies die einzige Lösung hier, die Ihnen einen String gibt und stabil ist. (Siehe die Antwort von French Boiethios. ) Es ist jedoch sehr mühsam und berücksichtigt keine Typparameter, sodass wir ...

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

Lass es uns benutzen:

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

Ausgabe:

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

Rostspielplatz


Diese Antwort könnte in zwei separate Antworten unterteilt werden, um eine Verwechslung der beiden zu vermeiden.
Prajwal Dhatwalia

2
@PrajwalDhatwalia Ich habe darüber nachgedacht, was Sie gesagt haben, und ich bin zufrieden damit, wie sich die Versionen ergänzen. Die Trait-Version zeigt eine Vereinfachung dessen, was die Makro-Version unter der Haube tut, um ihre Ziele klarer zu machen. Die Makroversion zeigt andererseits, wie die Trait-Version allgemeiner verwendet werden kann. Es ist nicht der einzige Weg, dies zu tun, aber es ist vorteilhaft zu zeigen, dass es möglich ist. Zusammenfassend könnte dies zwei Antworten sein, aber ich denke, das Ganze ist größer als die Summe seiner Teile.
Phicr

19

UPD Folgendes funktioniert nicht mehr. Überprüfen Sie Shubhams Antwort auf Korrektur.

Auschecken std::intrinsics::get_tydesc<T>(). Es befindet sich derzeit im "experimentellen" Zustand, aber es ist in Ordnung, wenn Sie nur das Typsystem hacken.

Schauen Sie sich das folgende Beispiel an:

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

Dies wird intern verwendet , um den berühmten {:?}Formatierer zu implementieren .


15

** UPDATE ** Es wurde in letzter Zeit nicht überprüft, ob dies funktioniert.

Ich habe eine kleine Kiste zusammengestellt, um dies basierend auf der Antwort von vbo zu tun. Sie erhalten ein Makro, mit dem Sie den Typ zurückgeben oder ausdrucken können.

Fügen Sie dies in Ihre Cargo.toml-Datei ein:

[dependencies]
t_bang = "0.1.2"

Dann können Sie es so verwenden:

#[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 sagt, dass seine Lösung nicht mehr funktioniert. Funktioniert deine?
Antony Hatchkins

funktioniert nicht `Fehler [E0554]: #![feature]darf nicht auf dem stabilen Release-Kanal verwendet werden`
Muhammed Moussa

7

Sie können auch den einfachen Ansatz verwenden, die Variable in zu verwenden println!("{:?}", var). Wenn Debugfür den Typ nicht implementiert, wird der Typ in der Fehlermeldung des Compilers angezeigt:

mod some {
    pub struct SomeType;
}

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

( Laufstall )

Es ist schmutzig, aber es funktioniert.


8
Wenn dies Debugnicht implementiert ist, ist dies jedoch ein ziemlich unwahrscheinlicher Fall. Eines der ersten Dinge, die Sie für die meisten Strukturen tun sollten, ist das Hinzufügen #[derive(Debug)]. Ich denke, die Zeiten, in denen du nicht willst, Debugsind sehr klein.
Shepmaster

1
Kannst du erklären, was in passiert println!("{:?}", unknown_var);? Ist es eine String-Interpolation, aber warum das :?Innere der geschweiften Klammern? @ DenisKolodin
Julio Marins

Ich provoziere Fehler. Die Idee, den Compiler Typinformationen mit Fehler bereitstellen zu lassen. Ich habe es verwendet, Debugweil es nicht implementiert ist, aber Sie können es auch verwenden {}.
DenisKolodin

4

Es gibt eine @ ChrisMorgan- Antwort , um den ungefähren Typ ("float") in stabilem Rost zu erhalten, und es gibt eine @ ShubhamJain- Antwort , um einen genauen Typ ("f64") durch instabile Funktion in nächtlichem Rost zu erhalten.

Hier ist eine Möglichkeit, wie man einen präzisen Typ (dh zwischen f32 und f64 entscheiden) für stabilen Rost erhalten kann:

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

führt zu

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)

Aktualisieren

Die Turbofisch-Variante

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

ist etwas kürzer aber etwas weniger lesbar.


Wenn Sie bereits wissen , ist es float, zu sagen zwischen f32und f64erreicht werden kann , mitstd::mem::size_of_val(&a)
Antony Hatchkins

1

Einige andere Antworten funktionieren nicht, aber ich finde, dass die Typennamen- Kiste funktioniert.

  1. Erstellen Sie ein neues Projekt:

    cargo new test_typename
  2. Ändern Sie die Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. Ändern Sie Ihren Quellcode

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

Die Ausgabe ist:

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

Ich habe die von Ihnen beschriebenen Schritte befolgt. Funktioniert ab heute typenamenicht mehr mit Variablen ohne expliziten Typ in der Deklaration. Wenn Sie es mit my_number der Frage ausführen, wird der folgende Fehler type_name_of{float}f32
angezeigt

Ich teste 0.65und es funktioniert gut : type of c 0.65 0.65 is f64. Hier ist meine Version:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq

1

Wenn Sie nur den Typ Ihrer Variablen während der interaktiven Entwicklung kennen möchten, würde ich die Verwendung von rls ( Rust Language Server) in Ihrem Editor oder Ihrer Idee sehr empfehlen . Sie können dann einfach die Schwebefähigkeit dauerhaft aktivieren oder umschalten und den Cursor einfach über die Variable bewegen. In einem kleinen Dialogfeld sollten Informationen zur Variablen einschließlich des Typs angezeigt werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.