O que são AsRef, AsMut e Borrow?
Esses três traits lidam com conversão de referências — transformar uma referência de um tipo em uma referência de outro tipo, sem cópia. Embora pareçam similares, cada um tem um propósito distinto:
AsRef<T>: “posso ser visto como uma referência aT”. Usado para tornar funções genéricas que aceitam diferentes tipos de entrada. Exemplo clássico: aceitar tantoStringquanto&str, ou tantoPathBufquanto&Path.AsMut<T>: versão mutável deAsRef. “Posso ser visto como uma referência mutável aT”.Borrow<T>: similar aAsRef, mas com um contrato adicional: o tipo emprestado deve ter o mesmo comportamento deHash,EqeOrdque o tipo original. Essencial para chaves deHashMapeHashSet.
A regra prática: use AsRef para funções genéricas comuns, use Borrow para containers que dependem de hash e igualdade.
Definição dos Traits
// std::convert::AsRef
pub trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
// std::convert::AsMut
pub trait AsMut<T: ?Sized> {
fn as_mut(&mut self) -> &mut T;
}
// std::borrow::Borrow
pub trait Borrow<Borrowed: ?Sized> {
fn borrow(&self) -> &Borrowed;
}
Diferenças-chave
| Aspecto | AsRef | Borrow |
|---|---|---|
| Propósito | Conversão genérica de referência | Empréstimo com contrato de equivalência |
| Contrato Hash/Eq | Nenhum | Obrigatório: hash e eq devem ser consistentes |
| Uso principal | Parâmetros de função | Chaves de HashMap/HashSet |
| Implementação blanket | T: AsRef<T> para todo T | T: Borrow<T> para todo T |
Como Implementar
AsRef manual
struct CaminhoConfig {
base: String,
arquivo: String,
}
impl AsRef<str> for CaminhoConfig {
fn as_ref(&self) -> &str {
&self.arquivo
}
}
impl AsRef<std::path::Path> for CaminhoConfig {
fn as_ref(&self) -> &std::path::Path {
std::path::Path::new(&self.arquivo)
}
}
fn imprimir_caminho(caminho: impl AsRef<std::path::Path>) {
println!("Caminho: {}", caminho.as_ref().display());
}
fn main() {
let config = CaminhoConfig {
base: String::from("/etc"),
arquivo: String::from("/etc/app/config.toml"),
};
imprimir_caminho(&config);
imprimir_caminho("/usr/local/bin");
imprimir_caminho(String::from("/home/user"));
}
Borrow manual
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
#[derive(Debug)]
struct EmailNormalizado {
original: String,
normalizado: String,
}
impl EmailNormalizado {
fn novo(email: &str) -> Self {
EmailNormalizado {
original: email.to_string(),
normalizado: email.to_lowercase(),
}
}
}
// Hash usa o valor normalizado
impl Hash for EmailNormalizado {
fn hash<H: Hasher>(&self, state: &mut H) {
self.normalizado.hash(state);
}
}
// Eq usa o valor normalizado
impl PartialEq for EmailNormalizado {
fn eq(&self, other: &Self) -> bool {
self.normalizado == other.normalizado
}
}
impl Eq for EmailNormalizado {}
// Borrow<str> permite buscar no HashMap com &str
impl Borrow<str> for EmailNormalizado {
fn borrow(&self) -> &str {
&self.normalizado
}
}
fn main() {
use std::collections::HashSet;
let mut emails = HashSet::new();
emails.insert(EmailNormalizado::novo("Ana@Exemplo.COM"));
emails.insert(EmailNormalizado::novo("bruno@exemplo.com"));
// Busca com &str (graças a Borrow<str>)
// Precisamos buscar com o valor normalizado
println!("Contém ana@exemplo.com? {}", emails.contains("ana@exemplo.com"));
println!("Contém bruno@exemplo.com? {}", emails.contains("bruno@exemplo.com"));
}
Exemplos Práticos
Exemplo 1: AsRef para APIs flexíveis
fn contar_palavras(texto: impl AsRef<str>) -> usize {
texto.as_ref().split_whitespace().count()
}
fn contem_palavra(texto: impl AsRef<str>, palavra: impl AsRef<str>) -> bool {
let texto = texto.as_ref().to_lowercase();
let palavra = palavra.as_ref().to_lowercase();
texto.contains(&palavra)
}
fn main() {
// Funciona com &str
println!("{}", contar_palavras("Rust é incrível")); // 3
// Funciona com String
let frase = String::from("A linguagem Rust é segura e rápida");
println!("{}", contar_palavras(&frase)); // 7
println!("{}", contar_palavras(frase.clone())); // 7
// Funciona com diferentes combinações
println!("{}", contem_palavra("Rust é incrível", "RUST")); // true
println!("{}", contem_palavra(frase, String::from("segura"))); // true
}
Exemplo 2: AsRef para operações de arquivo
use std::path::Path;
use std::fs;
fn arquivo_existe(caminho: impl AsRef<Path>) -> bool {
caminho.as_ref().exists()
}
fn extensao(caminho: impl AsRef<Path>) -> Option<String> {
caminho
.as_ref()
.extension()
.and_then(|ext| ext.to_str())
.map(|s| s.to_string())
}
fn listar_info(caminho: impl AsRef<Path>) {
let p = caminho.as_ref();
println!("Caminho: {}", p.display());
println!("Existe: {}", p.exists());
println!("É arquivo: {}", p.is_file());
println!("Extensão: {:?}", extensao(p));
}
fn main() {
// Aceita &str
println!("Existe? {}", arquivo_existe("/etc/hosts"));
// Aceita String
let caminho = String::from("/tmp/teste.txt");
listar_info(&caminho);
// Aceita &Path
let path = Path::new("/usr/bin/rustc");
listar_info(path);
// Aceita PathBuf
let pathbuf = std::path::PathBuf::from("/home");
listar_info(&pathbuf);
}
Exemplo 3: Borrow em HashMap
use std::collections::HashMap;
use std::borrow::Borrow;
fn main() {
let mut capitais: HashMap<String, String> = HashMap::new();
capitais.insert(
String::from("Brasil"),
String::from("Brasília"),
);
capitais.insert(
String::from("Argentina"),
String::from("Buenos Aires"),
);
capitais.insert(
String::from("Chile"),
String::from("Santiago"),
);
// HashMap::get aceita qualquer tipo que implemente Borrow<str>
// String implementa Borrow<str>, então podemos buscar com &str
if let Some(capital) = capitais.get("Brasil") {
println!("Capital do Brasil: {}", capital);
}
// Também funciona com String
let pais = String::from("Argentina");
if let Some(capital) = capitais.get(&pais) {
println!("Capital da Argentina: {}", capital);
}
// contains_key também usa Borrow
println!("Tem Chile? {}", capitais.contains_key("Chile"));
// remove também usa Borrow
capitais.remove("Chile");
println!("Após remover Chile: {:?}", capitais.keys().collect::<Vec<_>>());
}
Exemplo 4: AsMut para buffers
fn preencher_zeros(buffer: &mut impl AsMut<[u8]>) {
let slice = buffer.as_mut();
for byte in slice.iter_mut() {
*byte = 0;
}
}
fn capitalizar_ascii(texto: &mut impl AsMut<[u8]>) {
let bytes = texto.as_mut();
if let Some(primeiro) = bytes.first_mut() {
if primeiro.is_ascii_lowercase() {
*primeiro = primeiro.to_ascii_uppercase();
}
}
}
fn main() {
// Com Vec<u8>
let mut buffer = vec![1u8, 2, 3, 4, 5];
preencher_zeros(&mut buffer);
println!("{:?}", buffer); // [0, 0, 0, 0, 0]
// Com array
let mut arr = [10u8, 20, 30];
preencher_zeros(&mut arr);
println!("{:?}", arr); // [0, 0, 0]
// Com Vec<u8> como texto
let mut texto = b"rust".to_vec();
capitalizar_ascii(&mut texto);
println!("{}", String::from_utf8_lossy(&texto)); // Rust
}
Exemplo 5: Quando usar AsRef vs Borrow vs Deref
use std::borrow::Borrow;
use std::path::Path;
// AsRef: para conversões genéricas em parâmetros de função
fn processar_texto(input: impl AsRef<str>) {
let s: &str = input.as_ref();
println!("Processando: '{}'", s);
}
// Borrow: para containers que dependem de Hash/Eq
fn buscar_em_mapa<K, V>(mapa: &std::collections::HashMap<K, V>, chave: &str) -> Option<&V>
where
K: std::hash::Hash + Eq + Borrow<str>,
{
mapa.get(chave)
}
// Deref: acontece automaticamente via coercion
fn tamanho(s: &str) -> usize {
s.len()
}
fn main() {
let string = String::from("exemplo");
// AsRef: chamada explícita com as_ref()
processar_texto(&string);
processar_texto("literal");
// Borrow: usado internamente por HashMap
let mut mapa = std::collections::HashMap::new();
mapa.insert(String::from("chave"), 42);
println!("{:?}", buscar_em_mapa(&mapa, "chave"));
// Deref: coerção automática &String → &str
println!("Tamanho: {}", tamanho(&string));
}
Quando Usar Cada Um?
Use AsRef<T> quando:
- Sua função precisa de uma referência a
Te você quer aceitar vários tipos de entrada - Não há requisito de consistência entre Hash/Eq
- Cenário típico:
AsRef<str>,AsRef<Path>,AsRef<[u8]>
Use Borrow<T> quando:
- Você está implementando um container ou coleção
- A busca/comparação precisa ser consistente com Hash e Eq
- Cenário típico: chaves de
HashMap, elementos deHashSet
Use Deref quando:
- Você está criando um smart pointer ou tipo wrapper
- Quer que todos os métodos do tipo interno estejam disponíveis
- Cenário típico:
Box<T>,Rc<T>,Arc<T>, newtypes
Padrões e Boas Práticas
Prefira
AsRef<str>a&String: Funções que aceitamimpl AsRef<str>são mais genéricas e idiomáticas que funções com&String.O contrato de Borrow é sério: Se
a.borrow() == b.borrow(), entãohash(a) == hash(b)deve ser verdadeiro. Violar isso quebra HashMap/HashSet.AsRef é transitivo na stdlib:
String: AsRef<str>,str: AsRef<Path>, masStringnão implementaAsRef<Path>diretamente — use conversão explícita quando necessário.Não implemente AsRef e Deref para o mesmo Target: Isso pode causar ambiguidade. Escolha um:
Derefpara smart pointers,AsRefpara conversões genéricas.impl AsRef<str>vs&str: Para funções simples,&stré mais direto e legível. Useimpl AsRef<str>apenas quando realmente precisa aceitar múltiplos tipos.Borrow em APIs de coleção: Se você está criando uma coleção que faz lookup por chave, use
Borrowno método de busca, como a stdlib faz comHashMap::get.
Veja Também
- Deref e DerefMut — smart pointers e deref coercion
- From e Into — conversões de valor (não referência)
- Hash Trait — o contrato Hash+Eq que Borrow respeita
- HashMap — usa Borrow para busca flexível de chaves