Loading page…
Rust walkthroughs
Loading page…
TypeId represents a globally unique identifier for a type. It's part of Rust's std::any module and enables runtime type identification, which is useful for implementing dynamic typing patterns, type erasure, and trait object downcasting.
Key concepts:
TypeId::of::<T>()'static types (no lifetimes)When to use TypeId:
Limitations:
'static lifetimeuse std::any::TypeId;
fn main() {
// Get TypeId for different types
let type_id_i32 = TypeId::of::<i32>();
let type_id_string = TypeId::of::<String>();
let type_id_vec = TypeId::of::<Vec<i32>>();
println!("TypeId of i32: {:?}", type_id_i32);
println!("TypeId of String: {:?}", type_id_string);
println!("TypeId of Vec<i32>: {:?}", type_id_vec);
// Compare TypeIds
let same_type = TypeId::of::<i32>();
assert_eq!(type_id_i32, same_type);
// Different types have different TypeIds
assert_ne!(type_id_i32, type_id_string);
}use std::any::{Any, TypeId};
fn is_type<T: 'static>(value: &dyn Any) -> bool {
value.type_id() == TypeId::of::<T>()
}
fn main() {
let value: i32 = 42;
let any_ref: &dyn Any = &value;
println!("Is i32: {}", is_type::<i32>(any_ref));
println!("Is String: {}", is_type::<String>(any_ref));
println!("Is u32: {}", is_type::<u32>(any_ref));
}use std::any::TypeId;
use std::collections::HashMap;
struct TypeRegistry {
data: HashMap<TypeId, String>,
}
impl TypeRegistry {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
fn register<T: 'static>(&mut self, description: &str) {
self.data.insert(TypeId::of::<T>(), description.to_string());
}
fn describe<T: 'static>(&self) -> Option<&str> {
self.data.get(&TypeId::of::<T>()).map(|s| s.as_str())
}
}
fn main() {
let mut registry = TypeRegistry::new();
registry.register::<i32>("32-bit signed integer");
registry.register::<String>("Heap-allocated string");
registry.register::<Vec<u8>>("Byte vector");
println!("i32: {}", registry.describe::<i32>().unwrap());
println!("String: {}", registry.describe::<String>().unwrap());
println!("f64: {:?}", registry.describe::<f64>());
}use std::any::TypeId;
use std::collections::HashMap;
// Type-erased storage with TypeId keys
struct TypeMap {
data: HashMap<TypeId, Box<dyn std::any::Any>>,
}
impl TypeMap {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
fn insert<T: 'static>(&mut self, value: T) {
self.data.insert(TypeId::of::<T>(), Box::new(value));
}
fn get<T: 'static>(&self) -> Option<&T> {
self.data
.get(&TypeId::of::<T>())
.and_then(|v| v.downcast_ref::<T>())
}
fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.data
.get_mut(&TypeId::of::<T>())
.and_then(|v| v.downcast_mut::<T>())
}
fn remove<T: 'static>(&mut self) -> Option<T> {
self.data
.remove(&TypeId::of::<T>())
.and_then(|v| v.downcast::<T>().ok())
}
}
fn main() {
let mut map = TypeMap::new();
map.insert(42i32);
map.insert(String::from("Hello"));
map.insert(vec![1.0, 2.0, 3.0f64]);
println!("i32: {}", map.get::<i32>().unwrap());
println!("String: {}", map.get::<String>().unwrap());
println!("Vec<f64>: {:?}", map.get::<Vec<f64>>().unwrap());
// Modify a value
if let Some(s) = map.get_mut::<String>() {
s.push_str(", World!");
}
println!("Modified: {}", map.get::<String>().unwrap());
}use std::any::{Any, TypeId};
trait Widget: Any {
fn draw(&self);
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
// Helper to get TypeId from dyn Widget
fn widget_type_id(widget: &dyn Widget) -> TypeId {
widget.as_any().type_id()
}
struct Button {
label: String,
}
impl Widget for Button {
fn draw(&self) {
println!("Button: {}", self.label);
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
struct TextBox {
content: String,
}
impl Widget for TextBox {
fn draw(&self) {
println!("TextBox: {}", self.content);
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
fn main() {
let widgets: Vec<Box<dyn Widget>> = vec![
Box::new(Button { label: String::from("Click me") }),
Box::new(TextBox { content: String::from("Hello") }),
];
for widget in &widgets {
widget.draw();
// Check specific type
if widget.as_any().is::<Button>() {
println!(" -> This is a button!");
}
}
// Downcast to specific type
if let Some(button) = widgets[0].as_any().downcast_ref::<Button>() {
println!("Button label: {}", button.label);
}
}use std::any::{type_name, TypeId};
fn debug_type_info<T: 'static>(value: &T) {
println!("Type name: {}", type_name::<T>());
println!("Type ID: {:?}", TypeId::of::<T>());
println!("Size: {} bytes", std::mem::size_of::<T>());
println!("Alignment: {} bytes", std::mem::align_of::<T>());
}
fn main() {
debug_type_info(&42i32);
println!();
debug_type_info(&String::from("hello"));
println!();
debug_type_info(&vec![1, 2, 3]);
}use std::any::TypeId;
// TypeId::of is const fn, so can be used in const contexts
const I32_TYPE_ID: TypeId = TypeId::of::<i32>();
const STRING_TYPE_ID: TypeId = TypeId::of::<String>();
fn is_i32_type(id: TypeId) -> bool {
id == I32_TYPE_ID
}
fn main() {
println!("i32 TypeId: {:?}", I32_TYPE_ID);
println!("String TypeId: {:?}", STRING_TYPE_ID);
let id = TypeId::of::<i32>();
println!("Is i32: {}", is_i32_type(id));
}use std::any::TypeId;
use std::collections::HashMap;
use std::time::Instant;
struct Cache {
values: HashMap<TypeId, (Box<dyn std::any::Any>, Instant)>,
}
impl Cache {
fn new() -> Self {
Self {
values: HashMap::new(),
}
}
fn get_or_insert<T: 'static>(&mut self, f: impl FnOnce() -> T) -> &T {
let key = TypeId::of::<T>();
if !self.values.contains_key(&key) {
let value = Box::new(f());
self.values.insert(key, (value, Instant::now()));
}
self.values
.get(&key)
.and_then(|(v, _)| v.downcast_ref::<T>())
.unwrap()
}
fn clear<T: 'static>(&mut self) {
self.values.remove(&TypeId::of::<T>());
}
fn clear_all(&mut self) {
self.values.clear();
}
}
fn main() {
let mut cache = Cache::new();
// First access computes the value
let val1 = cache.get_or_insert(|| {
println!("Computing expensive value...");
42
});
println!("Got: {}", val1);
// Second access uses cached value
let val2 = cache.get_or_insert(|| {
println!("This won't print");
99
});
println!("Got again: {}", val2);
}use std::any::{Any, TypeId};
use std::collections::HashMap;
// Event handling with typed events
trait Event: Any {
fn type_name(&self) -> &'static str;
}
struct ClickEvent {
x: i32,
y: i32,
}
impl Event for ClickEvent {
fn type_name(&self) -> &'static str {
"ClickEvent"
}
}
struct KeyEvent {
key: char,
}
impl Event for KeyEvent {
fn type_name(&self) -> &'static str {
"KeyEvent"
}
}
struct EventHandler {
handlers: HashMap<TypeId, Box<dyn Fn(&dyn Any)>>,
}
impl EventHandler {
fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
fn on<E: Event + 'static>(&mut self, handler: impl Fn(&E) + 'static) {
let key = TypeId::of::<E>();
self.handlers.insert(key, Box::new(move |event: &dyn Any| {
if let Some(e) = event.downcast_ref::<E>() {
handler(e);
}
}));
}
fn dispatch(&self, event: &dyn Event) {
if let Some(handler) = self.handlers.get(&event.type_id()) {
handler(event.as_any());
}
}
}
impl dyn Event {
fn as_any(&self) -> &dyn Any {
// This requires Self: Any, which is satisfied
unsafe { &*(self as *const Self as *const dyn Any) }
}
}
fn main() {
let mut handler = EventHandler::new();
handler.on(|e: &ClickEvent| {
println!("Click at ({}, {})", e.x, e.y);
});
handler.on(|e: &KeyEvent| {
println!("Key pressed: {}", e.key);
});
// Dispatch events
let click = ClickEvent { x: 100, y: 200 };
let key = KeyEvent { key: 'a' };
handler.dispatch(&click);
handler.dispatch(&key);
}use std::any::TypeId;
fn main() {
// Same type always has same TypeId
assert_eq!(TypeId::of::<i32>(), TypeId::of::<i32>());
assert_eq!(TypeId::of::<String>(), TypeId::of::<String>());
// Different types have different TypeIds
assert_ne!(TypeId::of::<i32>(), TypeId::of::<u32>());
assert_ne!(TypeId::of::<Vec<i32>>(), TypeId::of::<Vec<u32>>());
// Type aliases don't create new TypeIds
type MyInt = i32;
assert_eq!(TypeId::of::<i32>(), TypeId::of::<MyInt>());
println!("All assertions passed!");
}use std::any::TypeId;
use std::marker::PhantomData;
// Type-scoped identifier
struct TypedId<T: 'static> {
id: u64,
_marker: PhantomData<T>,
}
impl<T: 'static> TypedId<T> {
fn new(id: u64) -> Self {
Self {
id,
_marker: PhantomData,
}
}
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
}
// Verify type at runtime
fn verify_type<T: 'static>(id: &TypedId<T>) -> bool {
id.type_id() == TypeId::of::<T>()
}
fn main() {
let int_id = TypedId::<i32>::new(1);
let str_id = TypedId::<String>::new(2);
println!("int_id type: {:?}", int_id.type_id());
println!("str_id type: {:?}", str_id.type_id());
println!("int_id verified: {}", verify_type(&int_id));
}use std::any::TypeId;
use std::collections::{HashMap, HashSet};
fn main() {
// TypeId implements Hash and Eq
let mut set = HashSet::new();
set.insert(TypeId::of::<i32>());
set.insert(TypeId::of::<String>());
set.insert(TypeId::of::<Vec<u8>>());
println!("Set contains i32: {}", set.contains(&TypeId::of::<i32>()));
println!("Set contains f64: {}", set.contains(&TypeId::of::<f64>()));
// Use in HashMap
let mut type_sizes: HashMap<TypeId, usize> = HashMap::new();
type_sizes.insert(TypeId::of::<i32>(), std::mem::size_of::<i32>());
type_sizes.insert(TypeId::of::<String>(), std::mem::size_of::<String>());
println!("Size of i32: {}", type_sizes[&TypeId::of::<i32>()]);
println!("Size of String: {}", type_sizes[&TypeId::of::<String>()]);
}use std::any::TypeId;
// This won't compile - types with lifetimes can't have TypeId
// fn get_lifetime_type_id<T>() -> TypeId {
// TypeId::of::<&'a T>() // Error: lifetime parameter 'a
// }
fn main() {
// &str has a lifetime, but the reference type itself is 'static
// when it points to a 'static str
let static_str: &'static str = "hello";
let str_ref_id = TypeId::of::<&'static str>();
println!("&'static str TypeId: {:?}", str_ref_id);
// Different lifetime = different type = different TypeId
// TypeId::of::<&'a str>() where 'a != 'static would be different
// But we can't demonstrate this without named lifetime parameters
// Generic type with different type arguments
assert_ne!(TypeId::of::<Vec<i32>>(), TypeId::of::<Vec<String>>());
// Closure types are unique (different TypeIds for different closures)
let closure1 = || 1;
let closure2 = || 1;
// These have different types even though they look the same!
// TypeId::of::<typeof(closure1)>() != TypeId::of::<typeof(closure2)>()
}TypeId Characteristics:
| Property | Description |
|----------|-------------|
| Uniqueness | Each type has exactly one TypeId |
| Stability | May change between compilations |
| Constraints | Only 'static types can have TypeId |
| Traits | Clone, Copy, Eq, Hash |
Key Functions:
| Function | Description |
|----------|-------------|
| TypeId::of::<T>() | Get TypeId for type T (const fn) |
| any.type_id() | Get TypeId from dyn Any |
| type_name::<T>() | Get type name as string (debug) |
Comparison with Other Languages:
| Language | Feature | Rust Equivalent |
|----------|---------|-----------------|
| Java | instanceof | Any::downcast_ref + TypeId |
| C++ | typeid | TypeId::of |
| C# | typeof | TypeId::of + type_name |
| Go | reflect.TypeOf | TypeId::of |
When to Use TypeId:
| Scenario | Appropriate? | |----------|-------------| | Downcasting trait objects | ✅ Yes (via Any) | | Type registries | ✅ Yes | | Runtime type checking | ✅ Yes | | Type-indexed storage | ✅ Yes | | Normal generic code | ❌ Use generics directly | | Persistent serialization | ❌ Not stable |
Key Points:
'static typeAny trait for runtime type identificationTypeId::of::<T>() is a const fntype_name for debugging