ExamplesBy LevelBy TopicLearning Paths
484 Fundamental

Cow<str> Usage

Functional Programming

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

  • • Construct Cow::Borrowed(s) and Cow::Owned(owned) variants
  • • Return Cow<'_, str> from a function to avoid unnecessary allocation
  • • Use matches!(val, Cow::Borrowed(_)) to verify no allocation occurred
  • • Deref Cow<str> to &str for use with any string-accepting API
  • • Chain Cow transformations without intermediate allocations
  • Code 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

  • Allocation transparency: Rust's 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).
  • Unified API: Cow<str> derefs to &str so callers need no special handling; OCaml would need explicit match or an option type.
  • Lifetime tracking: 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");
        }
    }
    ✓ Tests Rust test suite
    #[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

  • Normalise whitespace: Write normalize_spaces(s: &str) -> Cow<str> that collapses multiple spaces into one, returning Borrowed when the input already has no consecutive spaces.
  • Escape HTML: Write escape_html(s: &str) -> Cow<str> that returns Borrowed(s) when no <, >, &, or " are present, and Owned(escaped) otherwise.
  • Chain Cows: Implement a pipeline 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.
  • Open Source Repos