Jak wydrukować typ zmiennej w Rust?


238

Mam następujące:

let mut my_number = 32.90;

Jak wydrukować typ my_number ?

Używanie typei type_ofnie działało. Czy istnieje inny sposób wydrukowania typu numeru?

Odpowiedzi:


177

Jeśli chcesz się tylko dowiedzieć typ zmiennej i chcesz to zrobić w czasie kompilacji, możesz spowodować błąd i skłonić kompilator do jego pobrania.

Na przykład ustaw zmienną na typ, który nie działa :

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

Lub wywołaj nieprawidłową metodę :

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

Lub uzyskaj dostęp do nieprawidłowego pola :

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

Ujawniają typ, który w tym przypadku nie jest w pełni rozwiązany. Nazywa się to „zmienną zmiennoprzecinkową” w pierwszym przykładzie i „ {float}” we wszystkich trzech przykładach; jest to typ częściowo rozwiązany, który może się skończyć f32lub f64, w zależności od tego, jak go użyjesz. „ {float}” Nie jest prawną nazwą typu, jest symbolem zastępczym oznaczającym „Nie jestem całkowicie pewien, co to jest”, ale jest liczbą zmiennoprzecinkową. W przypadku zmiennych zmiennoprzecinkowych, jeśli ich nie ograniczysz, domyślnie będzie to f64¹. (Niekwalifikowany literał całkowity będzie domyślnie ustawiony na i32.)

Zobacz też:


Đ nadal mogą być sposoby zaskakujący kompilator, tak, że nie można wybrać pomiędzy f32i f64; Nie jestem pewny. Kiedyś było tak proste 32.90.eq(&32.90), ale to traktuje zarówno f64teraz, jak i szczęśliwie, więc nie wiem.


4
:?już od dłuższego czasu jest wdrażany ręcznie. Ale co ważniejsze, std::fmt::Debugimplementacja (do tego właśnie :?wykorzystuje) typów liczb nie zawiera już przyrostka wskazującego, jakiego typu ona jest.
Chris Morgan

2
Często używam tych technik, aby znaleźć typ wyrażenia, ale nie zawsze działa ono, szczególnie gdy w grę wchodzą parametry typu. Na przykład kompilator powie mi, że spodziewa się ImageBuffer<_, Vec<_>>czegoś, co nie bardzo mi pomaga, gdy próbuję napisać funkcję, która przyjmuje jedną z tych rzeczy jako parametr. I dzieje się tak w kodzie, który inaczej się kompiluje, dopóki nie dodam :(). Czy nie ma lepszego sposobu?
Christopher Armstrong,

2
Wydaje się to nieco skomplikowane i nieintuicyjne. Czy edytor kodu byłby bardzo trudny, np. Emacs podaje typ, gdy kursor spoczywa na zmiennej, jak w wielu innych językach? Jeśli kompilator może powiedzieć typ po błędzie, to na pewno powinien już znać typ, gdy nie ma żadnego błędu?
Xji

1
@JIXiang: Rust Language Server polega na dostarczaniu tych informacji IDE, ale nie jest jeszcze dojrzały - jego pierwsze wydanie alfa miało miejsce zaledwie kilka dni temu. Tak, to podejrzane podejście; tak, stale pojawiają się mniej ezoteryczne sposoby osiągnięcia celu.
Chris Morgan

1
to brzmi jak włamanie. czy to faktycznie idiomatyczny sposób sprawdzenia typu zmiennej?
zmieszany 00

109

Istnieje niestabilna funkcja, std::intrinsics::type_namektóra może dać ci nazwę typu, chociaż musisz użyć nocnej wersji Rdzy (jest mało prawdopodobne, aby działała w stabilnej Rust). Oto przykład:

#![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: nie, dopóki się nie ustabilizuje. Coś takiego nie jest prawdopodobnie stabilizowane przez dłuższy czas, jeśli w ogóle - i nie zaskoczyłoby mnie, gdyby nigdy nie było ustabilizowane; to nie jest coś, co powinieneś naprawdę zrobić.
Chris Morgan

2
W nocy z rdzą (1.3) działało to tylko przy zmianie pierwszej linii na#![feature(core_intrinsics)]
AT

1
@DmitriNesteruk: print_type_ofprzyjmuje referencje ( &T), a nie wartości ( T), więc musisz przekazać &&strzamiast &str; to znaczy print_type_of(&"foo")raczej niż print_type_of("foo").
Chris Morgan,

Miałeś rację, minęły 3 lata i nadal nie jest ustabilizowany.
Anton Kochkov

5
std::any::type_namejest stabilny od rdzy 1.38: stackoverflow.com/a/58119924
Tim Robinson

66

Możesz użyć tej std::any::type_namefunkcji. Nie wymaga to nocnego kompilatora ani zewnętrznej skrzynki, a wyniki są całkiem poprawne:

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

Uwaga: jak wspomniano w dokumentacji, informacje te należy wykorzystywać wyłącznie w celu debugowania:

Jest to przeznaczone do użytku diagnostycznego. Dokładna zawartość i format ciągu nie są określone, poza tym, że jest najlepszym opisem tego typu.

Jeśli chcesz, aby reprezentacja typu pozostała taka sama między wersjami kompilatora, powinieneś użyć cechy, takiej jak w odpowiedzi phicr .


1
najlepsza odpowiedź dla mnie, ponieważ większość deweloperów chce tego używać do celów debugowania, takich jak drukowanie błędów przetwarzania
Kaiser

Dokładnie to, czego potrzebowałem, nie wiem, dlaczego nie jest to wyraźna odpowiedź!
James Poulose

1
@JamesPoulose Ponieważ ta funkcja jest nowa, więc moja odpowiedź jest nowsza.
Boiethios,

53

Jeśli znasz wszystkie typy wcześniej, możesz użyć cech, aby dodać type_ofmetodę:

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

//...

Żadnych intrisk i nic, więc choć bardziej ograniczone, jest to jedyne rozwiązanie, które daje ci ciąg i jest stabilne. (patrz odpowiedź francuskiego Boiethiosa ). Jest to jednak bardzo pracochłonne i nie uwzględnia parametrów typu, więc moglibyśmy ...

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

Użyjmy tego:

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

wynik:

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

Plac zabaw dla rdzy


Ta odpowiedź może być podzielona na dwie osobne odpowiedzi, aby uniknąć pomieszania tych dwóch.
Prajwal Dhatwalia

2
@PrajwalDhatwalia Myślałem o tym, co powiedziałeś i czuję, że jestem zadowolony z tego, jak wersje się uzupełniają. Wersja cechy pokazuje uproszczenie tego, co robi wersja makro pod maską, dzięki czemu jej cele są wyraźniejsze. Z drugiej strony wersja makro pokazuje, jak uczynić wersję cechy bardziej użyteczną; nie jest to jedyny sposób, aby to zrobić, ale nawet wykazanie, że jest to możliwe, jest korzystne. Podsumowując, mogą to być dwie odpowiedzi, ale wydaje mi się, że całość jest większa niż suma jej części.
phicr

19

UPD Poniższe nie działa. Sprawdź odpowiedź Shubhama celu korekty.

Sprawdzić std::intrinsics::get_tydesc<T>() . W tej chwili jest w stanie „eksperymentalnym”, ale jest OK, jeśli tylko hakujesz wokół systemu typów.

Sprawdź następujący przykład:

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

To jest używane wewnętrznie do implementacji słynnego {:?}formatera.


15

** AKTUALIZACJA ** To nie zostało ostatnio zweryfikowane.

Złożyłem małą skrzynkę, aby to zrobić na podstawie odpowiedzi VBO. Daje makro do zwrócenia lub wydrukowania typu.

Umieść to w swoim pliku Cargo.toml:

[dependencies]
t_bang = "0.1.2"

Następnie możesz użyć go w następujący sposób:

#[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 mówi, że jego rozwiązanie już nie działa. Czy twoje działa?
Antony Hatchkins

nie działa „błąd [E0554]: #![feature]nie można go używać w kanale stabilnego wydawania„
Muhammed Moussa

7

Możesz także zastosować proste podejście do używania zmiennej w println!("{:?}", var). Jeśli Debugnie jest zaimplementowany dla tego typu, możesz zobaczyć typ w komunikacie o błędzie kompilatora:

mod some {
    pub struct SomeType;
}

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

( kojec )

Jest brudny, ale działa.


8
Jeśli Debugnie jest zaimplementowany - jest to jednak dość mało prawdopodobny przypadek. Jedną z pierwszych rzeczy, które powinieneś zrobić dla większości struktur, jest dodanie #[derive(Debug)]. Myślę, że czasy, w których nie chcesz, Debugsą bardzo małe.
Shepmaster

1
czy możesz wyjaśnić, co się dzieje println!("{:?}", unknown_var);? Czy to interpolacja łańcuchowa, ale dlaczego w :?nawiasach klamrowych? @DenisKolodin
Julio Marins

Prowokuję błąd. Pomysł, aby kompilator podał informacje o typie z błędem. Użyłem, Debugponieważ nie jest zaimplementowane, ale możesz również użyć {}.
DenisKolodin

4

Istnieje odpowiedź @ChrisMorgan, aby uzyskać przybliżony typ („float”) w stabilnej rdzy, oraz odpowiedź @ShubhamJain, aby uzyskać dokładny typ („f64”) poprzez niestabilną funkcję w nocnej rdzy.

Oto sposób, w jaki można uzyskać dokładny typ (tj. Zdecydować między f32 a f64) w stabilnej rdzy:

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

prowadzi do

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)

Aktualizacja

Odmiana turbin

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

jest nieco krótszy, ale nieco mniej czytelny.


Jeśli już wiesz, że tak jest float, mówienie pomiędzy f32i f64można to osiągnąć za pomocąstd::mem::size_of_val(&a)
Antony Hatchkins 28.08.19

1

Niektóre inne odpowiedzi nie działają, ale uważam, że typename paka działa.

  1. Utwórz nowy projekt:

    cargo new test_typename
  2. Zmodyfikuj Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. Zmodyfikuj kod źródłowy

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

Dane wyjściowe to:

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

Postępowałem zgodnie z krokami, które opisałeś. Na dzień dzisiejszy typenamenie działa ze zmiennymi bez wyraźnego typu w deklaracji. Uruchomienie go z my_number pytaniem powoduje następujący błąd: „nie można wywołać metody type_name_ofna niejednoznacznym typie numerycznym {float}. Help: musisz określić typ tego powiązania, np. f32
Antony Hatchkins,

Test ja 0.65i to działa dobrze: type of c 0.65 0.65 is f64. oto moja wersja:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq,

1

Jeśli chcesz tylko poznać typ swojej zmiennej podczas interaktywnego programowania, zdecydowanie polecam użycie rls (serwer języka rust) w edytorze lub ide. Następnie możesz po prostu na stałe włączyć lub przełączać zdolność najechania kursorem i po prostu umieścić kursor nad zmienną. W małym oknie dialogowym powinny pojawić się informacje o zmiennej, w tym o typie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.