O que é Cow<T>
Cow<'a, T> (Clone on Write) é um smart pointer que pode conter um dado emprestado (Borrowed) ou possuído (Owned). A ideia central é: manter os dados emprestados sempre que possível e só clonar quando uma modificação for necessária. O nome “Clone on Write” reflete exatamente esse comportamento — a cópia acontece apenas no momento da escrita.
Use Cow<T> quando uma função precisa às vezes retornar dados emprestados e às vezes dados novos. Isso é especialmente comum em funções de processamento de texto (normalização, sanitização, escape) onde a maioria das entradas não precisa de modificação. Em vez de sempre clonar “por precaução”, Cow adia a cópia até que seja realmente necessária, melhorando a performance.
Criando Cow
use std::borrow::Cow;
fn main() {
// Cow<str> — emprestado (referência a &str)
let emprestado: Cow<str> = Cow::Borrowed("texto literal");
// Cow<str> — possuído (dona de uma String)
let possuido: Cow<str> = Cow::Owned(String::from("texto dinâmico"));
// Usando .into() para conversão ergonômica
let de_str: Cow<str> = "literal".into(); // Borrowed
let de_string: Cow<str> = String::from("owned").into(); // Owned
// Cow<[T]> — com slices
let slice_emprestado: Cow<[i32]> = Cow::Borrowed(&[1, 2, 3]);
let vec_possuido: Cow<[i32]> = Cow::Owned(vec![4, 5, 6]);
println!("{emprestado} | {possuido}");
println!("{de_str} | {de_string}");
println!("{slice_emprestado:?} | {vec_possuido:?}");
// Verificando se é emprestado ou possuído
match &emprestado {
Cow::Borrowed(_) => println!("Emprestado!"),
Cow::Owned(_) => println!("Possuído!"),
}
}
Tabela de Métodos Principais
| Método / Operação | Descrição | Exemplo |
|---|---|---|
Cow::Borrowed(ref) | Cria com dado emprestado | Cow::Borrowed("oi") |
Cow::Owned(val) | Cria com dado possuído | Cow::Owned(String::from("oi")) |
to_mut() | Obtém &mut T::Owned, clonando se necessário | cow.to_mut().push_str("!") |
into_owned() | Converte para o tipo owned, clonando se emprestado | cow.into_owned() → String |
is_borrowed() | Verifica se é Borrowed (nightly/Rust 1.82+) | cow.is_borrowed() |
is_owned() | Verifica se é Owned (nightly/Rust 1.82+) | cow.is_owned() |
| Deref | Acessa como &T | &*cow ou cow.as_ref() |
as_ref() | Retorna &str de Cow<str> | cow.as_ref() |
.into() | Converte de &str ou String | "oi".into() |
Exemplos Práticos
1. Sanitização de Texto (Padrão Clássico)
O caso de uso mais comum de Cow<str>: uma função que modifica a string apenas quando necessário.
use std::borrow::Cow;
fn sanitizar_html(input: &str) -> Cow<str> {
// Se não contém caracteres especiais, retorna a referência original
if !input.contains('<') && !input.contains('>') && !input.contains('&') {
return Cow::Borrowed(input);
}
// Só aloca uma nova String quando precisa modificar
let mut resultado = String::with_capacity(input.len());
for c in input.chars() {
match c {
'<' => resultado.push_str("<"),
'>' => resultado.push_str(">"),
'&' => resultado.push_str("&"),
'"' => resultado.push_str("""),
_ => resultado.push(c),
}
}
Cow::Owned(resultado)
}
fn main() {
let textos = vec![
"Texto simples sem HTML",
"Alerta: <script>alert('xss')</script>",
"Preço: R$ 10 & desconto de 5%",
"Outro texto normal",
];
for texto in textos {
let limpo = sanitizar_html(texto);
let tipo = if matches!(limpo, Cow::Borrowed(_)) {
"emprestado"
} else {
"clonado"
};
println!("[{tipo}] {limpo}");
}
}
2. Normalização de Caminhos
use std::borrow::Cow;
fn normalizar_caminho(caminho: &str) -> Cow<str> {
if caminho.ends_with('/') || caminho.contains("//") {
let normalizado = caminho
.trim_end_matches('/')
.replace("//", "/");
Cow::Owned(normalizado)
} else {
Cow::Borrowed(caminho)
}
}
fn adicionar_prefixo<'a>(base: &str, caminho: &'a str) -> Cow<'a, str> {
if caminho.starts_with('/') {
Cow::Borrowed(caminho)
} else {
Cow::Owned(format!("{base}/{caminho}"))
}
}
fn main() {
let caminhos = vec![
"/api/users",
"/api/users/",
"/api//users//list",
"relativo/caminho",
"/absoluto/caminho",
];
for caminho in &caminhos {
let normalizado = normalizar_caminho(caminho);
let com_prefixo = adicionar_prefixo("/v1", caminho);
println!("{caminho:30} → normalizado: {normalizado:25} | com prefixo: {com_prefixo}");
}
}
3. Cow em Structs para Flexibilidade
use std::borrow::Cow;
#[derive(Debug)]
struct Mensagem<'a> {
remetente: Cow<'a, str>,
conteudo: Cow<'a, str>,
prioridade: u8,
}
impl<'a> Mensagem<'a> {
/// Cria com dados emprestados (sem alocação)
fn nova_estatica(remetente: &'a str, conteudo: &'a str) -> Self {
Mensagem {
remetente: Cow::Borrowed(remetente),
conteudo: Cow::Borrowed(conteudo),
prioridade: 0,
}
}
/// Cria com dados dinâmicos (precisa alocar)
fn nova_dinamica(remetente: String, conteudo: String) -> Mensagem<'static> {
Mensagem {
remetente: Cow::Owned(remetente),
conteudo: Cow::Owned(conteudo),
prioridade: 0,
}
}
fn com_prioridade(mut self, p: u8) -> Self {
self.prioridade = p;
self
}
fn para_owned(self) -> Mensagem<'static> {
Mensagem {
remetente: Cow::Owned(self.remetente.into_owned()),
conteudo: Cow::Owned(self.conteudo.into_owned()),
prioridade: self.prioridade,
}
}
}
fn main() {
// Sem alocação — usa referências a literais
let msg1 = Mensagem::nova_estatica("sistema", "Inicialização completa")
.com_prioridade(1);
// Com alocação — dados vêm de runtime
let nome = format!("usuario_{}", 42);
let texto = format!("Login às {}", "14:30");
let msg2 = Mensagem::nova_dinamica(nome, texto)
.com_prioridade(3);
println!("{msg1:?}");
println!("{msg2:?}");
// Converter para owned quando precisar armazenar sem lifetime
let armazenada: Mensagem<'static> = msg1.para_owned();
println!("Armazenada: {armazenada:?}");
}
4. Processamento de Configuração com Valores Padrão
use std::borrow::Cow;
use std::collections::HashMap;
struct Config {
valores: HashMap<String, String>,
}
impl Config {
fn obter<'a>(&'a self, chave: &str, padrao: &'a str) -> Cow<'a, str> {
match self.valores.get(chave) {
Some(valor) => Cow::Borrowed(valor.as_str()),
None => Cow::Borrowed(padrao),
}
}
fn obter_transformado(&self, chave: &str) -> Cow<str> {
match self.valores.get(chave) {
Some(valor) if valor == valor.to_uppercase().as_str() => {
// Já está em maiúsculas, retorna emprestado
Cow::Borrowed(valor.as_str())
}
Some(valor) => {
// Precisa converter, cria nova String
Cow::Owned(valor.to_uppercase())
}
None => Cow::Borrowed("NAO_DEFINIDO"),
}
}
}
fn main() {
let mut valores = HashMap::new();
valores.insert("host".to_string(), "localhost".to_string());
valores.insert("porta".to_string(), "8080".to_string());
valores.insert("env".to_string(), "PRODUCAO".to_string());
let config = Config { valores };
// Valor existente: emprestado
println!("Host: {}", config.obter("host", "127.0.0.1"));
// Valor padrão: emprestado (sem alocação!)
println!("Timeout: {}", config.obter("timeout", "30"));
// Transformado: depende do caso
println!("Env: {}", config.obter_transformado("env")); // Emprestado (já maiúsculo)
println!("Host: {}", config.obter_transformado("host")); // Owned (precisou converter)
println!("DB: {}", config.obter_transformado("db")); // Emprestado (padrão)
}
5. Cow<[T]> para Processamento de Slices
use std::borrow::Cow;
fn remover_zeros(dados: &[i32]) -> Cow<[i32]> {
if dados.iter().any(|&x| x == 0) {
// Precisa filtrar: aloca novo Vec
Cow::Owned(dados.iter().copied().filter(|&x| x != 0).collect())
} else {
// Nenhum zero: retorna o slice original
Cow::Borrowed(dados)
}
}
fn normalizar_notas(notas: &[f64], maximo: f64) -> Cow<[f64]> {
let max_atual = notas.iter().copied().fold(f64::NEG_INFINITY, f64::max);
if (max_atual - maximo).abs() < f64::EPSILON {
// Já normalizado
Cow::Borrowed(notas)
} else {
// Precisa normalizar
let fator = maximo / max_atual;
Cow::Owned(notas.iter().map(|&n| n * fator).collect())
}
}
fn main() {
let com_zeros = [1, 0, 3, 0, 5];
let sem_zeros = [1, 2, 3, 4, 5];
let r1 = remover_zeros(&com_zeros);
let r2 = remover_zeros(&sem_zeros);
println!("Com zeros: {r1:?} (alocado)");
println!("Sem zeros: {r2:?} (emprestado)");
let notas = [8.0, 6.0, 10.0, 7.5];
let ja_normalizado = [80.0, 60.0, 100.0, 75.0];
let n1 = normalizar_notas(¬as, 100.0);
let n2 = normalizar_notas(&ja_normalizado, 100.0);
println!("Normalizado: {n1:.1?}");
println!("Já ok: {n2:.1?}");
}
Dicas de Performance e Armadilhas
Meça antes de otimizar:
Cowadiciona complexidade ao código. Use-o quando profiling mostra que clonagens desnecessárias são um gargalo, ou quando a API naturalmente se beneficia (funções de sanitização/normalização).to_mut()clona na primeira chamada: Após chamarto_mut()em umCow::Borrowed, o dado é clonado e passa a serOwned. Chamadas subsequentes ato_mut()não clonam novamente.Lifetimes podem complicar:
Cow<'a, str>carrega um lifetime que se propaga pela struct. Se isso for problemático, useinto_owned()para obterCow<'static, str>.CowimplementaDeref: Você pode passar&Cow<str>onde&stré esperado, graças à coerção Deref. Isso torna o uso transparente na maioria dos casos.Tamanho em memória:
Cow<str>ocupa 3 palavras de máquina (24 bytes em 64-bit): o discriminante Borrowed/Owned, o ponteiro e o tamanho. É o mesmo que umaString.Alternativas: Para o caso específico de “emprestado ou owned” em retornos de função,
Cowé a solução padrão. Não tente reimplementar comenum { Ref(&str), Own(String) }—Cowjá faz isso com ergonomia e integrações com a stdlib.
Veja Também
- Otimização de Performance em Rust — técnicas avançadas de otimização
- String e &str —
Cow<str>é a ponte entreStringe&str - Slices (&[T]) —
Cow<[T]>trabalha com slices - Box<T>: Alocação no Heap — outro smart pointer para alocação no heap
- Rc<T> e Arc<T> — quando precisa de ownership compartilhado
- Cheatsheet Rust — referência rápida de sintaxe
- Documentação oficial — std::borrow::Cow