cowstr 1.0.0-beta1

Copy-on-Write shared strings
Documentation
use constptr::ConstPtr;
use std::ops::Deref;
use std::ops::Range;

use crate::*;

/// Arbitrary immutable text as span from some shared string
#[derive(Debug, Clone)]
pub struct SubStr {
    pub(crate) string: CowStr,
    pub(crate) substr: ConstPtr<str>,
}

unsafe impl Send for SubStr {}
unsafe impl Sync for SubStr {}

/// Tried to use a range larger than the string it should cover.
#[derive(Debug)]
pub struct RangeError;

/// A constant for an always empty `SubStr`.
pub static EMPTY_SUBSTR: SubStr = SubStr::from_static("");

impl SubStr {
    /// Create a `SubStr` that spans a complete `CowStr`.
    #[must_use]
    pub fn new(from: &CowStr) -> SubStr {
        SubStr {
            string: from.clone(),
            substr: ConstPtr::from(&from[..]),
        }
    }

    /// Create and `SubStr` from a `&str` that must be contained an initial `CowStr`.  This is useful
    /// when parsing or splitting a &str that is obtained from a `CowStr`.
    ///
    /// # Panics
    ///
    /// `substr` is not within `from`
    #[inline]
    #[must_use]
    pub fn from_contained_str(from: &CowStr, substr: &str) -> SubStr {
        // We need to check only for the first character, there is no way one can safely
        // construct a &str that spans past the end of a original CowStr
        assert!(
            from.as_bytes().as_ptr_range().contains(&substr.as_ptr()),
            "`substr` is not part of `from`"
        );
        unsafe { Self::from_raw_parts(from.clone(), ConstPtr::new_unchecked(substr)) }
    }

    /// Create and `SubStr` as span of a `CowStr`.
    pub fn new_range(from: &CowStr, range: Range<usize>) -> Result<SubStr, RangeError> {
        if let Some(substr) = from.get(range) {
            Ok(SubStr {
                string: from.clone(),
                // Safety: we got a valid 'Some(substr)' above.
                substr: unsafe { ConstPtr::new_unchecked(substr) },
            })
        } else {
            Err(RangeError)
        }
    }

    /// Create a `SubStr` as range from an existing `SubStr`.
    pub fn sub_range(&self, range: Range<usize>) -> Result<SubStr, RangeError> {
        if let Some(substr) = unsafe { self.substr.as_ref() }.get(range) {
            Ok(SubStr {
                string: self.string.clone(),
                // Safety: we got a valid 'Some(substr)' above.
                substr: unsafe { ConstPtr::new_unchecked(substr) },
            })
        } else {
            Err(RangeError)
        }
    }

    /// Create a `SubStr` that spans a complete `'static str`,
    #[must_use]
    #[inline]
    pub const fn from_static(from: &'static str) -> SubStr {
        SubStr {
            string: CowStr::from_static(from),
            // Safety: 'from' is a valid reference.
            substr: unsafe { ConstPtr::new_unchecked(from) },
        }
    }

    /// Returns `Ok(&'static str)` when the underlying string has static lifetime and
    /// `Err(&'self str)` when it is dynamically allocated.
    #[inline]
    pub fn deref_static(&self) -> Result<&'static str, &str> {
        match self.string.deref_static() {
            Ok(_) => Ok(unsafe { self.substr.as_ref() }),
            Err(_) => Err(unsafe { self.substr.as_ref() }),
        }
    }

    /// Get a reference to the original `CowStr` from which this `SubStr` is constructed.
    #[inline]
    #[must_use]
    pub fn origin(&self) -> &CowStr {
        &self.string
    }

    /// Returns 'true' when two `SubStrs` share the same original `CowStr`.
    #[inline]
    #[must_use]
    pub fn same_origin(&self, other: &Self) -> bool {
        self.origin().is_same_as(other.origin())
    }

    /// Create a `SubStr` from its raw components.
    ///
    /// # Safety
    ///
    /// `substr` must be pointing into a slice of `from`.
    #[inline]
    #[must_use]
    pub unsafe fn from_raw_parts(from: CowStr, substr: ConstPtr<str>) -> SubStr {
        debug_assert!(
            from.as_bytes()
                .as_ptr_range()
                .contains(&substr.cast::<u8>().as_ptr()),
            "`substr` is not part of `from`"
        );
        SubStr {
            string: from,
            substr,
        }
    }

    /// Deconstructs the `Substr` into its raw parts. Note that the returned `ConstPtr<str>`
    /// will only be valid as long the owning `CowStr` is not mutated or dropped. The only
    /// exception here are the `try_push_*()` methods which neither reallocate nor mutate the
    /// slice that is referenced by the `ConstPtr<str>`.
    #[inline]
    #[must_use]
    pub fn into_raw_parts(self) -> (CowStr, ConstPtr<str>) {
        (self.string, self.substr)
    }
}

impl Deref for SubStr {
    type Target = str;

    #[inline]
    fn deref(&self) -> &Self::Target {
        // Safety: substr is always valid
        unsafe { self.substr.as_ref() }
    }
}

impl From<&str> for SubStr {
    #[inline]
    fn from(s: &str) -> Self {
        SubStr::from(CowStr::from(s))
    }
}

impl From<CowStr> for SubStr {
    #[inline]
    fn from(cow: CowStr) -> Self {
        SubStr::new(&cow)
    }
}

impl From<&CowStr> for SubStr {
    #[inline]
    fn from(cow: &CowStr) -> Self {
        SubStr::new(cow)
    }
}

#[test]
fn from() {
    let origin = CowStr::from("test string");
    let substr = SubStr::from(origin);
    assert_eq!(&*substr, "test string");
}

#[test]
fn empty() {
    assert_eq!(&*EMPTY_SUBSTR, "");
}

#[test]
fn from_contained_str() {
    let origin = CowStr::from("test string");
    let (left, right) = origin.as_str().split_once(' ').unwrap();
    let left = SubStr::from_contained_str(&origin, left);
    let right = SubStr::from_contained_str(&origin, right);
    assert_eq!(&*left, "test");
    assert_eq!(&*right, "string");
}

#[test]
fn from_ref() {
    let origin = CowStr::from("test string");
    let substr = SubStr::from(&origin);
    assert_eq!(&*substr, "test string");
}

#[test]
fn new_range() {
    let origin = CowStr::from("test string");
    let substr = SubStr::new_range(&origin, 0..4).unwrap();
    assert_eq!(&*substr, "test");

    let subsubstr = substr.sub_range(1..3).unwrap();
    assert_eq!(&*subsubstr, "es");
}

#[test]
#[should_panic]
fn from_panic() {
    let origin = CowStr::from("test string");
    let _substr = SubStr::new_range(&origin, 10..100).unwrap();
}

#[test]
fn deref_static_static() {
    let static_origin = CowStr::from_static("test string");
    let static_substr = SubStr::from(&static_origin);
    assert_eq!(static_substr.deref_static(), Ok("test string"));
}

#[test]
fn deref_static_dynamic() {
    let dynamic_origin = CowStr::from("test string");
    let dynamic_substr = SubStr::from(&dynamic_origin);
    assert_eq!(dynamic_substr.deref_static(), Err("test string"));
}

#[test]
fn same_origin() {
    let origin = CowStr::from("foobar");
    let sub1 = SubStr::new_range(&origin, 0..3).unwrap();
    let sub2 = SubStr::new_range(&origin, 3..6).unwrap();
    assert_eq!(&*sub1, "foo");
    assert_eq!(&*sub2, "bar");
    assert!(sub1.same_origin(&sub2));
}

#[test]
fn different_origin() {
    let sub1 = SubStr::from("foo");
    let sub2 = SubStr::from("bar");
    assert!(!sub1.same_origin(&sub2));
}