Step 7 of 7
Downcasting with Any (briefly)
Once a value is behind dyn Trait, its concrete type is forgotten. Occasionally you need it back. The standard trait std::any::Any makes that possible: every type without borrowed references implements it automatically, and a &dyn Any can be asked "are you a T?" with downcast_ref::<T>(), which returns Some(&T) or None.
use std::any::Any;
fn describe(value: &dyn Any) -> String {
if let Some(n) = value.downcast_ref::<i32>() {
format!("an i32: {n}")
} else if let Some(s) = value.downcast_ref::<String>() {
format!("a String: {s}")
} else {
String::from("something else")
}
}
fn main() {
let values: Vec<Box<dyn Any>> = vec![Box::new(42), Box::new(String::from("crab")), Box::new(1.5)];
for v in &values {
println!("{}", describe(v.as_ref()));
}
}
an i32: 42
a String: crab
something else
With your own trait
To downcast your own trait objects, make Any a supertrait: trait Event: Any means "every Event is also an Any" (the advanced traits module covers supertraits). Since Rust 1.86, a &dyn Event then converts to a &dyn Any directly (trait upcasting):
use std::any::Any;
trait Event: Any {
fn name(&self) -> String;
}
struct Login {
user: String,
}
struct Logout;
impl Event for Login {
fn name(&self) -> String {
String::from("login")
}
}
impl Event for Logout {
fn name(&self) -> String {
String::from("logout")
}
}
fn main() {
let events: Vec<Box<dyn Event>> = vec![Box::new(Login { user: String::from("ana") }), Box::new(Logout)];
for e in &events {
let any: &dyn Any = e.as_ref();
match any.downcast_ref::<Login>() {
Some(login) => println!("{} by {}", e.name(), login.user),
None => println!("{}", e.name()),
}
}
}
login by ana
logout
Older code does the same with an extra trait method, fn as_any(&self) -> &dyn Any { self }, in every impl. You'll still see it in many codebases.
Use it rarely
Downcasting gives up what trait objects are for: if code keeps asking "which type are you really?", the trait is probably missing a method, or an enum would fit better (previous step). Legitimate uses exist: error handling (Box<dyn Error> and anyhow::Error have downcast_ref to check for a specific error type), plugin systems, and type-keyed maps in game engines and web frameworks.
Common mistakes
- Downcasting the
Boxinstead of what's in it.let any: &dyn Any = &boxed;makes theBox<dyn Any>itself theAnyvalue, sodowncast_ref::<i32>()returnsNone. Useboxed.as_ref()or callboxed.downcast_ref::<i32>()directly. - Types with borrowed references (like
&'a strfields) don't implementAny, because their type depends on a lifetime the run-time check can't see.
Your task
A config loader produced a list of mixed values. Write fn sum_numbers(values: &[Box<dyn Any>]) -> i64, which adds up every i64 and every i32 (converted to i64) and ignores everything else, and fn count_text(values: &[Box<dyn Any>]) -> usize, which counts the Strings and &'static strs.
Challenge 2
Fill in the blank to ask the value whether it's an i32. The program prints got 7 and then not an i32.
Challenge 3
An audit log stores events as Box<dyn Event>. Write fn logins(events: &[Box<dyn Event>]) -> Vec<String> that returns the user of every Login event, in order, by downcasting (upcast each event to &dyn Any first, as in the example). Also implement Event for Purchase (name purchase).