O que são From e Into?
Os traits From e Into formam o sistema de conversão de tipos do Rust. Eles permitem transformar um valor de um tipo em outro de forma segura, explícita e idiomática.
From<T>: define como criar um tipo a partir de outro tipoT.Into<U>: define como converter um tipo para outro tipoU.
A beleza desse sistema é que implementar From dá Into de graça. Graças a uma implementação blanket na biblioteca padrão, se você implementa From<A> for B, automaticamente A ganha Into<B>. Por isso, a convenção é sempre implementar From, nunca Into diretamente.
Definição dos Traits
// Definido em std::convert
pub trait From<T>: Sized {
fn from(value: T) -> Self;
}
pub trait Into<T>: Sized {
fn into(self) -> T;
}
A implementação blanket que conecta os dois:
// Na biblioteca padrão (simplificado)
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
Isso significa: para qualquer tipo T, se U implementa From<T>, então T automaticamente implementa Into<U>.
Como Implementar
Implementação de From
From não pode ser derivado — você sempre implementa manualmente:
struct Celsius(f64);
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() {
// Usando From::from explicitamente
let fahr = Fahrenheit::from(Celsius(100.0));
println!("100°C = {}°F", fahr.0); // 212°F
// Usando .into() (funciona por causa da blanket impl)
let celsius: Celsius = Fahrenheit(32.0).into();
println!("32°F = {}°C", celsius.0); // 0°C
}
From na biblioteca padrão
Rust já fornece dezenas de implementações de From:
fn main() {
// String::from(&str)
let s = String::from("olá mundo");
// Vec<u8> from String
let bytes: Vec<u8> = Vec::from(s.clone());
// String from Vec<u8> (pode falhar — use String::from_utf8)
// Mas &str from &[u8] que é válido UTF-8 usa from_utf8_unchecked
// i64 from i32 (conversão sem perda)
let grande: i64 = i64::from(42i32);
// Box<str> from String
let boxed: Box<str> = Box::from(s);
println!("{}", boxed);
}
Exemplos Práticos
Exemplo 1: Conversão de tipos de domínio
#[derive(Debug)]
struct Email(String);
#[derive(Debug)]
struct Usuario {
nome: String,
email: Email,
}
impl From<String> for Email {
fn from(s: String) -> Self {
Email(s)
}
}
impl From<&str> for Email {
fn from(s: &str) -> Self {
Email(s.to_string())
}
}
fn main() {
// From::from
let email1 = Email::from("ana@exemplo.com");
// .into() — o compilador infere o tipo alvo
let email2: Email = String::from("bruno@exemplo.com").into();
let usuario = Usuario {
nome: String::from("Ana"),
email: email1,
};
println!("{:?}", usuario);
}
Exemplo 2: From para conversão de erros
Um dos usos mais poderosos de From é na conversão de erros com o operador ?:
use std::fmt;
use std::num::ParseIntError;
use std::io;
#[derive(Debug)]
enum AppErro {
Io(io::Error),
Parse(ParseIntError),
Validacao(String),
}
impl fmt::Display for AppErro {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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, "Validação: {}", msg),
}
}
}
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)
}
}
// Agora o operador ? converte automaticamente!
fn ler_numero_do_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("Número negativo".into()));
}
Ok(numero)
}
fn main() {
match ler_numero_do_arquivo("numero.txt") {
Ok(n) => println!("Número: {}", n),
Err(e) => eprintln!("Erro: {}", e),
}
}
Exemplo 3: Into em parâmetros de função genérica
Usar Into<T> como bound em parâmetros torna a API mais flexível:
#[derive(Debug)]
struct Mensagem {
texto: String,
}
impl Mensagem {
// Aceita qualquer coisa que possa virar String
fn nova(texto: impl Into<String>) -> Self {
Mensagem {
texto: texto.into(),
}
}
}
fn saudacao(nome: impl Into<String>) -> String {
format!("Olá, {}!", nome.into())
}
fn main() {
// Funciona com &str
let msg1 = Mensagem::nova("olá");
// Funciona com String
let msg2 = Mensagem::nova(String::from("mundo"));
println!("{:?}", msg1);
println!("{:?}", msg2);
// A função aceita ambos os tipos
println!("{}", saudacao("Ana"));
println!("{}", saudacao(String::from("Bruno")));
}
Exemplo 4: Construindo tipos a partir de tuplas
#[derive(Debug)]
struct Ponto {
x: f64,
y: f64,
}
impl From<(f64, f64)> for Ponto {
fn from((x, y): (f64, f64)) -> Self {
Ponto { x, y }
}
}
impl From<[f64; 2]> for Ponto {
fn from(arr: [f64; 2]) -> Self {
Ponto { x: arr[0], y: arr[1] }
}
}
impl From<Ponto> for (f64, f64) {
fn from(p: Ponto) -> Self {
(p.x, p.y)
}
}
fn main() {
let p1 = Ponto::from((3.0, 4.0));
let p2: Ponto = [1.0, 2.0].into();
println!("{:?}", p1); // Ponto { x: 3.0, y: 4.0 }
println!("{:?}", p2); // Ponto { x: 1.0, y: 2.0 }
let tupla: (f64, f64) = p1.into();
println!("{:?}", tupla); // (3.0, 4.0)
}
Exemplo 5: TryFrom para conversões que podem falhar
Quando a conversão pode falhar, use TryFrom em vez de From:
use std::convert::TryFrom;
#[derive(Debug)]
struct Porcentagem(u8);
impl TryFrom<i32> for Porcentagem {
type Error = String;
fn try_from(valor: i32) -> Result<Self, Self::Error> {
if valor < 0 || valor > 100 {
Err(format!("Valor {} fora do intervalo 0-100", valor))
} else {
Ok(Porcentagem(valor as u8))
}
}
}
fn main() {
let p1 = Porcentagem::try_from(85);
let p2 = Porcentagem::try_from(150);
println!("{:?}", p1); // Ok(Porcentagem(85))
println!("{:?}", p2); // Err("Valor 150 fora do intervalo 0-100")
// Também funciona com .try_into()
let p3: Result<Porcentagem, _> = 50i32.try_into();
println!("{:?}", p3); // Ok(Porcentagem(50))
}
Padrões e Boas Práticas
Implemente
From, nãoInto: Sempre implementeFrom<T> for U. Você ganhaInto<U> for Tautomaticamente via blanket impl.Use
Into<T>em parâmetros: Quando uma função aceita um tipo, useimpl Into<String>ouimpl Into<T>para tornar a API mais ergonômica.Conversões de erro com
From: ImplementeFrom<SubErro> for MeuErropara que o operador?converta erros automaticamente.Fromdeve ser infalível: Se a conversão pode falhar, useTryFromem vez deFrom.Fromé um contrato de que a conversão sempre terá sucesso.Não implemente
Frompara si mesmo:From<T> for T(identidade) já é implementado na biblioteca padrão.String::fromvs.to_string(): Ambos convertem&strparaString.String::from("texto")usaFrom, enquanto"texto".to_string()usaDisplay. Ambos são idiomáticos.Conversões numéricas: Para conversões entre tipos numéricos sem perda (como
i32parai64),Fromjá está implementado. Para conversões com possível perda, useasouTryFrom.
Veja Também
- Display e Debug — Display é necessário para formatar a saída de erros convertidos
- Error Trait — usa
Fromextensivamente para conversão de erros - Clone e Copy — outro par de traits de conversão/cópia
- Converter String para Número — exemplo prático de conversão
- Converter Erros — padrões de conversão de erros com From