๐Ÿฆ€ Functional Rust
๐ŸŽฌ Rust Ownership in 30 seconds Visual walkthrough of ownership, moves, and automatic memory management.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Each value in Rust has exactly one owner โ€” when the owner goes out of scope, the value is dropped

โ€ข Assignment moves ownership by default; the original binding becomes invalid

โ€ข Borrowing (&T / &mut T) lets you reference data without taking ownership

โ€ข The compiler enforces: many shared references OR one mutable reference, never both

โ€ข No garbage collector needed โ€” memory is freed deterministically at scope exit

488: Owning References with Rc\<str\>

Difficulty: 2 Level: Intermediate Reference-counted immutable string slices โ€” share string data in a single thread without cloning.

The Problem This Solves

You have a string that multiple parts of your program need to read. The naive solution is to clone it everywhere โ€” but now you have N copies in memory, and each clone is an allocation. The opposite extreme is passing `&str` references, which works but ties you to lifetimes that quickly become unwieldy in data structures. `Rc<str>` sits in the middle. It's a reference-counted pointer to an immutable string slice: one heap allocation, multiple owners, no lifetime annotation required, no unnecessary copies. When the last `Rc<str>` drops, the string is freed. Use `Rc<str>` when you're building trees, graphs, or caches in a single-threaded context that share string data โ€” AST nodes sharing identifiers, a DOM sharing tag names, a symbol table in an interpreter.

The Intuition

A shared document in a filing cabinet. Multiple employees hold a key (the `Rc`) to the same drawer (the string). They can all read it simultaneously. Nobody has their own copy. When the last employee hands back their key, the drawer is cleared. The cabinet tracks how many keys are outstanding.

How It Works in Rust

1. Create `Rc<str>` from a string literal or `String`:
use std::rc::Rc;
let s: Rc<str> = "hello world".into();
let s2: Rc<str> = String::from("hello world").into();
2. Clone is cheap โ€” increments a counter, no string copy:
let clone = s.clone(); // just bumps refcount
assert_eq!(s, clone);
3. Deref to `&str` โ€” use all `str` methods directly:
println!("{}", s.to_uppercase()); // Rc<str> derefs to &str
4. Embed in structs without lifetime parameters:
struct AstNode {
    name: Rc<str>,   // no 'a lifetime annotation needed
    children: Vec<AstNode>,
}
5. For thread-safe sharing โ€” use `Arc<str>` instead (see example 489).

What This Unlocks

Key Differences

ConceptOCamlRust
Shared string referenceStructural sharing via GC`Rc<str>` with explicit refcount
Thread safetyGC handles it`Rc` is single-thread only
Lifetime annotationNot neededNot needed (that's the point)
vs `String`โ€”`Rc<str>`: shared + immutable; `String`: owned + mutable
//! # String Owning References โ€” Self-Referential Patterns
//!
//! Patterns for owning data while referencing into it.

use std::pin::Pin;

/// Simple owned string with cached parse result
pub struct ParsedString {
    source: String,
    words: Vec<(usize, usize)>, // (start, end) indices into source
}

impl ParsedString {
    pub fn new(s: &str) -> Self {
        let source = s.to_string();
        let words: Vec<_> = source
            .match_indices(char::is_alphanumeric)
            .map(|(i, _)| (i, i + 1))
            .collect();

        // Actually find word boundaries
        let mut words = Vec::new();
        let mut start = None;

        for (i, c) in source.char_indices() {
            if c.is_alphanumeric() {
                if start.is_none() {
                    start = Some(i);
                }
            } else if let Some(s) = start {
                words.push((s, i));
                start = None;
            }
        }
        if let Some(s) = start {
            words.push((s, source.len()));
        }

        Self { source, words }
    }

    pub fn get_word(&self, index: usize) -> Option<&str> {
        self.words.get(index).map(|(start, end)| &self.source[*start..*end])
    }

    pub fn word_count(&self) -> usize {
        self.words.len()
    }

    pub fn source(&self) -> &str {
        &self.source
    }
}

/// Cow-based approach
use std::borrow::Cow;

pub enum StringOrStatic<'a> {
    Static(&'static str),
    Owned(String),
    Borrowed(&'a str),
}

impl<'a> StringOrStatic<'a> {
    pub fn as_str(&self) -> &str {
        match self {
            Self::Static(s) => s,
            Self::Owned(s) => s,
            Self::Borrowed(s) => s,
        }
    }

    pub fn into_owned(self) -> String {
        match self {
            Self::Static(s) => s.to_string(),
            Self::Owned(s) => s,
            Self::Borrowed(s) => s.to_string(),
        }
    }
}

/// Using Cow for zero-copy when possible
pub fn maybe_uppercase(s: &str) -> Cow<'_, str> {
    if s.chars().all(|c| !c.is_lowercase()) {
        Cow::Borrowed(s)
    } else {
        Cow::Owned(s.to_uppercase())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parsed_string() {
        let ps = ParsedString::new("hello world rust");
        assert_eq!(ps.word_count(), 3);
        assert_eq!(ps.get_word(0), Some("hello"));
        assert_eq!(ps.get_word(1), Some("world"));
        assert_eq!(ps.get_word(2), Some("rust"));
        assert_eq!(ps.get_word(3), None);
    }

    #[test]
    fn test_string_or_static() {
        let s = StringOrStatic::Static("hello");
        assert_eq!(s.as_str(), "hello");

        let owned = StringOrStatic::Owned(String::from("world"));
        assert_eq!(owned.as_str(), "world");
    }

    #[test]
    fn test_cow_no_alloc() {
        let s = "ALREADY UPPER";
        let result = maybe_uppercase(s);
        assert!(matches!(result, Cow::Borrowed(_)));
    }

    #[test]
    fn test_cow_with_alloc() {
        let s = "needs uppercase";
        let result = maybe_uppercase(s);
        assert!(matches!(result, Cow::Owned(_)));
        assert_eq!(&*result, "NEEDS UPPERCASE");
    }
}
(* 488. Rc<str> concept โ€“ OCaml *)
(* OCaml GC handles shared strings; demonstrate with a symbol table *)

type symbol = { name: string; id: int }
let next_id = ref 0

let table : (string, symbol) Hashtbl.t = Hashtbl.create 64

let intern_symbol name =
  match Hashtbl.find_opt table name with
  | Some s -> s
  | None ->
    let s = { name; id = !next_id } in
    incr next_id;
    Hashtbl.add table name s;
    s

let () =
  let a = intern_symbol "hello" in
  let b = intern_symbol "world" in
  let c = intern_symbol "hello" in
  Printf.printf "a.id=%d b.id=%d c.id=%d\n" a.id b.id c.id;
  Printf.printf "a==c: %b\n" (a.id = c.id);  (* same symbol *)
  Printf.printf "table size: %d\n" (Hashtbl.length table)