【问题标题】:How do I print the type of a variable?如何在 Rust 中打印变量的类型?
【发布时间】:2023-03-26 07:53:01
【问题描述】:

我有以下几点:

let mut my_number = 32.90;

如何打印my_number的类型?

使用typetype_of 不起作用。还有其他方法可以打印号码的类型吗?

【问题讨论】:

    标签: types rust


    【解决方案1】:

    更新,下面的原始答案

    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!()来调试,原来是stddbg!()

    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://stackoverflow.com/a/48732525/1063961
            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
    

    【讨论】:

      【解决方案2】:

      如果你事先知道所有的类型,你可以使用traits添加一个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's answer)但是,它非常费力并且不会考虑类型参数,所以我们可以...

      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

      【讨论】:

      • 这个答案可以分成两个单独的答案,以避免混淆两者。
      • @PrajwalDhatwalia 我一直在考虑你所说的,我觉得我对这些版本如何相互补充感到满意。 trait 版本简化了宏版本在幕后所做的事情,使其目标更加清晰。另一方面,宏版本展示了如何使 trait 版本更普遍可用;这不是唯一的方法,但即使表明它是可能的也是有利的。总之,这可能是两个答案,但我觉得整体大于部分之和。
      【解决方案3】:

      您可以使用std::any::type_name。以下是在没有&amp; 的情况下可以使用的原始数据类型的示例。

      use std::any::type_name;
      
      fn type_of<T>(_: T) -> &'static str {
          type_name::<T>()
      }
      
      fn main() {
          let str1 = "Rust language";
          let str2 = str1;
          println!("str1 is:  {}, and the type is {}.", str1, type_of(str1));
          println!("str2 is: {}, and the type is {}.", str2, type_of(str2));
          let bool1 = true;
          let bool2 = bool1;
          println!("bool1 is {}, and the type is {}.", bool1, type_of(bool1));
          println!("bool2 is {}, and the type is {}.", bool2, type_of(bool2));
          let x1 = 5;
          let x2 = x1;
          println!("x1 is {}, and the type is {}.", x1, type_of(x1));
          println!("x2 is {}, and the type is {}.", x2, type_of(x2));
          let a1 = 'a';
          let a2 = a1;
          println!("a1 is {}, and the type is {}.", a1, type_of(a1));
          println!("a2 is {}, and the type is {}.", a2, type_of(a2));
          let tup1= ("hello", 5, 'c');
          let tup2 = tup1;
          println!("tup1 is {:?}, and the type is {}.", tup1, type_of(tup1));
          println!("tup2 is {:?}, and the type is {}.", tup2, type_of(tup2));
          let array1: [i32; 3] = [0; 3];
          let array2 = array1;
          println!("array1 is {:?}, and the type is {}.", array1, type_of(array1));
          println!("array2 is {:?}, and the type is {}.", array2, type_of(array2));
          let array: [i32; 5] = [0, 1, 2, 3, 4];
          let slice1 = &array[0..3];
          let slice2 = slice1;
          println!("slice1 is {:?}, and the type is {}.", slice1, type_of(slice1));
          println!("slice2 is {:?}, and the type is {}.", slice2, type_of(slice2));
      }
      

      输出是

      str1 is:  Rust language, and the type is &str.
      str2 is: Rust language, and the type is &str.
      bool1 is true, and the type is bool.
      bool2 is true, and the type is bool.
      x1 is 5, and the type is i32.
      x2 is 5, and the type is i32.
      a1 is a, and the type is char.
      a2 is a, and the type is char.
      tup1 is ("hello", 5, 'c'), and the type is (&str, i32, char).
      tup2 is ("hello", 5, 'c'), and the type is (&str, i32, char).
      array1 is [0, 0, 0], and the type is [i32; 3].
      array2 is [0, 0, 0], and the type is [i32; 3].
      slice1 is [0, 1, 2], and the type is &[i32].
      slice2 is [0, 1, 2], and the type is &[i32].
      

      【讨论】:

      【解决方案4】:

      ** 更新 ** 这在最近的任何时候都没有经过验证。

      我根据 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]可能无法在稳定发布频道上使用`
      • 不能谈论稳定,但在夜间它(仍然)有效。
      【解决方案5】:

      您可以使用std::any::type_name 函数。这不需要 nightly 编译器或外部 crate,结果非常正确:

      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's answer

      【讨论】:

      • 对我来说最好的答案,因为大多数开发人员都希望将其用于调试目的,例如打印解析失败
      • 另见 std::any::type_name_of_val
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        @ChrisMorgan answer 在稳定的 rust 中获得近似类型(“float”),@ShubhamJain answer 在夜间 rust 中通过不稳定的函数获得精确类型(“f64”)。

        现在有一种方法可以在稳定的 rust 中获得精确的类型(即在 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,那么f32f64之间的区分可以通过std::mem::size_of_val(&amp;a)来完成
        【解决方案8】:

        如果您只想找出变量的类型并愿意在编译时进行,您可能会导致错误并让编译器拾取它。

        例如set the variable to a type which doesn't work:

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

        call an invalid method:

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

        access an invalid field:

        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(&amp;32.90) 一样简单,但现在它把两者都当作f64 并愉快地前进,所以我不知道。

        【讨论】:

        • :? 已经手动实现了很长时间。但更重要的是,数字类型的 std::fmt::Debug 实现(因为这是 :? 使用的)不再包含表示它属于哪种类型的后缀。
        • 我经常使用这些技术来尝试查找表达式的类型,但它并不总是有效,尤其是在涉及类型参数时。例如,编译器会告诉我它期待一个ImageBuffer&lt;_, Vec&lt;_&gt;&gt;,当我尝试编写一个将这些东西之一作为参数的函数时,这对我没有多大帮助。这发生在否则会编译的代码中,直到我添加:()。有没有更好的办法?
        • 这似乎有点复杂和不直观。代码编辑器是否会非常困难,例如当光标停留在变量上时,Emacs 提供类型,就像在许多其他语言中一样?如果编译器可以在错误时告诉类型,那么它肯定也应该在没有任何错误时已经知道类型?
        • @JIXIang:Rust 语言服务器就是为 IDE 提供这些信息,但它还不成熟——它的第一个 alpha 版本才几天前。是的,这是一种可怕的方法;是的,实现目标的不那么深奥的方法正在稳步出现。
        • 这听起来很像 hack。这实际上是检查变量类型的惯用方法吗?
        【解决方案9】:

        其他一些答案不起作用,但我发现 typename 板条箱有效。

        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 运行它会出现以下错误“无法在不明确的数字类型{float} 上调用方法type_name_of。帮助:您必须为此绑定指定一个类型,例如f32
        • 我测试了0.65,它运行良好:type of c 0.65 0.65 is f64。这是我的版本:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
        【解决方案10】:

        UPD 以下不再起作用。检查Shubham's answer 进行更正。

        查看std::intrinsics::get_tydesc&lt;T&gt;()。它现在处于“实验”状态,但如果您只是在类型系统周围进行黑客攻击,也可以。

        查看以下示例:

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

        这就是used internally 来实现著名的{:?} 格式化程序。

        【讨论】:

          【解决方案11】:

          有一个不稳定的函数std::intrinsics::type_name 可以为您获取类型的名称,尽管您必须使用 Rust 的夜间构建(这不太可能在稳定的 Rust 中工作)。 Here’s an example:

          #![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:在稳定之前不要。如果有的话,这样的事情不太可能在相当长的一段时间内稳定下来——如果它从未稳定下来,我也不会感到惊讶;这不是你真正应该做的事情。
          • 在 rust-nightly (1.3) 上,它仅在将第一行更改为 #![feature(core_intrinsics)] 时才有效
          • @DmitriNesteruk: print_type_of 正在获取引用 (&amp;T),而不是值 (T),因此您必须通过 &amp;&amp;str 而不是 &amp;str;也就是说,print_type_of(&amp;"foo") 而不是 print_type_of("foo")
          • std::any::type_name 自 rust 1.38 起稳定:stackoverflow.com/a/58119924
          • 在编译/运行时获取某物的类型具有有效的用例。例如,用于序列化 - 或仅用于调试目的。那些写“你永远不应该做这样的事情”的人自己根本就没有遇到过这些用例。
          【解决方案12】:

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

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

          (playpen)

          它很脏,但它可以工作。

          【讨论】:

          • 如果 Debug 没有实现——不过这种情况不太可能发生。对于大多数结构,您应该做的第一件事就是添加#[derive(Debug)]。我认为你不想要Debug 的时间非常少。
          • 你能解释一下println!("{:?}", unknown_var);发生了什么吗?它是字符串插值,但为什么 :? 在大括号内? @DenisKolodin
          • 我引发了错误。让编译器提供错误类型信息的想法。我使用了Debug,因为它没有实现,但你也可以使用{}
          猜你喜欢
          • 1970-01-01
          • 2014-07-23
          • 2022-01-25
          • 1970-01-01
          • 2016-08-10
          • 1970-01-01
          相关资源
          最近更新 更多