Modulo std::any em Rust: Any e TypeId

Guia completo do modulo std::any em Rust: Any trait, TypeId, verificacao de tipos em runtime, downcast, type erasure e padroes de plugins.

Visao Geral do Modulo std::any

O modulo std::any fornece mecanismos para verificacao de tipos em tempo de execucao (runtime). Em um sistema de tipos estatico como o de Rust, normalmente todos os tipos sao conhecidos em tempo de compilacao. Porem, ha cenarios em que voce precisa trabalhar com tipos desconhecidos — como sistemas de plugins, registros de eventos heterogeneos ou frameworks de injecao de dependencia.

Os dois tipos principais sao:

  • Any — trait que permite a qualquer tipo 'static ser inspecionado e convertido (downcast) em runtime
  • TypeId — identificador unico de um tipo, util para comparacoes

O std::any e a resposta do Rust para cenarios que em outras linguagens seriam resolvidos com reflexao (reflection). Ele oferece uma forma controlada e segura de fazer type erasure e recuperacao.


Tipos e Funcoes Principais

Trait Any

A trait Any e implementada automaticamente para todo tipo que satisfaca 'static (ou seja, nao contem referencias com lifetimes nao-estaticos).

MetodoDescricao
.type_id()Retorna o TypeId do valor
.is::<T>()Verifica se o valor e do tipo T
.downcast_ref::<T>()Tenta converter para &T (retorna Option)
.downcast_mut::<T>()Tenta converter para &mut T (retorna Option)

Box

MetodoDescricao
.downcast::<T>()Tenta converter para Box<T> (retorna Result)
.is::<T>()Verifica se o conteudo e do tipo T

TypeId

MetodoDescricao
TypeId::of::<T>()Retorna o TypeId de um tipo

A comparacao entre TypeIds e constante e extremamente rapida.


Exemplos Praticos

1. Verificacao de Tipos em Runtime

use std::any::Any;

fn inspecionar(valor: &dyn Any) {
    if valor.is::<i32>() {
        let numero = valor.downcast_ref::<i32>().unwrap();
        println!("E um i32: {numero}");
    } else if valor.is::<String>() {
        let texto = valor.downcast_ref::<String>().unwrap();
        println!("E uma String: \"{texto}\"");
    } else if valor.is::<f64>() {
        let decimal = valor.downcast_ref::<f64>().unwrap();
        println!("E um f64: {decimal}");
    } else {
        println!("Tipo desconhecido (TypeId: {:?})", valor.type_id());
    }
}

fn main() {
    inspecionar(&42i32);
    inspecionar(&String::from("Rust"));
    inspecionar(&3.14f64);
    inspecionar(&true); // Tipo desconhecido
}

2. Colecao Heterogenea com Box

use std::any::Any;

struct Registro {
    campos: Vec<(&'static str, Box<dyn Any>)>,
}

impl Registro {
    fn new() -> Self {
        Registro { campos: Vec::new() }
    }

    fn inserir<T: Any>(&mut self, nome: &'static str, valor: T) {
        self.campos.push((nome, Box::new(valor)));
    }

    fn obter<T: Any>(&self, nome: &str) -> Option<&T> {
        self.campos
            .iter()
            .find(|(n, _)| *n == nome)
            .and_then(|(_, v)| v.downcast_ref::<T>())
    }

    fn remover<T: Any>(&mut self, nome: &str) -> Option<T> {
        let pos = self.campos.iter().position(|(n, _)| *n == nome)?;
        let (_, caixa) = self.campos.remove(pos);
        caixa.downcast::<T>().ok().map(|b| *b)
    }
}

fn main() {
    let mut reg = Registro::new();
    reg.inserir("nome", String::from("Alice"));
    reg.inserir("idade", 30u32);
    reg.inserir("ativo", true);
    reg.inserir("salario", 5500.0f64);

    // Recuperando com tipo correto
    if let Some(nome) = reg.obter::<String>("nome") {
        println!("Nome: {nome}");
    }

    if let Some(idade) = reg.obter::<u32>("idade") {
        println!("Idade: {idade}");
    }

    // Tipo incorreto retorna None
    let errado = reg.obter::<i32>("idade"); // None (e u32, nao i32!)
    println!("Tipo errado: {errado:?}");

    // Removendo com ownership
    if let Some(salario) = reg.remover::<f64>("salario") {
        println!("Salario removido: {salario}");
    }
}

3. Sistema de Plugins com Any

use std::any::{Any, TypeId};
use std::collections::HashMap;

trait Plugin: Any {
    fn nome(&self) -> &str;
    fn executar(&self);
    fn as_any(&self) -> &dyn Any;
}

struct PluginLog {
    prefixo: String,
}

impl Plugin for PluginLog {
    fn nome(&self) -> &str { "Logger" }

    fn executar(&self) {
        println!("[{}] Plugin de log executado", self.prefixo);
    }

    fn as_any(&self) -> &dyn Any { self }
}

struct PluginMetrica {
    contador: u64,
}

impl Plugin for PluginMetrica {
    fn nome(&self) -> &str { "Metricas" }

    fn executar(&self) {
        println!("Metricas coletadas: {} eventos", self.contador);
    }

    fn as_any(&self) -> &dyn Any { self }
}

struct GerenciadorPlugins {
    plugins: HashMap<TypeId, Box<dyn Plugin>>,
}

impl GerenciadorPlugins {
    fn new() -> Self {
        GerenciadorPlugins {
            plugins: HashMap::new(),
        }
    }

    fn registrar<P: Plugin + 'static>(&mut self, plugin: P) {
        let tipo = TypeId::of::<P>();
        println!("Registrando plugin: {}", plugin.nome());
        self.plugins.insert(tipo, Box::new(plugin));
    }

    fn obter<P: Plugin + 'static>(&self) -> Option<&P> {
        let tipo = TypeId::of::<P>();
        self.plugins
            .get(&tipo)
            .and_then(|p| p.as_any().downcast_ref::<P>())
    }

    fn executar_todos(&self) {
        for plugin in self.plugins.values() {
            plugin.executar();
        }
    }
}

fn main() {
    let mut gerenciador = GerenciadorPlugins::new();

    gerenciador.registrar(PluginLog {
        prefixo: "APP".into(),
    });
    gerenciador.registrar(PluginMetrica {
        contador: 1500,
    });

    // Executar todos os plugins
    gerenciador.executar_todos();

    // Acessar um plugin especifico pelo tipo
    if let Some(log) = gerenciador.obter::<PluginLog>() {
        println!("Prefixo do logger: {}", log.prefixo);
    }
}

4. TypeId para Comparacao Eficiente

use std::any::TypeId;

fn tipo_para_nome(id: TypeId) -> &'static str {
    if id == TypeId::of::<i32>() {
        "i32"
    } else if id == TypeId::of::<String>() {
        "String"
    } else if id == TypeId::of::<Vec<u8>>() {
        "Vec<u8>"
    } else {
        "desconhecido"
    }
}

fn nome_do_tipo<T: 'static>() -> &'static str {
    tipo_para_nome(TypeId::of::<T>())
}

fn main() {
    println!("{}", nome_do_tipo::<i32>());      // i32
    println!("{}", nome_do_tipo::<String>());    // String
    println!("{}", nome_do_tipo::<Vec<u8>>());   // Vec<u8>
    println!("{}", nome_do_tipo::<bool>());      // desconhecido

    // TypeId e Copy, barato de comparar e pode ser usado como chave
    let id1 = TypeId::of::<String>();
    let id2 = TypeId::of::<String>();
    let id3 = TypeId::of::<&str>();

    assert_eq!(id1, id2);
    assert_ne!(id1, id3); // String e &str sao tipos diferentes!
}

5. Type Erasure e Recuperacao

use std::any::Any;

struct Cache {
    dados: Vec<Box<dyn Any>>,
}

impl Cache {
    fn new() -> Self {
        Cache { dados: Vec::new() }
    }

    fn armazenar<T: Any>(&mut self, valor: T) -> usize {
        let indice = self.dados.len();
        self.dados.push(Box::new(valor));
        indice
    }

    fn recuperar<T: Any>(&self, indice: usize) -> Option<&T> {
        self.dados.get(indice)?.downcast_ref::<T>()
    }

    fn recuperar_owned<T: Any>(mut self, indice: usize) -> Option<T> {
        if indice >= self.dados.len() {
            return None;
        }
        let item = self.dados.swap_remove(indice);
        item.downcast::<T>().ok().map(|b| *b)
    }
}

fn main() {
    let mut cache = Cache::new();

    let i_str = cache.armazenar(String::from("cache de dados"));
    let i_num = cache.armazenar(42u64);
    let i_vec = cache.armazenar(vec![1, 2, 3]);

    // Recuperar como referencia tipada
    if let Some(texto) = cache.recuperar::<String>(i_str) {
        println!("String: {texto}");
    }

    if let Some(num) = cache.recuperar::<u64>(i_num) {
        println!("Numero: {num}");
    }

    if let Some(lista) = cache.recuperar::<Vec<i32>>(i_vec) {
        println!("Vec: {lista:?}");
    }

    // Tipo errado retorna None — sem panic
    assert!(cache.recuperar::<bool>(i_str).is_none());
}

Padroes Comuns

Padrao as_any() em Traits

Quando voce tem uma trait customizada e precisa fazer downcast, inclua um metodo as_any():

use std::any::Any;

trait Componente: Any {
    fn atualizar(&mut self);
    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

Este padrao e necessario porque dyn MinhaTraitCustomizada nao pode ser convertido diretamente para dyn Any.

Restricao ‘static

Any requer que o tipo seja 'static, ou seja, nao contenha referencias com lifetimes nao-estaticos. Isso significa que voce nao pode usar Any com tipos como &'a str (onde 'a nao e 'static). Use String ou tipos owned.

TypeId como Chave de HashMap

TypeId implementa Hash e Eq, sendo perfeito como chave para mapas tipo-valor:

use std::any::TypeId;
use std::collections::HashMap;

type TypeMap = HashMap<TypeId, Box<dyn std::any::Any>>;

Quando Usar (e Quando Nao Usar)

Use std::any quando:

  • Precisar de colecoes heterogeneas (valores de tipos diferentes)
  • Implementando sistemas de plugins ou extensoes
  • Criando registros de servicos ou injecao de dependencia
  • Precisar comparar tipos em runtime

Nao use std::any quando:

  • Enums resolverem o problema (preferivel a type erasure)
  • Generics forem suficientes (resolucao em compile-time e melhor)
  • Trait objects normais (dyn Trait) atenderem a necessidade
  • Estiver tentando emular heranca de classes OOP

Dica de performance: TypeId::of::<T>() e uma operacao const — e resolvida em tempo de compilacao. downcast_ref envolve apenas uma comparacao de TypeId, sendo extremamente rapida (tempo constante).

Limitacao importante: Any so funciona com tipos 'static. Se voce precisa de type erasure com lifetimes arbitrarios, considere usar trait objects com generics ou crates como typemap.


Veja Tambem