Cow<T>: Clone on Write em Rust

Guia completo de Cow<T> em Rust: Clone on Write, Cow<str>, Cow<[T]>, evitando clonagens desnecessárias e padrões de otimização.

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çãoDescriçãoExemplo
Cow::Borrowed(ref)Cria com dado emprestadoCow::Borrowed("oi")
Cow::Owned(val)Cria com dado possuídoCow::Owned(String::from("oi"))
to_mut()Obtém &mut T::Owned, clonando se necessáriocow.to_mut().push_str("!")
into_owned()Converte para o tipo owned, clonando se emprestadocow.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()
DerefAcessa 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("&lt;"),
            '>' => resultado.push_str("&gt;"),
            '&' => resultado.push_str("&amp;"),
            '"' => resultado.push_str("&quot;"),
            _ => 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(&notas, 100.0);
    let n2 = normalizar_notas(&ja_normalizado, 100.0);
    println!("Normalizado: {n1:.1?}");
    println!("Já ok: {n2:.1?}");
}

Dicas de Performance e Armadilhas

  1. Meça antes de otimizar: Cow adiciona 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).

  2. to_mut() clona na primeira chamada: Após chamar to_mut() em um Cow::Borrowed, o dado é clonado e passa a ser Owned. Chamadas subsequentes a to_mut() não clonam novamente.

  3. Lifetimes podem complicar: Cow<'a, str> carrega um lifetime que se propaga pela struct. Se isso for problemático, use into_owned() para obter Cow<'static, str>.

  4. Cow implementa Deref: Você pode passar &Cow<str> onde &str é esperado, graças à coerção Deref. Isso torna o uso transparente na maioria dos casos.

  5. 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 uma String.

  6. 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 com enum { Ref(&str), Own(String) }Cow já faz isso com ergonomia e integrações com a stdlib.


Veja Também