Skip to content

Commit

Permalink
Add const_new(); remove new_inline() and from_static_str() (#336)
Browse files Browse the repository at this point in the history
* Rename `from_static_str()` into `const_new()`

* Remove `new_inline()`
  • Loading branch information
Kijewski authored Jan 9, 2024
1 parent b00aa81 commit 68d42f5
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 108 deletions.
50 changes: 17 additions & 33 deletions compact_str/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ impl CompactString {
/// Creates a new [`CompactString`] from any type that implements `AsRef<str>`.
/// If the string is short enough, then it will be inlined on the stack!
///
/// In a `static` or `const` context you can use the method [`CompactString::const_new()`].
///
/// # Examples
///
/// ### Inlined
Expand Down Expand Up @@ -210,51 +212,33 @@ impl CompactString {
CompactString(Repr::new(text.as_ref()).unwrap_with_msg())
}

/// Creates a new inline [`CompactString`] at compile time.
///
/// For most use cases you should use the method [`CompactString::from_static_str()`],
/// which will inline static strings, too, if they are short enough.
///
/// # Examples
/// ```
/// use compact_str::CompactString;
///
/// const DEFAULT_NAME: CompactString = CompactString::new_inline("untitled");
/// ```
///
/// Note: Trying to create a long string that can't be inlined, will fail to build.
/// ```compile_fail
/// # use compact_str::CompactString;
/// const LONG: CompactString = CompactString::new_inline("this is a long string that can't be stored on the stack");
/// ```
#[inline]
pub const fn new_inline(text: &str) -> Self {
CompactString(Repr::new_inline(text))
}

/// Creates a new inline [`CompactString`] from `&'static str` at compile time.
///
/// Complexity: O(1). As an optimization, short strings get inlined.
///
/// In a dynamic context you can use the method [`CompactString::new()`].
///
/// # Examples
/// ```
/// use compact_str::CompactString;
///
/// const DEFAULT_NAME: CompactString = CompactString::from_static_str("untitled");
/// const DEFAULT_NAME: CompactString = CompactString::const_new("untitled");
/// ```
#[inline]
pub const fn from_static_str(text: &'static str) -> Self {
CompactString(Repr::from_static_str(text))
pub const fn const_new(text: &'static str) -> Self {
CompactString(Repr::const_new(text))
}

/// Get back the `&'static str` constructed by [`CompactString::from_static_str`].
/// Get back the `&'static str` constructed by [`CompactString::const_new`].
///
/// If the string was short enough that it could be inlined, then it was inline, and
/// this method will return `None`.
///
/// # Examples
/// ```
/// use compact_str::CompactString;
///
/// const DEFAULT_NAME: CompactString =
/// CompactString::from_static_str("That is not dead which can eternal lie.");
/// CompactString::const_new("That is not dead which can eternal lie.");
/// assert_eq!(
/// DEFAULT_NAME.as_static_str().unwrap(),
/// "That is not dead which can eternal lie.",
Expand Down Expand Up @@ -958,7 +942,7 @@ impl CompactString {
#[must_use]
pub fn repeat(&self, n: usize) -> Self {
if n == 0 || self.is_empty() {
Self::new_inline("")
Self::const_new("")
} else if n == 1 {
self.clone()
} else {
Expand Down Expand Up @@ -1098,15 +1082,15 @@ impl CompactString {
///
/// ```
/// # use compact_str::CompactString;
/// let mut s = CompactString::from_static_str("Hello, world!");
/// let mut s = CompactString::const_new("Hello, world!");
/// let w = s.split_off(5);
///
/// assert_eq!(w, ", world!");
/// assert_eq!(s, "Hello");
/// ```
pub fn split_off(&mut self, at: usize) -> Self {
if let Some(s) = self.as_static_str() {
let result = Self::from_static_str(&s[at..]);
let result = Self::const_new(&s[at..]);
// SAFETY: the previous line `self[at...]` would have panicked if `at` was invalid
unsafe { self.set_len(at) };
result
Expand Down Expand Up @@ -2397,9 +2381,9 @@ impl fmt::Write for CompactString {
if self.is_empty() && !self.is_heap_allocated() {
// Since self is currently an empty inline variant or
// an empty `StaticStr` variant, constructing a new one
// with `Self::from_static_str` is more efficient since
// with `Self::const_new` is more efficient since
// it is guaranteed to be O(1).
*self = Self::from_static_str(s);
*self = Self::const_new(s);
} else {
self.push_str(s);
}
Expand Down
59 changes: 29 additions & 30 deletions compact_str/src/repr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub const STATIC_STR_MASK: u8 = LastUtf8Char::Static as u8;
/// by `LENGTH_MASK`
pub const LENGTH_MASK: u8 = 0b11000000;

const EMPTY: Repr = Repr::new_inline("");
const EMPTY: Repr = Repr::const_new("");

#[repr(C)]
pub struct Repr(
Expand Down Expand Up @@ -81,26 +81,13 @@ impl Repr {
}

#[inline]
pub const fn new_inline(text: &str) -> Self {
let len = text.len();

if len <= MAX_SIZE {
let inline = InlineBuffer::new_const(text);
Repr::from_inline(inline)
} else {
panic!("Inline string was too long, max length is `core::mem::size_of::<CompactString>()` bytes");
}
}

#[inline]
pub const fn from_static_str(text: &'static str) -> Self {
pub const fn const_new(text: &'static str) -> Self {
if text.len() <= MAX_SIZE {
let inline = InlineBuffer::new_const(text);
Self::from_inline(inline)
Repr::from_inline(inline)
} else {
let repr = StaticStr::new(text);
// SAFETY: A `StaticStr` and `Repr` have the same size
unsafe { core::mem::transmute(repr) }
Repr::from_static(repr)
}
}

Expand Down Expand Up @@ -604,6 +591,18 @@ impl Repr {
unsafe { core::mem::transmute(heap) }
}

/// Reinterprets a [`StaticStr`] into a [`Repr`]
///
/// Note: This is safe because [`StaticStr`] and [`Repr`] are the same size. We used to define
/// [`Repr`] as a `union` which implicitly transmuted between the two types, but that prevented
/// us from defining a "niche" value to make `Option<CompactString>` the same size as just
/// `CompactString`
#[inline(always)]
const fn from_static(heap: StaticStr) -> Self {
// SAFETY: A `StaticStr` and `Repr` have the same size
unsafe { core::mem::transmute(heap) }
}

/// Reinterprets a [`Repr`] as a [`HeapBuffer`]
///
/// # SAFETY
Expand Down Expand Up @@ -839,7 +838,7 @@ mod tests {
assert_eq!(repr.len(), s.len());

// test StaticStr variant
let repr = Repr::from_static_str(s);
let repr = Repr::const_new(s);
assert_eq!(repr.as_str(), s);
assert_eq!(repr.len(), s.len());
}
Expand Down Expand Up @@ -949,7 +948,7 @@ mod tests {
assert_eq!(control, s.as_str());

// test StaticStr variant
let r = Repr::from_static_str(control);
let r = Repr::const_new(control);
let s = r.into_string();

assert_eq!(control.len(), s.len());
Expand Down Expand Up @@ -983,7 +982,7 @@ mod tests {
assert_eq!(r.is_heap_allocated(), is_heap);

// test StaticStr variant
let mut r = Repr::from_static_str(control);
let mut r = Repr::const_new(control);
let mut c = String::from(control);

r.push_str(append);
Expand Down Expand Up @@ -1047,7 +1046,7 @@ mod tests {
assert_eq!(r.is_heap_allocated(), is_heap);

// Test static_str variant
let mut r = Repr::from_static_str(initial);
let mut r = Repr::const_new(initial);
r.reserve(additional).unwrap();

assert!(r.capacity() >= initial.len() + additional);
Expand Down Expand Up @@ -1078,7 +1077,7 @@ mod tests {
assert_eq!(r_a.is_heap_allocated(), r_b.is_heap_allocated());

// test StaticStr variant
let r_a = Repr::from_static_str(initial);
let r_a = Repr::const_new(initial);
let r_b = r_a.clone();

assert_eq!(r_a.as_str(), initial);
Expand All @@ -1090,14 +1089,14 @@ mod tests {
assert_eq!(r_a.is_heap_allocated(), r_b.is_heap_allocated());
}

#[test_case(Repr::from_static_str(""), Repr::from_static_str(""); "empty clone from static")]
#[test_case(Repr::new_inline("abc"), Repr::from_static_str("efg"); "short clone from static")]
#[test_case(Repr::new("i am a longer string that will be on the heap").unwrap(), Repr::from_static_str(EIGHTEEN_MB_STR); "long clone from static")]
#[test_case(Repr::from_static_str(""), Repr::new_inline(""); "empty clone from inline")]
#[test_case(Repr::new_inline("abc"), Repr::new_inline("efg"); "short clone from inline")]
#[test_case(Repr::new("i am a longer string that will be on the heap").unwrap(), Repr::new_inline("small"); "long clone from inline")]
#[test_case(Repr::from_static_str(""), Repr::new(EIGHTEEN_MB_STR).unwrap(); "empty clone from heap")]
#[test_case(Repr::new_inline("abc"), Repr::new(EIGHTEEN_MB_STR).unwrap(); "short clone from heap")]
#[test_case(Repr::const_new(""), Repr::const_new(""); "empty clone from static")]
#[test_case(Repr::const_new("abc"), Repr::const_new("efg"); "short clone from static")]
#[test_case(Repr::new("i am a longer string that will be on the heap").unwrap(), Repr::const_new(EIGHTEEN_MB_STR); "long clone from static")]
#[test_case(Repr::const_new(""), Repr::const_new(""); "empty clone from inline")]
#[test_case(Repr::const_new("abc"), Repr::const_new("efg"); "short clone from inline")]
#[test_case(Repr::new("i am a longer string that will be on the heap").unwrap(), Repr::const_new("small"); "long clone from inline")]
#[test_case(Repr::const_new(""), Repr::new(EIGHTEEN_MB_STR).unwrap(); "empty clone from heap")]
#[test_case(Repr::const_new("abc"), Repr::new(EIGHTEEN_MB_STR).unwrap(); "short clone from heap")]
#[test_case(Repr::new("i am a longer string that will be on the heap").unwrap(), Repr::new(EIGHTEEN_MB_STR).unwrap(); "long clone from heap")]
fn test_clone_from(mut initial: Repr, source: Repr) {
initial.clone_from(&source);
Expand Down
2 changes: 1 addition & 1 deletion compact_str/src/repr/smallvec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ mod tests {
assert_eq!(ex_compact, s);

// test `StaticStr` variant
let og_compact = CompactString::from_static_str(s);
let og_compact = CompactString::const_new(s);
assert_eq!(og_compact, s);

let bytes = og_compact.into_bytes();
Expand Down
17 changes: 14 additions & 3 deletions compact_str/src/repr/traits.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use core::hint::unreachable_unchecked;

use super::Repr;
use crate::ToCompactStringError;

const FALSE: Repr = Repr::new_inline("false");
const TRUE: Repr = Repr::new_inline("true");
const FALSE: Repr = Repr::const_new("false");
const TRUE: Repr = Repr::const_new("true");

/// Defines how to _efficiently_ create a [`Repr`] from `self`
pub(crate) trait IntoRepr {
Expand Down Expand Up @@ -42,7 +44,16 @@ impl IntoRepr for char {
#[inline]
fn into_repr(self) -> Result<Repr, ToCompactStringError> {
let mut buf = [0_u8; 4];
Ok(Repr::new_inline(self.encode_utf8(&mut buf)))
let s = self.encode_utf8(&mut buf);

// This match is just a hint for the compiler.
match s.len() {
1..=4 => (),
// SAFETY: a UTF-8 character is 1 to 4 bytes.
_ => unsafe { unreachable_unchecked() },
}

Ok(Repr::new(s)?)
}
}

Expand Down
Loading

0 comments on commit 68d42f5

Please sign in to comment.