ExamplesBy LevelBy TopicLearning Paths
555 Intermediate

Owning References Pattern

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Owning References Pattern" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Sometimes you want a type that owns its data and simultaneously exposes a view into it — a buffer that knows which window of its bytes is "active," a string that knows where its meaningful content starts and ends. Key difference from OCaml: 1. **Zero

Tutorial

The Problem

Sometimes you want a type that owns its data and simultaneously exposes a view into it — a buffer that knows which window of its bytes is "active," a string that knows where its meaningful content starts and ends. Storing both the owner and a reference to its contents in the same struct leads to self-referential problems. The owning-reference pattern solves this by storing indices rather than pointers, computing views on demand, or using separate types for owner and view.

🎯 Learning Outcomes

  • • How OwnedSlice stores indices instead of references to avoid self-referential issues
  • • How fn slice(&self) -> &[u8] computes the view from stored indices at access time
  • • How fn narrow(&mut self, start, end) adjusts the active window without copying data
  • • How this pattern enables zero-copy processing of large buffers
  • • Where owning references appear: network buffers, text editors (rope data structures), binary parsers
  • Code Example

    #![allow(clippy::all)]
    //! Owning References Pattern
    //!
    //! Combining ownership with interior borrowing.
    
    /// Owner with cached view.
    pub struct OwnedSlice {
        data: Vec<u8>,
        start: usize,
        end: usize,
    }
    
    impl OwnedSlice {
        pub fn new(data: Vec<u8>) -> Self {
            let end = data.len();
            OwnedSlice {
                data,
                start: 0,
                end,
            }
        }
    
        pub fn slice(&self) -> &[u8] {
            &self.data[self.start..self.end]
        }
    
        pub fn narrow(&mut self, start: usize, end: usize) {
            self.start = start.min(self.data.len());
            self.end = end.min(self.data.len());
        }
    }
    
    /// String with owned and view.
    pub struct OwnedStr {
        data: String,
    }
    
    impl OwnedStr {
        pub fn new(s: &str) -> Self {
            OwnedStr {
                data: s.to_string(),
            }
        }
    
        pub fn as_str(&self) -> &str {
            &self.data
        }
    
        pub fn into_string(self) -> String {
            self.data
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_owned_slice() {
            let mut slice = OwnedSlice::new(vec![1, 2, 3, 4, 5]);
            assert_eq!(slice.slice(), &[1, 2, 3, 4, 5]);
            slice.narrow(1, 4);
            assert_eq!(slice.slice(), &[2, 3, 4]);
        }
    
        #[test]
        fn test_owned_str() {
            let s = OwnedStr::new("hello");
            assert_eq!(s.as_str(), "hello");
            assert_eq!(s.into_string(), "hello");
        }
    }

    Key Differences

  • Zero-copy views: Rust &self.data[s..e] is a true zero-copy view into the Vec; OCaml Bytes.sub copies — zero-copy needs Bigarray.
  • Index invalidation: Rust's borrow checker ensures slice() result cannot outlive self; OCaml's GC keeps Bigarray slices alive but does not prevent use-after-mutation.
  • Window mutation: Rust narrow(&mut self) requires &mut self — the compiler prevents concurrent reads of the slice while narrowing; OCaml allows concurrent access through ref.
  • Rope data structure: Text editors in Rust (like ropey) use owning-reference patterns extensively; OCaml text editors use GC-managed trees of string chunks.
  • OCaml Approach

    OCaml's Bytes or Bigarray slices are reference-counted or GC-managed, so owning references are natural:

    type owned_slice = { data: bytes; mutable start: int; mutable end_: int }
    let slice s = Bytes.sub s.data s.start (s.end_ - s.start)  (* copies *)
    (* Zero-copy requires Bigarray.Array1 sub-views *)
    

    True zero-copy sub-views in OCaml require Bigarray.Array1 with explicit layout management.

    Full Source

    #![allow(clippy::all)]
    //! Owning References Pattern
    //!
    //! Combining ownership with interior borrowing.
    
    /// Owner with cached view.
    pub struct OwnedSlice {
        data: Vec<u8>,
        start: usize,
        end: usize,
    }
    
    impl OwnedSlice {
        pub fn new(data: Vec<u8>) -> Self {
            let end = data.len();
            OwnedSlice {
                data,
                start: 0,
                end,
            }
        }
    
        pub fn slice(&self) -> &[u8] {
            &self.data[self.start..self.end]
        }
    
        pub fn narrow(&mut self, start: usize, end: usize) {
            self.start = start.min(self.data.len());
            self.end = end.min(self.data.len());
        }
    }
    
    /// String with owned and view.
    pub struct OwnedStr {
        data: String,
    }
    
    impl OwnedStr {
        pub fn new(s: &str) -> Self {
            OwnedStr {
                data: s.to_string(),
            }
        }
    
        pub fn as_str(&self) -> &str {
            &self.data
        }
    
        pub fn into_string(self) -> String {
            self.data
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_owned_slice() {
            let mut slice = OwnedSlice::new(vec![1, 2, 3, 4, 5]);
            assert_eq!(slice.slice(), &[1, 2, 3, 4, 5]);
            slice.narrow(1, 4);
            assert_eq!(slice.slice(), &[2, 3, 4]);
        }
    
        #[test]
        fn test_owned_str() {
            let s = OwnedStr::new("hello");
            assert_eq!(s.as_str(), "hello");
            assert_eq!(s.into_string(), "hello");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_owned_slice() {
            let mut slice = OwnedSlice::new(vec![1, 2, 3, 4, 5]);
            assert_eq!(slice.slice(), &[1, 2, 3, 4, 5]);
            slice.narrow(1, 4);
            assert_eq!(slice.slice(), &[2, 3, 4]);
        }
    
        #[test]
        fn test_owned_str() {
            let s = OwnedStr::new("hello");
            assert_eq!(s.as_str(), "hello");
            assert_eq!(s.into_string(), "hello");
        }
    }

    Deep Comparison

    OCaml vs Rust: lifetime owning ref

    See example.rs and example.ml for implementations.

    Key Differences

  • OCaml uses garbage collection
  • Rust uses ownership and borrowing
  • Both support the core concept
  • Exercises

  • Packet parser: Implement a struct Packet { data: Vec<u8>, pos: usize } with a fn read_u16(&mut self) -> Option<u16> that reads two bytes at pos and advances it.
  • Ring buffer view: Extend OwnedSlice to wrap around: fn wrap_slice(&self) -> (&[u8], &[u8]) returning the two parts when start > end (ring buffer state).
  • Zero-copy chains: Implement fn split_at(&self, mid: usize) -> (OwnedSliceView<'_>, OwnedSliceView<'_>) returning two views into the same OwnedSlice without copying.
  • Open Source Repos