Cow<str> Usage
Tutorial
The Problem
Many string transformations are conditional: sanitise input that may or may not contain bad characters, uppercase a string that may already be uppercase, strip a prefix that may or may not be present. A function that always returns String always allocates even when the input is already correct. A function that always returns &str cannot return a modified string. Cow<str> resolves this: it carries the borrowed slice when no change is needed and the owned string when a change was made, with a single return type and zero overhead in the no-change case.
🎯 Learning Outcomes
Cow::Borrowed(s) and Cow::Owned(owned) variantsCow<'_, str> from a function to avoid unnecessary allocationmatches!(val, Cow::Borrowed(_)) to verify no allocation occurredCow<str> to &str for use with any string-accepting APICow transformations without intermediate allocationsCode Example
#![allow(clippy::all)]
// 484. Cow<str> for flexible strings
use std::borrow::Cow;
fn ensure_no_spaces(s: &str) -> Cow<'_, str> {
if !s.contains(' ') {
Cow::Borrowed(s) // no allocation!
} else {
Cow::Owned(s.replace(' ', "_")) // allocates only when needed
}
}
fn to_uppercase_if_needed(s: Cow<str>) -> Cow<str> {
if s.chars().any(|c| c.is_lowercase()) {
Cow::Owned(s.to_uppercase())
} else {
s // pass through unchanged
}
}
fn process(input: Cow<str>) -> String {
// Can call String methods on Cow<str> via deref
format!("processed: {}", input.trim())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_alloc() {
assert!(matches!(ensure_no_spaces("nospace"), Cow::Borrowed(_)));
}
#[test]
fn test_allocs() {
assert!(matches!(ensure_no_spaces("has space"), Cow::Owned(_)));
}
#[test]
fn test_content() {
assert_eq!(&*ensure_no_spaces("a b"), "a_b");
}
}Key Differences
Cow makes allocation vs. borrow explicit in the type; OCaml's string functions always return a new string or the same pointer (without type-level distinction).Cow<str> derefs to &str so callers need no special handling; OCaml would need explicit match or an option type.Cow<'a, str> carries a lifetime; OCaml strings are GC-managed so lifetimes are irrelevant.into_owned**: Rust's Cow::into_owned() cheaply converts a borrowed cow to an owned String via .to_string(); OCaml has no equivalent operation with the same allocation transparency.OCaml Approach
OCaml has no Cow equivalent in the standard library. The pattern is approximated with Either or by returning option (where None means "unchanged"):
let ensure_no_spaces s =
if not (String.contains s ' ') then s (* same pointer *)
else String.map (fun c -> if c = ' ' then '_' else c) s
(* Caller cannot distinguish borrowed vs. allocated without pointer comparison *)
The Slice_and_rope library provides rope structures for efficient persistent string manipulation. Stringext provides replace_all utilities. There is no idiomatic zero-copy conditional allocation pattern.
Full Source
#![allow(clippy::all)]
// 484. Cow<str> for flexible strings
use std::borrow::Cow;
fn ensure_no_spaces(s: &str) -> Cow<'_, str> {
if !s.contains(' ') {
Cow::Borrowed(s) // no allocation!
} else {
Cow::Owned(s.replace(' ', "_")) // allocates only when needed
}
}
fn to_uppercase_if_needed(s: Cow<str>) -> Cow<str> {
if s.chars().any(|c| c.is_lowercase()) {
Cow::Owned(s.to_uppercase())
} else {
s // pass through unchanged
}
}
fn process(input: Cow<str>) -> String {
// Can call String methods on Cow<str> via deref
format!("processed: {}", input.trim())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_alloc() {
assert!(matches!(ensure_no_spaces("nospace"), Cow::Borrowed(_)));
}
#[test]
fn test_allocs() {
assert!(matches!(ensure_no_spaces("has space"), Cow::Owned(_)));
}
#[test]
fn test_content() {
assert_eq!(&*ensure_no_spaces("a b"), "a_b");
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_alloc() {
assert!(matches!(ensure_no_spaces("nospace"), Cow::Borrowed(_)));
}
#[test]
fn test_allocs() {
assert!(matches!(ensure_no_spaces("has space"), Cow::Owned(_)));
}
#[test]
fn test_content() {
assert_eq!(&*ensure_no_spaces("a b"), "a_b");
}
}
Exercises
normalize_spaces(s: &str) -> Cow<str> that collapses multiple spaces into one, returning Borrowed when the input already has no consecutive spaces.escape_html(s: &str) -> Cow<str> that returns Borrowed(s) when no <, >, &, or " are present, and Owned(escaped) otherwise.trim → ensure_no_spaces → to_ascii_lowercase where each step returns Cow<str> and measure the total number of allocations for inputs that require 0, 1, and 3 modifications.