如何在Rust中打印变量的类型?

458

我有以下内容:

let mut my_number = 32.90;

我该如何打印my_number的类型?

使用typetype_of都无法实现。是否有其他方法可以打印出该数字的类型?

18个回答

424
您可以使用 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的答案中所示。


15
对于我来说最好的答案是,大多数开发人员想将其用于调试目的,比如打印解析失败信息。 - kaiser
9
请返回翻译的文本:另请参阅 std::any::type_name_of_val - volante
2
无法使用它来获取“切片/数组引用”的类型 - print_type_of(&a[1..]); - somenickname
这是一个旧答案的副本:https://dev59.com/xGEi5IYBdhLWcg3wCYGY#29168659 - Jean-François Fabre
3
@Jean-FrançoisFabre 那个答案已经被编辑过了。当我写我的答案时,另一个答案推荐了不稳定的内在版本。 - Boiethios
显示剩余2条评论

273
如果你只是想在编译时了解变量的类型,并且愿意这样做,你可以引发一个错误并让编译器检测到它。例如,将变量设置为一个不起作用的类型: 。请注意保留HTML标记。
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}”,这是一个部分确定的类型,可能会变成f32f64,具体取决于您如何使用它。 “{float}”不是合法的类型名称,它是一个占位符,表示“我不完全确定这是什么”,但它是一个浮点数。对于浮点变量,如果您不加限制,它将默认为f64¹。(未经限定的整数字面值将默认为i32。)
另请参见:
¹ 仍然可能有方法使编译器无法确定f32f64之间的选择;我不确定。以前只需使用32.90.eq(&32.90)就可以,但现在它将两者都视为f64,并且可以正常运行,所以我不知道。

4
很长一段时间以来,“:?“ 一直都是手动实现的。但更重要的是,数值类型的 std::fmt::Debug 实现(也就是“:?”使用的)不再包含后缀来指示它属于哪种类型。 - Chris Morgan
6
我经常使用这些技巧来尝试找到表达式的类型,但并不总是有效,特别是涉及类型参数时。例如,编译器会告诉我它期望一个 ImageBuffer<_, Vec<_>>,但当我试图编写一个以这些内容作为参数的函数时,这并没有帮助我太多。而且这种情况通常发生在能够正常编译的代码中,直到我加上 :() 为止。难道没有更好的方法吗? - Christopher Armstrong
2
这似乎有点复杂和不直观。如果代码编辑器(例如Emacs)在光标停留在变量上时提供类型,是否非常困难?如果编译器可以在出错时确定类型,那么当没有错误时它也应该已经知道类型了吧? - xji
5
听起来很像一个黑客,这是否实际上是检查变量类型的惯用方式? - confused00
1
@ChrisMorgan 一个当前的例子:我不得不修改代码才能看到一个部分生成了HashMap<String,i32>,而另一个部分生成了HashMap<&String,&i32>(并导致各种错误,TheBook和Google都没有解决方法),而我目前没有任何“理智”的方法来帮助我看到这一点。这对于Rust初学者来说非常糟糕。 - grin
显示剩余6条评论

142

有一个不稳定的函数std::intrinsics::type_name,可以获取类型名称,但必须使用 Rust nightly 版本(这在稳定版本中几乎不可能工作)。这里有一个例子:

#![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"
}

2
在 rust-nightly (1.3) 上,只有将第一行更改为 #![feature(core_intrinsics)] 时才能正常工作。 - A T
26
std::any::type_name 自 Rust 1.38 版本以来已经稳定:https://dev59.com/xGEi5IYBdhLWcg3wCYGY#58119924/ - Tim Robinson
5
编译时或运行时获取某个东西的类型有其合理的应用场景。例如,对于序列化或者仅仅是调试目的而言。那些写道“你永远不应该这样做”的人可能只是还没有遇到过这些情况。 - BitTickler

81
如果您事先知道所有类型,可以使用特性来添加一个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


这个答案可以分成两个独立的答案,以避免混淆。 - Prajwal
7
我思考了你说的话,感觉两个版本相互补充的效果很不错。Trait版本简化了宏版本在底层的操作,使其目标更加明确。而宏版本则展示了如何使Trait版本更普适;虽然这并不是唯一的方法,但即使仅仅展示了这种可能性也是有优势的。总之,这可以是两个单独的答案,但我认为整体要优于各个部分的总和。 - phicr

27
更新,原回答如下:
怎么样使用 trait 函数 type_name,它可以快速获得类型名称。
pub trait AnyExt {
    fn type_name(&self) -> &'static str;
}

impl<T> AnyExt for T {
    fn type_name(&self) -> &'static str {
        std::any::type_name::<T>()
    }
}

fn main(){
    let my_number = 32.90;
    println!("{}",my_number.type_name());
}

输出:

f64

我编写了一个宏type_of!()来调试,该宏源自于标准库的dbg!()
pub fn type_of2<T>(v: T) -> (&'static str, T) {
    (std::any::type_name::<T>(), v)
}

#[macro_export]
macro_rules! type_of {
    // NOTE: We cannot use `concat!` to make a static string as a format argument
    // of `eprintln!` because `file!` could contain a `{` or
    // `$val` expression could be a block (`{ .. }`), in which case the `eprintln!`
    // will be malformed.
    () => {
        eprintln!("[{}:{}]", file!(), line!());
    };
    ($val:expr $(,)?) => {
        // Use of `match` here is intentional because it affects the lifetimes
        // of temporaries - https://dev59.com/71YM5IYBdhLWcg3whAaH#48732525
        match $val {
            tmp => {
                let (type_,tmp) = $crate::type_of2(tmp);
                eprintln!("[{}:{}] {}: {}",
                    file!(), line!(), stringify!($val), type_);
                tmp
            }
        }
    };
    ($($val:expr),+ $(,)?) => {
        ($($crate::type_of!($val)),+,)
    };
}

fn main(){
    let my_number = type_of!(32.90);
    type_of!(my_number);
}

输出:

[src/main.rs:32] 32.90: f64
[src/main.rs:33] my_number: f64

更新后的答案现在始终返回 'polars_core::datatypes::any_value::AnyValue'。 - Chuck Carlson

22

更新 下面的方法已经不再适用。请查看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>"
}

这是用于实现著名的 {:?} 格式化程序的内部使用内容,请参考链接


现在的稳定版本是 std::any::type_name - Jk Jensen

17

** 更新 ** 最近没有验证其有效性。

我根据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说他的解决方案不再起作用了。你的有效吗? - Antony Hatchkins
错误[E0554]:#![feature]不能在稳定版发布通道上使用。 - Muhammed Moussa
不能说它很稳定,但在夜间版本上它(仍然)可以工作。 - mox

9

版本1.38中新增 std::any::type_name

use std::any::type_name;

fn type_of<T>(_: T) -> &'static str {
    type_name::<T>()
}
fn main() {
    let x = 21;
    let y = 2.5;
    println!("{}", type_of(&y));
    println!("{}", type_of(x));
}

9

您也可以采用简单的方法,在println!("{:?}", var)中使用变量。如果该类型未实现Debug,则可以在编译器的错误消息中看到该类型:

mod some {
    pub struct SomeType;
}

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

(playpen)

虽然有点脏,但确实管用。


8
如果没有实现Debug——虽然这种情况可能性很小。对于大多数结构体,你应该做的第一件事就是添加#[derive(Debug)]。我认为不需要使用Debug的情况非常少。 - Shepmaster
1
你能解释一下 println!("{:?}", unknown_var); 发生了什么吗?这是字符串插值,但为什么花括号里面有 :??@DenisKolodin - Julio Marins
我故意引发错误。这个想法是让编译器在出错时提供类型信息。我使用了 Debug,因为它还没有被实现,但你也可以使用 {} - DenisKolodin
1
惊讶这个答案排名不高。这是最好的方法。 - Urthor

5

如果你只是想在交互式开发中知道你的变量类型,我强烈建议在你的编辑器或IDE中使用rls(rust语言服务器)。然后你可以简单地永久启用或切换悬停功能,将光标放在变量上方。一个小对话框应该会出现,并提供关于变量的信息,包括变量类型。


这是实用的、推荐的答案。然而这也非常不幸。如果我正在调试一个方法,而这个方法在我已经 ssh 连接到的服务器上运行,那么我将没有完整的 IDE 工具集可用。与其让编译器失败或者类似的情况,我宁愿在运行时打印程序的状态。 - Urthor
对于基于终端的编辑器来说,不一定是真的。 - nrdxp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接