// Example 112: Cow<T> Clone-on-Write
//
// Cow<'a, T> = either borrowed (&'a T) or owned (T).
// Clones only when mutation is needed. Efficient for "maybe modify" patterns.
use std::borrow::Cow;
// Approach 1: Conditional string modification
fn normalize_whitespace(s: &str) -> Cow<str> {
if s.contains('\t') {
// Need to modify → allocate (Owned)
Cow::Owned(s.replace('\t', " "))
} else {
// No modification → borrow (no allocation)
Cow::Borrowed(s)
}
}
fn approach1() {
let clean = "hello world";
let dirty = "hello\tworld";
let r1 = normalize_whitespace(clean);
let r2 = normalize_whitespace(dirty);
assert_eq!(&*r1, "hello world");
assert_eq!(&*r2, "hello world");
// r1 is Borrowed (zero-cost), r2 is Owned (allocated)
println!("Clean borrowed: {}, Fixed owned: {}",
matches!(r1, Cow::Borrowed(_)), matches!(r2, Cow::Owned(_)));
}
// Approach 2: Default with optional override
fn with_default<'a>(default: &'a str, override_val: Option<String>) -> Cow<'a, str> {
match override_val {
Some(v) => Cow::Owned(v),
None => Cow::Borrowed(default),
}
}
fn approach2() {
let default = "default_config";
let r1 = with_default(default, None);
let r2 = with_default(default, Some("custom".into()));
assert_eq!(&*r1, "default_config");
assert_eq!(&*r2, "custom");
println!("r1={}, r2={}", r1, r2);
}
// Approach 3: Batch processing with Cow
fn process_items<'a>(items: &'a [&'a str]) -> Vec<Cow<'a, str>> {
items.iter().map(|&item| {
if item.len() > 5 {
Cow::Owned(item.to_uppercase())
} else {
Cow::Borrowed(item)
}
}).collect()
}
fn approach3() {
let items = vec!["hi", "hello", "extraordinary"];
let result = process_items(&items);
assert_eq!(&*result[0], "hi");
assert_eq!(&*result[2], "EXTRAORDINARY");
let strs: Vec<&str> = result.iter().map(|c| c.as_ref()).collect();
println!("Processed: {}", strs.join(", "));
}
fn main() {
println!("=== Approach 1: Normalize Whitespace ===");
approach1();
println!("\n=== Approach 2: Default with Override ===");
approach2();
println!("\n=== Approach 3: Batch Processing ===");
approach3();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cow_borrowed() {
let result = normalize_whitespace("clean");
assert!(matches!(result, Cow::Borrowed(_)));
}
#[test]
fn test_cow_owned() {
let result = normalize_whitespace("has\ttab");
assert!(matches!(result, Cow::Owned(_)));
assert_eq!(&*result, "has tab");
}
#[test]
fn test_cow_to_mut() {
let mut cow: Cow<str> = Cow::Borrowed("hello");
// to_mut() clones if borrowed
cow.to_mut().push_str(" world");
assert_eq!(&*cow, "hello world");
assert!(matches!(cow, Cow::Owned(_)));
}
#[test]
fn test_with_default() {
let d = with_default("def", None);
assert!(matches!(d, Cow::Borrowed(_)));
let o = with_default("def", Some("custom".into()));
assert!(matches!(o, Cow::Owned(_)));
}
#[test]
fn test_cow_into_owned() {
let cow: Cow<str> = Cow::Borrowed("hello");
let owned: String = cow.into_owned();
assert_eq!(owned, "hello");
}
}
(* Example 112: Cow<T> Clone-on-Write — Lazy Cloning *)
(* OCaml doesn't need CoW — GC handles sharing.
But we can demonstrate the concept of deferred copying. *)
(* Approach 1: Conditional modification *)
let normalize_whitespace s =
if String.contains s '\t' then
String.map (fun c -> if c = '\t' then ' ' else c) s
else
s (* no allocation if no tabs *)
let approach1 () =
let clean = "hello world" in
let dirty = "hello\tworld" in
let r1 = normalize_whitespace clean in
let r2 = normalize_whitespace dirty in
assert (r1 = "hello world");
assert (r2 = "hello world");
Printf.printf "Clean: %s, Fixed: %s\n" r1 r2
(* Approach 2: Default with override *)
let with_default default_val override_opt =
match override_opt with
| Some v -> v
| None -> default_val
let approach2 () =
let default = "default_config" in
let r1 = with_default default None in
let r2 = with_default default (Some "custom") in
assert (r1 = "default_config");
assert (r2 = "custom");
Printf.printf "r1=%s, r2=%s\n" r1 r2
(* Approach 3: Batch processing with conditional transform *)
let process_items items transform_fn =
List.map (fun item ->
if String.length item > 5 then transform_fn item
else item
) items
let approach3 () =
let items = ["hi"; "hello"; "extraordinary"] in
let result = process_items items String.uppercase_ascii in
assert (result = ["hi"; "hello"; "EXTRAORDINARY"]);
Printf.printf "Processed: %s\n" (String.concat ", " result)
let () =
approach1 ();
approach2 ();
approach3 ();
Printf.printf "✓ All tests passed\n"