Modulo std::convert em Rust: From, Into e TryFrom

Guia completo do modulo std::convert em Rust: From, Into, TryFrom, TryInto, AsRef, AsMut, Infallible e padroes de conversao idiomaticos.

Visao Geral do Modulo std::convert

O modulo std::convert define as traits padrao de conversao do Rust. Essas traits formam o contrato universal para transformar valores de um tipo em outro, de forma segura e idiomatica.

As quatro traits principais formam dois pares:

  • From<T> / Into<T> — conversoes infaliveis (que nunca falham)
  • TryFrom<T> / TryInto<T> — conversoes faliveis (que podem retornar erro)

Alem dessas, o modulo inclui:

  • AsRef<T> / AsMut<T> — conversoes baratas por referencia
  • Infallible — tipo de erro impossivel (usado com From)
  • identity — funcao identidade (retorna o proprio valor)

Essas traits sao tao fundamentais que a maioria esta incluida no prelude, ou seja, voce nao precisa importa-las explicitamente.


Traits e Funcoes Principais

From e Into

TraitMetodoDescricao
From<T>fn from(T) -> SelfConverte de T para Self
Into<T>fn into(self) -> TConverte self para T

Regra: Implemente From<T>. A implementacao de Into<T> e gerada automaticamente pela blanket implementation. Nunca implemente Into diretamente.

TryFrom e TryInto

TraitMetodoDescricao
TryFrom<T>fn try_from(T) -> Result<Self, Error>Conversao falivel
TryInto<T>fn try_into(self) -> Result<T, Error>Conversao falivel

AsRef e AsMut

TraitMetodoDescricao
AsRef<T>fn as_ref(&self) -> &TReferencia barata
AsMut<T>fn as_mut(&mut self) -> &mut TReferencia mutavel barata

Outros

ItemDescricao
InfallibleTipo de erro que nunca pode ser construido
identity(x)Retorna x sem modificacao

Exemplos Praticos

1. Implementando From para Tipos Customizados

#[derive(Debug)]
struct Email {
    endereco: String,
}

// Conversao de &str para Email
impl From<&str> for Email {
    fn from(s: &str) -> Self {
        Email {
            endereco: s.to_lowercase(),
        }
    }
}

// Conversao de String para Email
impl From<String> for Email {
    fn from(s: String) -> Self {
        Email {
            endereco: s.to_lowercase(),
        }
    }
}

#[derive(Debug)]
struct Celsius(f64);

#[derive(Debug)]
struct Fahrenheit(f64);

impl From<Celsius> for Fahrenheit {
    fn from(c: Celsius) -> Self {
        Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
    }
}

impl From<Fahrenheit> for Celsius {
    fn from(f: Fahrenheit) -> Self {
        Celsius((f.0 - 32.0) * 5.0 / 9.0)
    }
}

fn main() {
    // From explicito
    let email = Email::from("Usuario@Email.COM");
    println!("{email:?}"); // Email { endereco: "usuario@email.com" }

    // Into implicito (gerado automaticamente pelo From)
    let email2: Email = "outro@teste.com".into();
    println!("{email2:?}");

    // Conversao de temperatura
    let agua_fervendo = Celsius(100.0);
    let em_fahrenheit: Fahrenheit = agua_fervendo.into();
    println!("{em_fahrenheit:?}"); // Fahrenheit(212.0)

    let corpo = Fahrenheit(98.6);
    let em_celsius: Celsius = corpo.into();
    println!("{em_celsius:?}"); // Celsius(37.0)
}

2. TryFrom para Conversoes Faliveis

use std::num::TryFromIntError;

#[derive(Debug)]
struct Idade(u8);

#[derive(Debug)]
enum IdadeErro {
    MuitoJovem,
    MuitoVelho,
    NumeroInvalido(TryFromIntError),
}

impl std::fmt::Display for IdadeErro {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            IdadeErro::MuitoJovem => write!(f, "Idade deve ser pelo menos 0"),
            IdadeErro::MuitoVelho => write!(f, "Idade deve ser no maximo 150"),
            IdadeErro::NumeroInvalido(e) => write!(f, "Numero invalido: {e}"),
        }
    }
}

impl TryFrom<i32> for Idade {
    type Error = IdadeErro;

    fn try_from(valor: i32) -> Result<Self, Self::Error> {
        if valor < 0 {
            Err(IdadeErro::MuitoJovem)
        } else if valor > 150 {
            Err(IdadeErro::MuitoVelho)
        } else {
            // u8::try_from(i32) pode falhar se i32 > 255,
            // mas ja validamos que esta entre 0 e 150
            Ok(Idade(valor as u8))
        }
    }
}

fn main() {
    let idades = [25, -5, 200, 0, 100];

    for &valor in &idades {
        match Idade::try_from(valor) {
            Ok(idade) => println!("{valor} -> {idade:?}"),
            Err(e) => println!("{valor} -> Erro: {e}"),
        }
    }

    // TryInto tambem funciona (gerado automaticamente)
    let resultado: Result<Idade, _> = 30i32.try_into();
    println!("\n30.try_into() = {resultado:?}");

    // Conversoes entre inteiros (TryFrom ja implementado na stdlib)
    let grande: i64 = 1_000_000;
    let tentativa: Result<i16, _> = grande.try_into();
    println!("i64({grande}) -> i16: {tentativa:?}"); // Err(TryFromIntError)
}

3. AsRef para Funcoes Genericas

use std::path::Path;

// AsRef<str> aceita tanto &str quanto String
fn contar_palavras(texto: impl AsRef<str>) -> usize {
    texto.as_ref().split_whitespace().count()
}

// AsRef<Path> aceita &str, String, PathBuf, &Path...
fn arquivo_existe(caminho: impl AsRef<Path>) -> bool {
    caminho.as_ref().exists()
}

// AsRef<[u8]> aceita &[u8], Vec<u8>, String, &str...
fn calcular_hash(dados: impl AsRef<[u8]>) -> u64 {
    let bytes = dados.as_ref();
    // Hash simples para exemplo
    bytes.iter().fold(0u64, |acc, &b| {
        acc.wrapping_mul(31).wrapping_add(b as u64)
    })
}

fn main() {
    // contar_palavras aceita diferentes tipos de string
    println!("{}", contar_palavras("Rust e incrivel")); // 3
    println!("{}", contar_palavras(String::from("Ola mundo"))); // 2

    // arquivo_existe aceita diferentes tipos de caminho
    println!("{}", arquivo_existe("/tmp"));
    println!("{}", arquivo_existe(String::from("/nao/existe")));

    // calcular_hash aceita bytes de diferentes fontes
    println!("{}", calcular_hash("texto"));
    println!("{}", calcular_hash(vec![1u8, 2, 3]));
    println!("{}", calcular_hash(b"bytes" as &[u8]));
}

4. From para Conversao de Erros

Um dos usos mais poderosos de From e a conversao automatica de erros com o operador ?:

use std::io;
use std::num::ParseIntError;

#[derive(Debug)]
enum AppErro {
    Io(io::Error),
    Parse(ParseIntError),
    Validacao(String),
}

// Implementando From para cada tipo de erro
impl From<io::Error> for AppErro {
    fn from(e: io::Error) -> Self {
        AppErro::Io(e)
    }
}

impl From<ParseIntError> for AppErro {
    fn from(e: ParseIntError) -> Self {
        AppErro::Parse(e)
    }
}

impl std::fmt::Display for AppErro {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AppErro::Io(e) => write!(f, "Erro de I/O: {e}"),
            AppErro::Parse(e) => write!(f, "Erro de parse: {e}"),
            AppErro::Validacao(msg) => write!(f, "Validacao: {msg}"),
        }
    }
}

// Agora o operador ? converte automaticamente os erros!
fn processar_arquivo(caminho: &str) -> Result<i32, AppErro> {
    let conteudo = std::fs::read_to_string(caminho)?; // io::Error -> AppErro
    let numero: i32 = conteudo.trim().parse()?;       // ParseIntError -> AppErro

    if numero < 0 {
        return Err(AppErro::Validacao("Numero deve ser positivo".into()));
    }

    Ok(numero * 2)
}

fn main() {
    match processar_arquivo("/tmp/numero.txt") {
        Ok(resultado) => println!("Resultado: {resultado}"),
        Err(e) => println!("Erro: {e}"),
    }
}

5. Padroes Avancados de Conversao

// Padrao: construtor generico com Into
struct Configuracao {
    host: String,
    porta: u16,
    nome: String,
}

impl Configuracao {
    // Aceita qualquer tipo que pode ser convertido em String
    fn new(host: impl Into<String>, porta: u16, nome: impl Into<String>) -> Self {
        Configuracao {
            host: host.into(),
            porta,
            nome: nome.into(),
        }
    }
}

// Padrao: colecao de itens convertidos
fn criar_lista_de_strings(itens: &[&str]) -> Vec<String> {
    itens.iter().map(|&s| String::from(s)).collect()
}

// Padrao: identity como funcao de primeira classe
fn processar<F>(dados: Vec<i32>, transformar: F) -> Vec<i32>
where
    F: Fn(i32) -> i32,
{
    dados.into_iter().map(transformar).collect()
}

fn main() {
    // Construtor generico — aceita &str e String
    let cfg = Configuracao::new("localhost", 8080, String::from("meu_app"));
    println!("{}:{} ({})", cfg.host, cfg.porta, cfg.nome);

    // identity — util com APIs que exigem uma funcao
    let dados = vec![1, 2, 3];
    let sem_mudanca = processar(dados.clone(), std::convert::identity);
    let dobrados = processar(dados, |x| x * 2);
    println!("Identidade: {sem_mudanca:?}");
    println!("Dobrados: {dobrados:?}");

    // Conversoes embutidas na stdlib
    let n: i64 = i64::from(42i32);     // i32 -> i64 (sempre seguro)
    let s: String = String::from("ola"); // &str -> String
    let v: Vec<u8> = Vec::from(&[1u8, 2, 3][..]); // &[u8] -> Vec<u8>

    println!("{n}, {s}, {v:?}");
}

Padroes Comuns

Quando Implementar From vs TryFrom

SituacaoUse
Conversao sempre funcionaFrom
Conversao pode falharTryFrom
Conversao de subconjunto (ex: u8 -> u32)From
Conversao de superconjunto (ex: u32 -> u8)TryFrom
Parsing de stringsTryFrom ou FromStr
Conversao de errosFrom

Cadeia de Conversao com Into em Parametros

Prefira impl Into<T> em parametros de funcao para flexibilidade:

fn saudar(nome: impl Into<String>) {
    let nome = nome.into();
    println!("Ola, {nome}!");
}

AsRef vs Borrow

  • AsRef<T> e para conversao generica de referencia
  • Borrow<T> e para quando o tipo emprestado tem as mesmas garantias de Eq/Hash/Ord (importante para chaves de HashMap)

Quando Usar (e Quando Nao Usar)

Use std::convert quando:

  • Precisar converter entre tipos de forma idiomatica
  • Quiser que o operador ? converta erros automaticamente
  • Quiser funcoes genericas que aceitem multiplos tipos de entrada
  • Precisar de validacao na conversao (TryFrom)

Nao use std::convert quando:

  • A conversao envolve efeitos colaterais (I/O, rede) — use metodos explicitos
  • A conversao e ambigua ou tem multiplas interpretacoes — use metodos nomeados
  • Dois tipos tem From reciproco e isso pode causar confusao
  • A conversao e custosa — documente claramente ou use um metodo explicito

Dica de performance: From/Into tem custo zero de abstracao — o compilador inline e otimiza as conversoes. AsRef tambem e custo zero, pois retorna apenas uma referencia.


Veja Tambem