Как вывести тип переменной в Rust? (аналог type() из Python или typeof из JS)
Стандартный способ: std::any::type_name
В Rust действительно нет прямого аналога typeof из динамических языков, потому что Rust — статически типизированный язык, и типы известны на этапе компиляции. Однако для отладки можно использовать функцию std::any::type_name, которая возвращает строковое представление типа переменной. Вот как это работает:
fn print_type<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let a = 10;
let b = 3.14;
let c = "hello";
let d = vec![1, 2, 3];
print_type(&a); // i32
print_type(&b); // f64
print_type(&c); // &str
print_type(&d); // alloc::vec::Vec<i32>
}
Обратите внимание: std::any::type_name — это не рантайм-функция в классическом смысле. Она использует механизм мономорфизации дженериков: для каждого конкретного типа компилятор генерирует свою версию функции, в которую подставляет имя типа на этапе компиляции. Это полностью безопасно и не требует динамической диспетчеризации.
Универсальный макрос для быстрой отладки
Чтобы не писать каждый раз вспомогательную функцию, можно создать простой макрос:
macro_rules! print_type {
($x:expr) => {
println!("Тип '{}' — {}", stringify!($x), std::any::type_name::<_>());
};
}
fn main() {
let a = 10;
let b = 3.14;
print_type!(a); // Тип 'a' — i32
print_type!(b); // Тип 'b' — f64
print_type!(a + b as f64); // Тип '(a + b as f64)' — f64
}
Макрос использует stringify! для вывода исходного кода выражения и type_name::<_>() для автоматического вывода типа.
Решение вашей проблемы с типами
Ваш пример с ошибкой компиляции можно исправить явным приведением типов:
fn main() {
let a = 10; // i32
let b = 3.14; // f64
// Вариант 1: привести a к f64
let c = a as f64 + b;
// Вариант 2: привести b к i32 (потеря точности!)
let c2 = a + b as i32;
println!("{}", c); // 13.14
println!("{}", c2); // 13
}
Важные замечания
type_nameне гарантирует уникальность или читаемость имени — это зависит от реализации компилятора. На практике имена стабильны, но могут быть "замусорены" путями модулей.- Для сложных вложенных типов (например, замыканий) имена могут быть очень длинными.
- Не используйте
type_nameдля логики программы — только для отладки и логирования. - Если вам нужно именно рантайм-определение типа (например, для объектов типажа
Any), используйтеstd::any::Anyс методамиtype_id()иdowncast_ref().
Альтернатива для трейтов
Если ваша переменная реализует трейт std::fmt::Debug, можно использовать форматирование {:?} для вывода значения, но не типа:
println!("{:?}", vec![1, 2, 3]); // [1, 2, 3]
Это не покажет тип, но поможет отладить содержимое.
<<Использование крейта infer для более удобного вывода
Если вы часто отлаживаете типы, рекомендую установить крейт infer (версия 0.1.0 или новее). Он предоставляет макрос infer_type!, который выводит тип в более читаемом формате:
// Добавьте в Cargo.toml:
// [dependencies]
// infer = "0.1"
use infer::infer_type;
fn main() {
let a = 10;
let b = 3.14;
let closure = |x: i32| x * 2;
infer_type!(a); // a: i32
infer_type!(b); // b: f64
infer_type!(closure); // closure: [closure@src/main.rs:8:17: 8:30]
}
Преимущество infer в том, что он автоматически подставляет имя переменной, а не показывает сырой путь типа. Это особенно полезно для замыканий и сложных дженериков.
Ручной вывод с помощью аннотаций
Самый простой способ узнать тип — заставить компилятор сказать вам его через намеренную ошибку:
fn main() {
let a = 10;
let b: () = a; // Ошибка: expected `()`, found `i32`
// Компилятор скажет: "expected `()`, found `i32`"
}
Этот "метод" не требует никаких зависимостей, но работает только один раз за запуск компиляции. Для быстрой проверки — идеально, для постоянного использования — лучше макросы.
Сравнение подходов
std::any::type_name— встроенный, без зависимостей, но имена могут быть "шумными".inferкрейт — чище вывод, но требует внешней зависимости.- Намеренная ошибка — быстро, грязно, не для продакшена.
Выбирайте подходящий способ под вашу задачу. Для простого обучения достаточно std::any::type_name.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги