Any în Rust

Trait-ul care permite aflarea tipului concret la rulare: downcast_ref::<T>() pe &dyn Any sau Box<dyn Any>.

Sintaxă
use std::any::Any;

let x: &dyn Any = &valoare;
if let Some(n) = x.downcast_ref::<i32>() { ... }

Ce face Any

Orice tip 'static (care nu conține referințe temporare) implementează automat Any. Prin &dyn Any sau Box<dyn Any> poți ține valori de tipuri diferite și le poți verifica la rulare: is::<T>() răspunde da/nu, downcast_ref::<T>() dă Option<&T>, iar Box::downcast::<T>() recuperează valoarea.

Any este o unealtă rară. În cod obișnuit, un enum cu câte o variantă pentru fiecare tip sau un trait propriu sunt mai clare și mai sigure. Any apare la pluginuri, la sisteme generice de stocare și la valorile primite din panic::catch_unwind. Pentru numele unui tip, folosit la depanare, există std::any::type_name::<T>().

Ce returnează

type_id() returnează un TypeId; downcast_ref returnează Option<&T>.

Exemple

Exemplul 1: O listă de valori de tipuri diferite

use std::any::Any;

fn descrie(v: &dyn Any) -> String {
    if let Some(n) = v.downcast_ref::<i32>() {
        format!("număr întreg: {n}")
    } else if let Some(s) = v.downcast_ref::<String>() {
        format!("text: {s:?}")
    } else if let Some(s) = v.downcast_ref::<&str>() {
        format!("text literal: {s:?}")
    } else if v.is::<bool>() {
        String::from("valoare booleană")
    } else {
        String::from("tip necunoscut")
    }
}

fn main() {
    let valori: Vec<Box<dyn Any>> = vec![
        Box::new(42),
        Box::new(String::from("Iași")),
        Box::new("Cluj"),
        Box::new(true),
        Box::new(3.14),
    ];
    for v in &valori {
        println!("{}", descrie(v.as_ref()));
    }
}

Rezultat

număr întreg: 42
text: "Iași"
text literal: "Cluj"
valoare booleană
tip necunoscut

Exemplul 2: Box::downcast și type_name

use std::any::{type_name, Any};

fn main() {
    let cutie: Box<dyn Any> = Box::new(vec![9, 10, 8]);
    match cutie.downcast::<Vec<i32>>() {
        Ok(note) => println!("note: {note:?}, media: {:.2}", note.iter().sum::<i32>() as f64 / note.len() as f64),
        Err(_) => println!("nu este Vec<i32>"),
    }
    println!("{}", type_name::<Option<String>>());
    println!("{}", type_name::<f64>());
}

Rezultat

note: [9, 10, 8], media: 9.00
core::option::Option<alloc::string::String>
f64

Greșeli frecvente

  • Faci downcast_ref::<String>() pe o valoare care este de fapt &str (sau invers) și primești None. Tipul trebuie să fie exact același.
  • Apelezi metodele pe &Box<dyn Any> în loc de &dyn Any: Box este el însuși Any, deci verificarea se face pe tipul cutiei. Folosește v.as_ref() sau &**v.
  • Folosești Any unde un enum ar fi suficient și pierzi verificarea completă a cazurilor la compilare.

Comenzi înrudite

  • Sized: Trait marker pentru tipurile cu dimensiune cunoscută la compilare; ?Sized permite și str, [T], dyn Trait.
  • Send și Sync: Trait-uri marker automate prin care compilatorul previne erorile de concurență: Send = poate trece în alt fir, Sync = poate fi împărțit.
  • fmt::Debug: Trait-ul pentru afișarea de depanare cu {:?} și {:#?}; se obține aproape mereu cu #[derive(Debug)].