2

We can use std::any::Any to gather different types into a Box.

use std::any::Any;

fn foo(value: Box<dyn Any>) {
    if let Some(string) = value.downcast_ref::<String>() {
        println!("String: {}", *string);
    } else if let Some(int) = value.downcast_ref::<i32>() {
        println!("i32: {}", *int);
    }
}

fn main() {
    let x = Box::new("hello".to_owned());
    let y = Box::new(123);
    foo(x);
    foo(y);
}

we can also use downcast to identify the type of a value in a Box. I learnt that the types in C++ can be determined by virtual functions, according to this question, How does RTTI work?. However, types like i32 can also be downcasted in Rust. How does it work?

273K
  • 29,503
  • 10
  • 41
  • 64
flsflsfls
  • 97
  • 5
  • 2
    The question is not about C++, thus the tag is removed. BTW RTTI has nothing to do with virtual functions. – 273K Mar 28 '23 at 03:07

2 Answers2

5

When you do let y = Box::new(123) here, y is inferred to be Box<dyn Any>, not Box<i32>. This is essentially equivalent to writing let y = Box::new(123) as Box<dyn Any>, which might make it more obvious what's happening: you're upcasting the box to add the typeid of i32 when constructing it so that you can downcast it back later.

Aplet123
  • 33,825
  • 1
  • 29
  • 55
4

However, types like i32 can also be downcasted in Rust.

As Aplet123 notes, because you're using them as arguments to foo your x and y are inferred and immediately created as Box<dyn Any>, not Box<String> or Box<i32>.

The downcasting feature then comes from dyn Any, Box<dyn Any> is a "fat pointer", it contains the original data but also a vtable of Any for whatever type is erased, which provides the wapped concrete type's TypeId. This, then, can be checked against the id of the requested type (the downcasting target), and in case of match we "know" the value is of the correct type, and can then transmute the pointer:

    // Get `TypeId` of the type this function is instantiated with.
    let t = TypeId::of::<T>();

    // Get `TypeId` of the type in the trait object (`self`).
    let concrete = self.type_id();

    // Compare both `TypeId`s on equality.
    if t == concrete {
        unsafe { &*(self as *const dyn Any as *const T) }
    } else {
        None
    }

Essentially, dyn Any provides a limited ad-hoc RTTI, rather than have it as a general language feature like C++.

Masklinn
  • 34,759
  • 3
  • 38
  • 57