Как напечатать тип переменной в Rust?

240

У меня есть следующее:

let mut my_number = 32.90;

Как мне распечатать тип my_number?

Использование typeи type_ofне работает. Есть ли другой способ, которым я могу напечатать тип номера?

user2431012
источник

Ответы:

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.)

Смотрите также:


Still Могут существовать способы сбить с толку компилятор, чтобы он не мог выбрать между f32и f64; Я не уверен. Раньше все было так же просто 32.90.eq(&32.90), но к этому f64относились как сейчас, так и к счастью, так что я не знаю.

Крис Морган
источник
4
:?в течение довольно долгого времени был реализован вручную. Но что более важно, std::fmt::Debugреализация (для этого и :?используется) для числовых типов больше не включает суффикс, указывающий, к какому типу он относится.
Крис Морган
2
Я часто использую эти методы, чтобы попытаться найти тип выражения, но это не всегда работает, особенно когда задействованы параметры типа. Компилятор, например, скажет мне, что он ожидает, ImageBuffer<_, Vec<_>>что мне не очень поможет, когда я пытаюсь написать функцию, которая принимает одну из этих вещей в качестве параметра. И это происходит в коде, который в противном случае компилируется, пока я не добавлю :(). Нет лучшего способа?
Кристофер Армстронг
2
Это кажется немного запутанным и неинтуитивным. Будет ли это очень сложно для редактора кода, например, Emacs предоставляет тип, когда курсор находится на переменной, как во многих других языках? Если компилятор может сообщить тип при ошибке, он также должен знать тип, когда ошибки нет?
xji
1
@JIXiang: сервер Rust Language Server предназначен для предоставления этой информации в IDE, но он еще не готов - его первая альфа-версия была всего пару дней назад. Да, это полдрич-подход; да, менее эзотерические способы достижения цели неуклонно появляются.
Крис Морган
1
это звучит очень похоже на взломать. это на самом деле идиоматический способ проверить тип переменной?
confused00
109

Существует нестабильная функция, std::intrinsics::type_nameкоторая может дать вам имя типа, хотя вы должны использовать ночную сборку Rust (это вряд ли когда-либо будет работать в стабильном Rust). Вот пример:

#![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").
Крис Морган
6
std::any::type_nameстабильна с момента ржавчины 1.38: stackoverflow.com/a/58119924
Тим Робинсон
1
Получение типа чего-либо во время компиляции / выполнения имеет допустимые варианты использования. Например, для сериализации - или просто для отладки. Те, кто пишет: «Никогда не делай такого», сами никогда не сталкивались с этими вариантами использования.
BitTickler
68

Вы можете использовать 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 .

Boiethios
источник
1
лучший ответ для меня, так как большинство разработчиков хотят использовать это для целей отладки, таких как печать ошибок разбора
kaiser
Именно то, что мне было нужно, я не знаю, почему это не отмеченный ответ!
Джеймс Полус
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

Rust Playground

phicr
источник
Этот ответ можно разбить на два отдельных ответа, чтобы не перепутать их.
Prajwal Dhatwalia
2
@PrajwalDhatwalia Я думал о том, что ты сказал, и я чувствую, что доволен тем, как версии дополняют друг друга. Черта версии показывает упрощение того, что макро версия делает под капотом, делая ее цели более ясными. Макро версия, с другой стороны, показывает, как сделать черту более универсальной; это не единственный способ сделать это, но даже показать, что это возможно, выгодно. Таким образом, это может быть два ответа, но я чувствую, что целое больше, чем сумма его частей.
фото
19

UPD Следующее больше не работает. Проверьте ответ Shubham для исправления.

Проверьте 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"
}
mpiccolo
источник
@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
Хулио Маринс
Я провоцирую ошибку. Идея позволить компилятору предоставлять информацию о типе с ошибкой. Я использовал, Debugпотому что это не реализовано, но вы также можете использовать {}.
ДенисКолодин
4

Есть ответ @ChrisMorgan, чтобы получить приблизительный тип ("float") в стабильной ржавчине, и есть ответ @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)
Энтони Hatchkins
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
Flyq
источник
Я выполнил шаги, которые вы описали. На сегодняшний день typenameне работает с переменными без явного типа в объявлении. Запуск его my_number из вопроса дает следующую ошибку: «не удается вызвать метод type_name_ofдля неоднозначного числового типа {float}. f32
Help
Я проверяю, 0.65и это хорошо работает type of c 0.65 0.65 is f64. вот моя версия:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq
1

Если вы просто хотите знать тип вашей переменной во время интерактивной разработки, я настоятельно рекомендую использовать rls (сервер языка ржавчины) внутри вашего редактора или ide. Затем вы можете просто включить или отключить возможность наведения и просто навести курсор на переменную. В небольшом диалоге должна появиться информация о переменной, включая тип.

nrdxp
источник