Bagaimana cara mencetak tipe variabel di Rust?

239

Saya memiliki yang berikut ini:

let mut my_number = 32.90;

Bagaimana cara saya mencetak jenis my_number?

Menggunakan typedan type_oftidak berhasil. Apakah ada cara lain untuk mencetak jenis nomor itu?

pengguna2431012
sumber

Jawaban:

177

Jika Anda hanya ingin mengetahui jenis variabel dan bersedia melakukannya pada waktu kompilasi, Anda dapat menyebabkan kesalahan dan meminta kompilator untuk mengambilnya.

Misalnya, atur variabel ke tipe yang tidak berfungsi :

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

Atau hubungi metode yang tidak valid :

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

Atau akses bidang yang tidak valid :

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

Ini mengungkapkan jenis, yang dalam hal ini sebenarnya tidak sepenuhnya diselesaikan. Ini disebut "variabel titik-mengambang" pada contoh pertama, dan " {float}" dalam ketiga contoh; ini adalah jenis yang diselesaikan sebagian yang bisa berakhir f32atau f64, tergantung pada bagaimana Anda menggunakannya. " {float}" Bukan nama jenis hukum, ini adalah placeholder yang berarti "Saya tidak sepenuhnya yakin apa ini", tetapi ini adalah angka floating-point. Dalam kasus variabel floating-point, jika Anda tidak membatasi itu, itu akan default ke f64¹. (Literal integer yang tidak berkualitas akan default ke i32.)

Lihat juga:


¹ Mungkin masih ada cara membingungkan penyusun sehingga tidak bisa memutuskan antara f32dan f64; Saya tidak yakin. Dulu sesederhana itu 32.90.eq(&32.90), tetapi itu memperlakukan baik seperti f64sekarang dan bersama-sama bahagia, jadi saya tidak tahu.

Chris Morgan
sumber
4
:?telah cukup lama sekarang telah diimplementasikan secara manual. Tetapi yang lebih penting, std::fmt::Debugimplementasi (untuk itulah yang :?digunakan) untuk tipe angka tidak lagi menyertakan akhiran untuk menunjukkan tipe yang mana.
Chris Morgan
2
Saya banyak menggunakan teknik ini untuk mencoba menemukan jenis ekspresi, tetapi itu tidak selalu berhasil, terutama ketika ada parameter tipe yang terlibat. Kompiler akan, misalnya, memberi tahu saya bahwa itu mengharapkan ImageBuffer<_, Vec<_>>yang tidak banyak membantu saya ketika saya mencoba menulis fungsi yang menggunakan salah satu dari hal-hal ini sebagai parameter. Dan ini terjadi dalam kode yang jika tidak dikompilasi sampai saya menambahkan :(). Apakah tidak ada cara yang lebih baik?
Christopher Armstrong
2
Ini tampaknya agak berbelit-belit dan tidak intuitif. Akankah sangat sulit bagi editor kode misalnya Emacs memberikan jenis ketika kursor bertumpu pada variabel, seperti dalam banyak bahasa lain? Jika kompiler dapat mengetahui tipe setelah kesalahan, tentunya ia juga harus tahu jenisnya ketika tidak ada kesalahan?
xji
1
@JIXiang: Server Bahasa Rust adalah tentang memberikan informasi ini ke IDE, tapi belum matang — rilis alpha pertamanya hanya beberapa hari yang lalu. Ya, ini adalah pendekatan eldritch; ya, cara-cara yang kurang esoterik untuk mencapai tujuan terus datang.
Chris Morgan
1
ini terdengar seperti retasan. apakah ini sebenarnya cara idiomatis untuk memeriksa jenis variabel?
confused00
109

Ada fungsi std::intrinsics::type_nameyang tidak stabil yang bisa membuat Anda mendapatkan nama suatu tipe, meskipun Anda harus menggunakan build malam hari Rust (ini tidak mungkin bekerja di Rust yang stabil). Ini sebuah contoh:

#![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
sumber
@ Vbo: tidak sampai itu stabil. Sesuatu seperti ini tidak mungkin distabilkan untuk beberapa waktu, jika pernah — dan itu tidak akan mengejutkan saya jika tidak pernah distabilkan; itu bukan hal yang harus Anda lakukan.
Chris Morgan
2
Pada karat-malam (1.3) itu hanya bekerja ketika mengubah baris pertama ke#![feature(core_intrinsics)]
AT
1
@DmitriNesteruk: print_type_ofmengambil referensi ( &T), bukan nilai ( T), jadi Anda harus lulus &&strdaripada &str; itu, print_type_of(&"foo")bukannya print_type_of("foo").
Chris Morgan
6
std::any::type_namestabil sejak karat 1.38: stackoverflow.com/a/58119924
Tim Robinson
1
Mendapatkan jenis sesuatu saat kompilasi / runtime memiliki kasus penggunaan yang valid. Untuk serialisasi misalnya - atau hanya untuk keperluan debugging. Mereka yang menulis "Anda seharusnya tidak pernah melakukan hal seperti itu" sama sekali belum pernah menemukan kasing itu sendiri.
BitTickler
67

Anda dapat menggunakan std::any::type_namefungsi ini. Ini tidak memerlukan kompiler malam atau peti eksternal, dan hasilnya cukup benar:

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

Diperingatkan: seperti yang disebutkan dalam dokumentasi, informasi ini harus digunakan hanya untuk tujuan debug:

Ini dimaksudkan untuk penggunaan diagnostik. Isi dan format string yang tepat tidak ditentukan, selain sebagai deskripsi upaya terbaik dari tipe tersebut.

Jika Anda ingin representasi tipe Anda tetap sama di antara versi kompiler, Anda harus menggunakan sifat, seperti dalam jawaban phicr .

Boiethios
sumber
1
jawaban terbaik untuk saya, karena kebanyakan devs ingin menggunakan ini untuk keperluan debugging, seperti mencetak kegagalan parsing
kaiser
Persis apa yang saya butuhkan, saya tidak tahu mengapa ini bukan jawaban yang ditandai!
James Poulose
1
@JamesPoulose Karena fungsi ini baru jadi jawaban saya lebih baru.
Boiethios
53

Jika Anda tahu semua tipe sebelumnya, Anda bisa menggunakan ciri untuk menambahkan type_ofmetode:

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

//...

Tidak ada intrik atau apa pun, jadi meskipun lebih terbatas ini adalah satu-satunya solusi di sini yang memberi Anda string dan stabil. (lihat jawaban French Boiethios ) Namun, ini sangat melelahkan dan tidak memperhitungkan parameter tipe, sehingga kami dapat ...

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

Mari kita gunakan:

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

keluaran:

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

Taman Bermain Karat

phicr
sumber
Jawaban ini dapat dipecah menjadi dua jawaban terpisah untuk menghindari mencampuradukkan keduanya.
Prajwal Dhatwalia
2
@PrajwalDhatwalia Saya sudah memikirkan apa yang Anda katakan dan saya merasa puas dengan versi-versi yang saling melengkapi. Versi sifat menunjukkan penyederhanaan dari apa yang versi makro lakukan di bawah tenda, membuat tujuannya lebih jelas. Versi makro, di sisi lain, menunjukkan bagaimana membuat versi sifat secara umum dapat digunakan; itu bukan satu-satunya cara untuk melakukannya, tetapi bahkan menunjukkan bahwa itu mungkin bermanfaat. Singkatnya, ini bisa menjadi dua jawaban tetapi saya merasa keseluruhan lebih besar dari jumlah bagian-bagiannya.
phicr
19

UPD Berikut ini tidak berfungsi lagi. Periksa jawaban Shubham untuk koreksi.

Lihat std::intrinsics::get_tydesc<T>(). Ini dalam keadaan "eksperimental" sekarang, tetapi tidak apa-apa jika Anda hanya meretas sistem tipe.

Lihat contoh berikut:

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

Inilah yang digunakan secara internal untuk mengimplementasikan {:?}formatter yang terkenal .

vbo
sumber
15

** PEMBARUAN ** Ini belum diverifikasi untuk berfungsi kapan saja.

Saya mengumpulkan peti kecil untuk melakukan ini berdasarkan jawaban vbo. Ini memberi Anda makro untuk mengembalikan atau mencetak jenis.

Masukkan ini ke file Cargo.toml Anda:

[dependencies]
t_bang = "0.1.2"

Maka Anda dapat menggunakannya seperti ini:

#[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
sumber
@vbo mengatakan solusinya tidak berfungsi lagi. Apakah milikmu bekerja?
Antony Hatchkins
tidak berfungsi `error [E0554]: #![feature]tidak dapat digunakan pada saluran rilis stabil`
Muhammed Moussa
7

Anda juga dapat menggunakan pendekatan sederhana menggunakan variabel dalam println!("{:?}", var). Jika Debugtidak diterapkan untuk tipe, Anda bisa melihat tipe dalam pesan kesalahan kompiler:

mod some {
    pub struct SomeType;
}

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

( playpen )

Itu kotor tapi berhasil.

DenisKolodin
sumber
8
Jika Debugtidak diterapkan - ini adalah kasus yang sangat tidak mungkin. Salah satu hal pertama yang harus Anda lakukan untuk sebagian besar struct adalah menambahkan #[derive(Debug)]. Saya pikir waktu di mana Anda tidak ingin Debugsangat kecil.
Shepmaster
1
dapatkah Anda menjelaskan apa yang sedang terjadi println!("{:?}", unknown_var);?? Apakah ini interpolasi string tetapi mengapa bagian :?dalam kurung keriting? @DenisKolodin
Julio Marins
Saya memprovokasi kesalahan. Gagasan untuk membiarkan kompiler memberikan info jenis dengan kesalahan. Saya menggunakan Debugkarena tidak diimplementasikan, tetapi Anda dapat menggunakannya {}juga.
DenisKolodin
4

Ada jawaban @ChrisMorgan untuk mendapatkan tipe perkiraan ("float") dalam karat yang stabil dan ada jawaban @ShubhamJain untuk mendapatkan tipe yang tepat ("f64") melalui fungsi yang tidak stabil pada karat malam.

Sekarang inilah cara orang bisa mendapatkan tipe yang tepat (yaitu memutuskan antara f32 dan f64) dalam karat stabil:

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

hasil dalam

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)

Memperbarui

Variasi turbofish

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

sedikit lebih pendek tetapi agak kurang mudah dibaca.

Antony Hatchkins
sumber
Jika Anda sudah tahu itu float, mengatakan di antara f32dan f64dapat dicapai denganstd::mem::size_of_val(&a)
Antony Hatchkins
1

Beberapa jawaban lain tidak berfungsi, tetapi saya menemukan bahwa kotak ketik berfungsi.

  1. Buat proyek baru:

    cargo new test_typename
  2. Ubah Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. Ubah kode sumber Anda

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

Outputnya adalah:

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
sumber
Saya telah mengikuti langkah-langkah yang Anda jelaskan. Sampai hari ini, typenametidak bekerja dengan variabel tanpa tipe eksplisit dalam deklarasi. Menjalankannya dengan my_number dari pertanyaan memberikan kesalahan berikut "tidak dapat memanggil metode type_name_ofpada tipe numerik yang ambigu {float}. Bantuan: Anda harus menentukan tipe untuk pengikatan ini, seperti f32"
Antony Hatchkins
Saya tes 0.65dan bekerja dengan baik: type of c 0.65 0.65 is f64. di sini adalah versi saya:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq
1

Jika Anda hanya ingin tahu jenis variabel Anda selama pengembangan interaktif, saya akan sangat menyarankan menggunakan rls (server bahasa karat) di dalam editor atau ide Anda. Anda kemudian dapat secara permanen mengaktifkan atau mengaktifkan kemampuan melayang dan cukup meletakkan kursor Anda di atas variabel. Sebuah dialog kecil akan muncul dengan informasi tentang variabel termasuk jenisnya.

nrdxp
sumber