Як надрукувати тип змінної в Rust?


238

У мене є таке:

let mut my_number = 32.90;

Як надрукувати тип my_number?

Користуватися typeі type_ofне вийшло. Чи є інший спосіб я надрукувати тип номера?

Відповіді:


177

Якщо ви просто хочете дізнатись тип змінної і готові зробити це під час компіляції, ви можете викликати помилку і змусити компілятора взяти її.

Наприклад, встановіть змінну типу, який не працює :

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

Або викличте недійсний метод :

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

Або отримати доступ до недійсного поля :

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

Вони розкривають тип, який у даному випадку фактично не вирішений повністю. Це називається "змінною з плаваючою комою" у першому прикладі та " {float}" у всіх трьох прикладах; це частково вирішений тип, який може закінчитися f32або f64, залежно від того, яким чином ви його використовуєте. « {float}» Чи не є ім'я правового типу, це означає заповнювач «Я не зовсім впевнений , що це», але це число з плаваючою крапкою. У випадку змінних з плаваючою комою, якщо ви не обмежуєте її, вона за замовчуванням буде f64¹. (Некваліфікований цілочисельний літерал за замовчуванням буде i32.)

Дивитися також:


¹ Ще можуть існувати способи збиття компілятора, щоб він не міг вирішувати між f32і f64; Я не впевнений. Раніше це було так просто 32.90.eq(&32.90), але це стосується як f64зараз, так і погладжує щасливо, тому я не знаю.


4
:?вже досить давно впроваджується вручну. Але ще важливіше, що std::fmt::Debugреалізація (для чого саме :?використовується) для типів чисел більше не містить суфікс, який вказує, для якого типу він є.
Кріс Морган

2
Я дуже часто використовую ці методи, щоб спробувати знайти тип виразу, але це не завжди спрацьовує, особливо коли є параметри типу. Наприклад, компілятор скаже мені, що очікує, ImageBuffer<_, Vec<_>>що це мені не дуже допоможе, коли я намагаюся написати функцію, яка приймає одну з цих речей як параметр. І це відбувається в коді, який інакше компілюється, поки я не додаю :(). Немає кращого способу?
Крістофер Армстронг

2
Це здається трохи заплутаним і неінтуїтивним. Чи буде дуже важко редактору коду, наприклад, Emacs надає тип, коли курсор спирається на змінну, як у багатьох інших мовах? Якщо компілятор може повідомити тип при помилці, напевно він також повинен уже знати тип, коли немає жодної помилки?
xji

1
@JIXiang: Сервер мови іржі все стосується надання цієї інформації IDE, але вона ще не дозріла - її перший випуск альфа був лише пару днів тому. Так, це елдрітський підхід; так, менш езотеричні шляхи досягнення мети неухильно йдуть.
Кріс Морган

1
це дуже схоже на злом. це насправді ідіоматичний спосіб перевірити тип змінної?
заплутався00

109

Існує нестабільна функція, std::intrinsics::type_nameяка може отримати вам назву типу, хоча вам доведеться використовувати нічну збірку іржі (це навряд чи коли-небудь працюватиме в стабільній іржі). Ось приклад:

#![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: поки не стабілізується. Щось подібне навряд чи стабілізується протягом досить тривалого часу, якщо і коли-небудь, і це не здивувало б мене, якщо воно ніколи не стабілізується; це не та річ, яку ви коли-небудь справді повинні робити.
Кріс Морган

2
На іржі щоночі (1.3) він працював тільки при зміні , що на першій лінії#![feature(core_intrinsics)]
AT

1
@DmitriNesteruk: print_type_ofбере посилання ( &T), а не значення ( T), тож вам потрібно пройти, &&strа не &str; тобто, print_type_of(&"foo")а не print_type_of("foo").
Кріс Морган

Ви мали рацію, минули 3 роки, і це все ще не стабілізується.
Антон Кочков

5
std::any::type_nameстабільна після іржі 1,38: stackoverflow.com/a/58119924
Тім Робінсон

66

Ви можете використовувати std::any::type_nameфункцію. Для цього не потрібен нічний компілятор або зовнішня ящик, а результати цілком правильні:

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

Будьте попереджені: як зазначено в документації, ця інформація повинна використовуватися лише для налагодження:

Це призначено для діагностичного використання. Точний вміст і формат рядка не вказуються, окрім опису типу.

Якщо ви хочете, щоб представлення типу залишалося однаковим між версіями компілятора, вам слід використовувати ознаку, як у відповіді phicr .


1
найкраща відповідь для мене, оскільки більшість розробників хочуть використовувати це для цілей налагодження, як, наприклад, помилки розбору друку
кайзер

Саме те, що мені було потрібно, я не знаю, чому це не помічена відповідь!
Джеймс Поулоз

1
@JamesPoulose Оскільки ця функція є недавньою, тому моя відповідь є новішою.
Boiethios

53

Якщо ви заздалегідь знаєте всі типи, можете додати type_ofметод:

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

//...

Ніяких інтрисиків чи нічого, тому, хоча більш обмежене, це єдине рішення, яке отримує вам струну та стабільне. (див. відповідь французького Boiethios ) Однак це дуже трудомістко і не враховує параметрів типу, тому ми могли б ...

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

Давайте використовувати:

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

вихід:

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

Іржавий майданчик


Цю відповідь можна розділити на дві окремі відповіді, щоб уникнути їх змішування.
Праджваль Дхатвалія

2
@PrajwalDhatwalia Я думав про те, що ви сказали, і мені здається, я задоволений тим, як версії доповнюють одна одну. Версія trait показує спрощення того, що робить макро-версія під кришкою, роблячи її цілі зрозумілішими. Макро-версія, з іншого боку, показує, як зробити версію ознак більш загальнопридатною; це не єдиний спосіб зробити це, але навіть показати, що це можливо, це вигідно. Підсумовуючи це, це може бути дві відповіді, але я вважаю, що ціле більше, ніж сума його частин.
фікр

19

UPD Наступне більше не працює. Перевірте відповідь Шубхема на виправлення.

Перевірте std::intrinsics::get_tydesc<T>(). Зараз він перебуває в "експериментальному" стані, але це нормально, якщо ви просто злому навколо системи типів.

Перегляньте наступний приклад:

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

Це те, що використовується внутрішньо для реалізації відомого {:?}форматера.


15

** ОНОВЛЕННЯ ** Це нещодавно не підтверджено для роботи.

Я зібрав трохи ящика, щоб зробити це на основі відповіді vbo. Це дає вам макрос для повернення або роздрукування типу.

Помістіть це у свій файл Cargo.toml:

[dependencies]
t_bang = "0.1.2"

Тоді ви можете використовувати його так:

#[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 каже, що його рішення більше не працює. Ваша робота?
Антоні Хеткінс

не працює `помилка [E0554]: #![feature]не можна використовувати на каналі стабільного випуску`
Мухаммед Муса

7

Ви також можете використовувати простий підхід використання змінної в println!("{:?}", var). Якщо Debugцей тип не реалізований, ви можете побачити тип у повідомленні про помилку компілятора:

mod some {
    pub struct SomeType;
}

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

( манеж )

Це брудно, але це працює.


8
Якщо Debugце не реалізовано - це дуже малоймовірний випадок. Одне з перших речей, яке ви повинні зробити для більшості будь-яких структур - це додати #[derive(Debug)]. Я думаю, що часів, коли ти не хочеш Debug, дуже мало.
Шепмайстер

1
чи можете ви пояснити, що відбувається в println!("{:?}", unknown_var);?? Це струнна інтерполяція, але чому :?всередині фігурні дужки? @DenisKolodin
Julio Marins

Я провокую помилку. Ідея дозволити компілятору надавати інформацію про тип із помилкою. Я використовував, Debugтому що він не реалізований, але ви також можете використовувати його {}.
Денис Колодін

4

Там в @ChrisMorgan відповідь отримати приблизний тип ( «поплавок») в стабільній іржі і є @ShubhamJain відповідь , щоб отримати точний тип ( «F64») через нестабільну функцію в нічних іржі.

Тепер ось спосіб отримати точний тип (тобто вирішити між f32 та f64) у стійкій іржі:

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

призводить до

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)

Оновлення

Варіація турбофіта

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

трохи коротший, але дещо менш читабельний.


Якщо ви вже знаєте, що це - floatрозповідь між ними f32та f64її можна досягтиstd::mem::size_of_val(&a)
Ентоні Хеткінс,

1

Деякі інші відповіді не працюють, але я вважаю, що ящик з набором тексту працює.

  1. Створіть новий проект:

    cargo new test_typename
  2. Змініть Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. Змініть свій вихідний код

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

Вихід:

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

Я дотримувався описаних вами кроків. На сьогоднішній день typenameне працює зі змінними без явного типу в декларації. Якщо запустити його з my_number запитання, виникає така помилка: "не можна викликати метод type_name_ofнеоднозначного числового типу {float}. f32
Довідка

Тест я , 0.65і це добре працює: type of c 0.65 0.65 is f64. ось моя версія:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq

1

Якщо ви просто хочете дізнатися тип вашої змінної під час інтерактивної розробки, я б дуже рекомендував використовувати rls (сервер мови іржі) всередині вашого редактора або ide. Потім можна просто назавжди ввімкнути або переключити здатність навести курсор і просто навести курсор на змінну. У невеликому діалоговому вікні має з’явитися інформація про змінну, включаючи тип.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.