How to print the type of a variable in Rust: 3 approaches from beginner to pro

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

How to print the type of a variable in Rust without losing your mind

\n\n

Have you ever been writing Rust code and suddenly frozen with the question: "What type is this variable?" If so — you are not alone. The question "How do I print the type of a variable in Rust" on Stack Overflow has gathered thousands of views, and for good reason. Rust is a language with a strict but smart type system, and sometimes the compiler knows more than we do.

\n\n

In this article, we will cover three ways to print the type of a variable: from a simple debugging trick to a professional macro. You will learn when to use which method and stop guessing what beast is hiding behind let x = ....

\n\n

Why doesn't Rust just let you print the type?

\n\n

In languages like Python or JavaScript, you can write print(type(x)) — and voilà. Rust doesn't have this luxury for two reasons:

\n\n
    \n
  • Static typing: types are known at compile time, but are not always obvious to the programmer (especially when working with closures or complex generics).
  • \n
  • Lack of reflection: Rust does not store type information at runtime by default — this would impact performance.
  • \n
\n\n

But the Rust developers have left us a few loopholes. Let's take a look at them.

\n\n

Method 1: std::any::type_name — the simplest (but not perfect)

\n\n

The Rust standard library provides the std::any::type_name function. It returns a string with the type name. Here's how it works:

\n\n
use std::any::type_name;\n\nfn print_type<T>(_: &T) {\n    println!("{}", type_name::<T>());\n}\n\nfn main() {\n    let x = 42;\n    let y = "hello";\n    let z = vec![1, 2, 3];\n    \n    print_type(&x); // i32\n    print_type(&y); // &str\n    print_type(&z); // Vec<i32>\n}
\n\n

Pros:

\n
    \n
  • Minimal code — works out of the box.
  • \n
  • Suitable for debugging and quick checks.
  • \n
\n\n

Cons:

\n
    \n
  • The result depends on the compiler: the string may differ across Rust versions or platforms.
  • \n
  • Cannot be used in production logic where decisions need to be made based on the type.
  • \n
\n\n

Live example: debugging a complex expression

\n\n

Imagine you are working with iterators and method chains. What type will you end up with?

\n\n
use std::any::type_name;\n\nfn main() {\n    let data = vec![1, 2, 3, 4, 5];\n    let result = data.iter()\n        .filter(|x| *x % 2 == 0)\n        .map(|x| x * 2)\n        .collect::<Vec<_>>();\n    \n    // Want to know the type before collect?\n    let intermediate = data.iter()\n        .filter(|x| *x % 2 == 0)\n        .map(|x| x * 2);\n    \n    println!("Type of intermediate: {}", type_name::<decltype!(intermediate)>()); \n    // Oops, that won't work — need to pass a reference\n    println!("Type of intermediate: {}", type_name_of_val(&intermediate));\n}\n\nfn type_name_of_val<T>(_: &T) -> &str {\n    type_name::<T>()\n}
\n\n

This trick will save you when the compiler gives an error and you don't understand what type it expects.

\n\n

Method 2: Using compiler errors — a clever trick

\n\n

Sometimes the best way to find out the type is to make the compiler tell you itself. There is an old but working trick: assign the variable to a non-existent type.

\n\n
fn main() {\n    let x = 42;\n    let y: () = x; // Error! expected (), found i32\n}
\n\n

The compiler will output something like:

\n
error[E0308]: mismatched types\n --> src/main.rs:3:18\n  |\n3 |     let y: () = x;\n  |            --   ^ expected `()`, found `i32`\n  |            |\n  |            expected due to this
\n\n

Pros:

\n
    \n
  • No imports required.
  • \n
  • Works even in environments without std (no_std).

Blogs

Book Recommendations