Repository: ParkMyCar/compact_str
Pull request #46 (2022-01-18 03:59:24)

chore: Add public documentation to CompactStr
This PR adds more documentation and doc tests to the public API


Patch:
diff --git a/compact_str/src/features/bytes.rs b/compact_str/src/features/bytes.rs
index 6b1478a3..69da4f13 100644
--- a/compact_str/src/features/bytes.rs
+++ b/compact_str/src/features/bytes.rs
@@ -44,8 +44,8 @@ impl CompactStr {
     ///
     /// # Safety
     /// This function is unsafe because it does not check that the provided bytes are valid UTF-8.
-    /// If this constraint is violated, it may cause memory unsafety issues with futures uses of
-    /// the `ComapctStr`, as the rest of the library assumes that `CompactStr`s are valid UTF-8
+    /// If this constraint is violated, it may cause memory safety issues with futures uses of the
+    /// `ComapctStr`, as the rest of the library assumes that `CompactStr`s are valid UTF-8
     ///
     /// # Examples
     /// ```
diff --git a/compact_str/src/lib.rs b/compact_str/src/lib.rs
index e280766b..54d594d2 100644
--- a/compact_str/src/lib.rs
+++ b/compact_str/src/lib.rs
@@ -1,5 +1,5 @@
-//! `CompactStr` is a compact immutable string type that stores itself on the stack, if possible,
-//! and seamlessly interacts with `String`s and `&str`s.
+//! `CompactStr` is a compact string type that stores itself on the stack if possible, otherwise
+//! known as a "small string optimization".
 //!
 //! ### Memory Layout
 //! Normally strings are stored on the heap, since they're dynamically sized. In Rust a `String`
@@ -8,8 +8,10 @@
 //! 2. A pointer to a location on the heap where the string is stored
 //! 3. A `usize` denoting the capacity of the string
 //!
-//! On 64-bit architectures this results in 24 bytes being stored on the stack, 12 bytes for 32-bit
-//! architectures. For small strings, e.g. < 23 characters
+//! On 64-bit architectures this results in 24 bytes being stored on the stack (12 bytes for 32-bit
+//! architectures). For small strings, e.g. <= 23 characters, instead of storing a pointer, length,
+//! and capacity on the stack, you store the string itself! This avoids the need to heap allocate
+//! which reduces the amount of memory used, and improves performance.
 
 use core::borrow::Borrow;
 use core::cmp::Ordering;
@@ -30,15 +32,16 @@ use repr::Repr;
 #[cfg(test)]
 mod tests;
 
-/// A `CompactStr` is a memory efficient immuatable string that can be used almost anywhere a
+/// A `CompactStr` is a compact string type that can be used almost anywhere a
 /// `String` or `&str` can be used.
 ///
 /// ## Using `CompactStr`
 /// ```
 /// use compact_str::CompactStr;
-/// use std::collections::HashMap;
+/// # use std::collections::HashMap;
 ///
-/// // CompactStr auto derefs into a str so you can use all methods from `str` that take a `&self`
+/// // CompactStr auto derefs into a str so you can use all methods from `str`
+/// // that take a `&self`
 /// if CompactStr::new("hello world!").is_ascii() {
 ///     println!("we're all ASCII")
 /// }
@@ -57,7 +60,8 @@ mod tests;
 ///     println!("{}", text.as_ref());
 /// }
 ///
-/// // CompactStr impls AsRef<str> and Borrow<str>, so it can be used anywhere that excepts a generic string
+/// // CompactStr impls AsRef<str> and Borrow<str>, so it can be used anywhere
+/// // that excepts a generic string
 /// if let Some(building) = map.get("nyc") {
 ///     wrapped_print(building);
 /// }
@@ -72,6 +76,56 @@ pub struct CompactStr {
 }
 
 impl CompactStr {
+    /// Creates a new `CompactStr` from any type that implements `AsRef<str>`.
+    /// If the string is short enough, then it will be inlined on the stack!
+    ///
+    /// # Examples
+    ///
+    /// ### Inlined
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// // We can inline strings up to 12 characters long on 32-bit architectures...
+    /// #[cfg(target_pointer_width = "32")]
+    /// let s = "i'm 12 chars";
+    /// // ...and up to 24 characters on 64-bit architectures!
+    /// #[cfg(target_pointer_width = "64")]
+    /// let s = "i am 24 characters long!";
+    ///
+    /// let compact = CompactStr::new(&s);
+    ///
+    /// assert_eq!(compact, s);
+    /// // we are not allocated on the heap!
+    /// assert!(!compact.is_heap_allocated());
+    /// ```
+    ///
+    /// ### Heap
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// // For longer strings though, we get allocated on the heap
+    /// let long = "I am a longer string that will be allocated on the heap";
+    /// let compact = CompactStr::new(long);
+    ///
+    /// assert_eq!(compact, long);
+    /// // we are allocated on the heap!
+    /// assert!(compact.is_heap_allocated());
+    /// ```
+    ///
+    /// ### Creation
+    /// ```
+    /// use compact_str::CompactStr;
+    ///
+    /// // Using a `&'static str`
+    /// let s = "hello world!";
+    /// let hello = CompactStr::new(&s);
+    ///
+    /// // Using a `String`
+    /// let u = String::from("🦄🌈");
+    /// let unicorn = CompactStr::new(u);
+    ///
+    /// // Using a `Box<str>`
+    /// let b: Box<str> = String::from("📦📦📦").into_boxed_str();
+    /// let boxed = CompactStr::new(&b);
+    /// ```
     #[inline]
     pub fn new<T: AsRef<str>>(text: T) -> Self {
         CompactStr {
@@ -79,6 +133,20 @@ impl CompactStr {
         }
     }
 
+    /// Creates a new inline `CompactStr` at compile time.
+    ///
+    /// # Examples
+    /// ```
+    /// use compact_str::CompactStr;
+    ///
+    /// const DEFAULT_NAME: CompactStr = CompactStr::new_inline("untitled");
+    /// ```
+    ///
+    /// Note: Trying to create a long string that can't be inlined, will fail to build.
+    /// ```compile_fail
+    /// # use compact_str::CompactStr;
+    /// const LONG: CompactStr = CompactStr::new_inline("this is a long string that can't be stored on the stack");
+    /// ```
     #[inline]
     pub const fn new_inline(text: &str) -> Self {
         CompactStr {
@@ -86,16 +154,108 @@ impl CompactStr {
         }
     }
 
+    /// Creates a new empty `CompactStr` with the provided capacity.
+    ///
+    /// A `CompactStr` will inline strings on the stack, if they're small enough. Specifically, if
+    /// the string is smaller than `std::mem::size_of::<String>` bytes then it will be inlined. This
+    /// means that `CompactStr`s have a minimum capacity of `std::mem::size_of::<String> - 1`.
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let empty = CompactStr::with_capacity(0);
+    /// let min_size = std::mem::size_of::<String>() - 1;
+    ///
+    /// assert_eq!(empty.capacity(), min_size);
+    /// assert_ne!(0, min_size);
+    /// assert!(!empty.is_heap_allocated());
+    /// ```
+    ///
+    /// ### Heap Allocating
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// // If you create a `CompactStr` with a capacity greater than or equal to
+    /// // `std::mem::size_of::<String>`, it will heap allocated
+    ///
+    /// let heap_size = std::mem::size_of::<String>();
+    /// let empty = CompactStr::with_capacity(heap_size);
+    ///
+    /// assert_eq!(empty.capacity(), heap_size);
+    /// assert!(empty.is_heap_allocated());
+    /// ```
+    ///
+    /// # Note
+    /// A `CompactStr` can inline strings that are equal to `std::mem::size_of::<String>()`, if the
+    /// first character is ASCII. But we do this by foregoing any metadata to track the string's
+    /// length, and using the invariant of the first byte being ASCII (i.e. <= 127) to denote this
+    /// "packed" representation. Creating a `CompactStr` with capacity of `size_of::<String>()` will
+    /// heap allocate, because at this time we don't know if the first character will be ASCII or
+    /// not.
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Self {
+        CompactStr {
+            repr: Repr::with_capacity(capacity),
+        }
+    }
+
+    /// Returns the length of the `CompactStr` in `bytes`, not `chars` or graphemes.
+    ///
+    /// When using UTF-8 encoding (which all strings in Rust do) a single character will be 1 - 4
+    /// bytes long, therefore the return value of this method might not be what a human considers
+    /// the length of the string.
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let ascii = CompactStr::new("hello world");
+    /// assert_eq!(ascii.len(), 11);
+    ///
+    /// let emoji = CompactStr::new("👱");
+    /// assert_eq!(emoji.len(), 4);
+    /// ```
     #[inline]
     pub fn len(&self) -> usize {
         self.repr.len()
     }
 
+    /// Returns `true` if the `CompactStr` has a length of 0, `false` otherwise
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let mut msg = CompactStr::new("");
+    /// assert!(msg.is_empty());
+    ///
+    /// // add some characters
+    /// msg.push_str("hello reader!");
+    /// assert!(!msg.is_empty());
+    /// ```
     #[inline]
     pub fn is_empty(&self) -> bool {
         self.len() == 0
     }
 
+    /// Returns the capacity of the `CompactStr`, in bytes.
+    ///
+    /// # Note
+    /// * A `CompactStr` will always have a capacity of at least `std::mem::size_of::<String>() - 1`
+    ///
+    /// # Examples
+    /// ### Minimum Size
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let min_size = std::mem::size_of::<String>() - 1;
+    /// let compact = CompactStr::new("");
+    ///
+    /// assert!(compact.capacity() >= min_size);
+    /// ```
+    ///
+    /// ### Heap Allocated
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let compact = CompactStr::with_capacity(128);
+    /// assert_eq!(compact.capacity(), 128);
+    /// ```
     #[inline]
     pub fn capacity(&self) -> usize {
         self.repr.capacity()
@@ -109,9 +269,12 @@ impl CompactStr {
     /// * A `CompactStr` will always have at least a capacity of `(WORD * 3) - 1`
     /// * Reserving additional bytes may cause the `CompactStr` to become heap allocated
     ///
-    /// ### For example:
+    /// # Panics
+    /// Panics if the new capacity overflows `usize`
+    ///
+    /// # Examples
     /// ```
-    /// use compact_str::CompactStr;
+    /// # use compact_str::CompactStr;
     ///
     /// const WORD: usize = std::mem::size_of::<usize>();
     /// let mut compact = CompactStr::default();
@@ -119,22 +282,39 @@ impl CompactStr {
     ///
     /// compact.reserve(200);
     /// assert!(compact.is_heap_allocated());
+    /// assert!(compact.capacity() >= 200);
     /// ```
-    ///
-    /// # Panics
-    /// Panics if the new capacity overflows `usize`
+
     #[inline]
     pub fn reserve(&mut self, additional: usize) {
         self.repr.reserve(additional)
     }
 
+    /// Returns a string slice containing the entire `CompactStr`.
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let s = CompactStr::new("hello");
+    ///
+    /// assert_eq!(s.as_str(), "hello");
+    /// ```
     #[inline]
     pub fn as_str(&self) -> &str {
         self.repr.as_str()
     }
 
+    /// Returns a byte slice of the `CompactStr`s contents.
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let s = CompactStr::new("hello");
+    ///
+    /// assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes());
+    /// ```
     #[inline]
-    pub fn as_slice(&self) -> &[u8] {
+    pub fn as_bytes(&self) -> &[u8] {
         self.repr.as_slice()
     }
 
@@ -146,43 +326,120 @@ impl CompactStr {
     /// underlying buffer (e.g. you previously cloned this `CompactStr`), calling this method will
     /// clone the entire buffer to prevent silently mutating other owned `CompactStr`s.
     ///
-    /// ### Futher Explanation
+    /// # Safety
+    /// * All Rust strings, including `CompactStr`, must be valid UTF-8. The caller must guarantee
+    /// that any modifications made to the underlying buffer are valid UTF-8.
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let mut s = CompactStr::new("hello");
+    ///
+    /// let slice = unsafe { s.as_mut_bytes() };
+    /// // copy bytes into our string
+    /// slice[5..11].copy_from_slice(" world".as_bytes());
+    /// // set the len of the string
+    /// unsafe { s.set_len(11) };
+    ///
+    /// assert_eq!(s, "hello world");
+    /// ```
+    ///
+    /// # Further Explanation
     /// When a `CompactStr` becomes sufficiently large, the underlying buffer becomes a reference
     /// counted buffer on the heap. Then, cloning a `CompactStr` increments a reference count
     /// instead of cloning the entire buffer (very similar to `Arc<str>`). To prevent silently
     /// mutating the data of other owned `CompactStr`s when taking a mutable slice, we clone the
     /// underlying buffer and mutate that, if more than one outstanding reference exists.
-    ///
-    /// # Safety
-    /// * All Rust strings, including `CompactStr`, must be valid UTF-8. The caller must guarantee
-    /// that any modifications made to the underlying buffer are valid UTF-8.
     #[inline]
-    pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
+    pub unsafe fn as_mut_bytes(&mut self) -> &mut [u8] {
         self.repr.as_mut_slice()
     }
 
+    /// Appends the given `char` to the end of this `CompactStr`.
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let mut s = CompactStr::new("foo");
+    ///
+    /// s.push('b');
+    /// s.push('a');
+    /// s.push('r');
+    ///
+    /// assert_eq!("foobar", s);
+    /// ```
     #[inline]
     pub fn push(&mut self, ch: char) {
         self.repr.push(ch)
     }
 
+    /// Removes the last character from the `CompactStr` and returns it.
+    /// Returns `None` if this `ComapctStr` is empty.
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let mut s = CompactStr::new("abc");
+    ///
+    /// assert_eq!(s.pop(), Some('c'));
+    /// assert_eq!(s.pop(), Some('b'));
+    /// assert_eq!(s.pop(), Some('a'));
+    ///
+    /// assert_eq!(s.pop(), None);
+    /// ```
     #[inline]
     pub fn pop(&mut self) -> Option<char> {
         self.repr.pop()
     }
 
+    /// Appends a given string slice onto the end of this `CompactStr`
+    ///
+    /// # Examples
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let mut s = CompactStr::new("abc");
+    ///
+    /// s.push_str("123");
+    ///
+    /// assert_eq!("abc123", s);
+    /// ```
     #[inline]
     pub fn push_str(&mut self, s: &str) {
         self.repr.push_str(s)
     }
 
+    /// Forces the length of the `CompactStr` to `new_len`.
+    ///
+    /// This is a low-level operation that maintains none of the normal invariants for `CompactStr`.
+    /// If you want to modify the `CompactStr` you should use methods like `push`, `push_str` or
+    /// `pop`.
+    ///
     /// # Safety
-    /// * TODO: Document safety here
+    /// * `new_len` must be less than or equal to `capacity()`
+    /// * The elements at `old_len..new_len` must be initialized
     #[inline]
-    pub unsafe fn set_len(&mut self, length: usize) {
-        self.repr.set_len(length)
+    pub unsafe fn set_len(&mut self, new_len: usize) {
+        self.repr.set_len(new_len)
     }
 
+    /// Returns whether or not the `CompactStr` is heap allocated.
+    ///
+    /// # Examples
+    /// ### Inlined
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let hello = CompactStr::new("hello world");
+    ///
+    /// assert!(!hello.is_heap_allocated());
+    /// ```
+    ///
+    /// ### Heap Allocated
+    /// ```
+    /// # use compact_str::CompactStr;
+    /// let msg = CompactStr::new("this message will self destruct in 5, 4, 3, 2, 1 💥");
+    ///
+    /// assert!(msg.is_heap_allocated());
+    /// ```
     #[inline]
     pub fn is_heap_allocated(&self) -> bool {
         self.repr.is_heap_allocated()
diff --git a/compact_str/src/repr/arc/mod.rs b/compact_str/src/repr/arc/mod.rs
index 60b08781..7350cf08 100644
--- a/compact_str/src/repr/arc/mod.rs
+++ b/compact_str/src/repr/arc/mod.rs
@@ -52,6 +52,18 @@ impl ArcString {
         ArcString { len, ptr }
     }
 
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Self {
+        // We should never be able to programatically create an `ArcString` with a capacity less
+        // than our max inline size, since then the string should be inlined
+        debug_assert!(capacity >= super::MAX_SIZE);
+
+        let len = 0;
+        let ptr = ArcStringInner::with_capacity(capacity);
+
+        ArcString { len, ptr }
+    }
+
     #[inline]
     pub const fn len(&self) -> usize {
         self.len
diff --git a/compact_str/src/repr/heap.rs b/compact_str/src/repr/heap.rs
index 09cfe496..8b3dda68 100644
--- a/compact_str/src/repr/heap.rs
+++ b/compact_str/src/repr/heap.rs
@@ -39,6 +39,15 @@ impl HeapString {
         HeapString { padding, string }
     }
 
+    /// Creates a `HeapString` with the provided capacity.
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Self {
+        let padding = PADDING;
+        let string = ArcString::with_capacity(capacity);
+
+        HeapString { padding, string }
+    }
+
     /// Makes a mutable reference to the underlying buffer, cloning if there is more than one out
     /// standing reference.
     ///
diff --git a/compact_str/src/repr/mod.rs b/compact_str/src/repr/mod.rs
index 05a2c9e6..80243d51 100644
--- a/compact_str/src/repr/mod.rs
+++ b/compact_str/src/repr/mod.rs
@@ -82,6 +82,16 @@ impl Repr {
         }
     }
 
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Self {
+        if capacity <= inline::MAX_INLINE_SIZE {
+            EMPTY
+        } else {
+            let heap = ManuallyDrop::new(HeapString::with_capacity(capacity));
+            Repr { heap }
+        }
+    }
+
     #[inline]
     pub fn len(&self) -> usize {
         self.cast().len()


Test patch:
diff --git a/compact_str/src/tests.rs b/compact_str/src/tests.rs
index 004c1641..180afb7f 100644
--- a/compact_str/src/tests.rs
+++ b/compact_str/src/tests.rs
@@ -72,7 +72,7 @@ proptest! {
         compact.reserve(word.len());
 
         // SAFETY: We're writing a String which we know is UTF-8
-        let slice = unsafe { compact.as_mut_slice() };
+        let slice = unsafe { compact.as_mut_bytes() };
         slice[..word.len()].copy_from_slice(word.as_bytes());
 
         // SAFTEY: We know this is the length of our string, since `compact` started with 0 bytes
@@ -92,7 +92,7 @@ proptest! {
         compact.reserve(word.len());
 
         // SAFETY: We're writing a String which we know is UTF-8
-        let slice = unsafe { compact.as_mut_slice() };
+        let slice = unsafe { compact.as_mut_bytes() };
         slice[..word.len()].copy_from_slice(word.as_bytes());
 
         // SAFTEY: We know this is the length of our string, since `compact` started with 0 bytes