Skip to content

Commit f8ed961

Browse files
moulinsVeykril
authored andcommitted
feat: Add SmolStr::from_static
Allows creating `SmolStr`s longer than 23 bytes in constant contexts. This is done by replacing the `Repr::Substring` variant by a more general `Repr::Static(&'static str)` variant, and borrowing from ̀`WS` directly instead of storing two `usize`s. As a bonus, it also simplifies the `as_str` implementation, hopefully saving an extra branch.
1 parent 8734783 commit f8ed961

File tree

2 files changed

+26
-16
lines changed

2 files changed

+26
-16
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77

88
A `SmolStr` is a string type that has the following properties:
99

10-
* `size_of::<SmolStr>() == 24 (therefore == size_of::<String>() on 64 bit platforms)
10+
* `size_of::<SmolStr>() == 24` (therefore `== size_of::<String>()` on 64 bit platforms)
1111
* `Clone` is `O(1)`
1212
* Strings are stack-allocated if they are:
1313
* Up to 23 bytes long
1414
* Longer than 23 bytes, but substrings of `WS` (see `src/lib.rs`). Such strings consist
1515
solely of consecutive newlines, followed by consecutive spaces
1616
* If a string does not satisfy the aforementioned conditions, it is heap-allocated
17+
* Additionally, a `SmolStr` can be explicitly created from a `&'static str` without allocation
1718

1819
Unlike `String`, however, `SmolStr` is immutable. The primary use case for
1920
`SmolStr` is a good enough default storage for tokens of typical programming

src/lib.rs

+24-15
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use core::{
2626
/// * Longer than 23 bytes, but substrings of `WS` (see below). Such strings consist
2727
/// solely of consecutive newlines, followed by consecutive spaces
2828
/// * If a string does not satisfy the aforementioned conditions, it is heap-allocated
29+
/// * Additionally, a `SmolStr` can be explicitely created from a `&'static str` without allocation
2930
///
3031
/// Unlike `String`, however, `SmolStr` is immutable. The primary use case for
3132
/// `SmolStr` is a good enough default storage for tokens of typical programming
@@ -78,6 +79,17 @@ impl SmolStr {
7879
})
7980
}
8081

82+
/// Constructs a `SmolStr` from a statically allocated string.
83+
///
84+
/// This never allocates.
85+
#[inline(always)]
86+
pub const fn new_static(text: &'static str) -> SmolStr {
87+
// NOTE: this never uses the inline storage; if a canonical
88+
// representation is needed, we could check for `len() < INLINE_CAP`
89+
// and call `new_inline`, but this would mean an extra branch.
90+
SmolStr(Repr::Static(text))
91+
}
92+
8193
pub fn new<T>(text: T) -> SmolStr
8294
where
8395
T: AsRef<str>,
@@ -395,6 +407,11 @@ const N_NEWLINES: usize = 32;
395407
const N_SPACES: usize = 128;
396408
const WS: &str =
397409
"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n ";
410+
const _: () = {
411+
assert!(WS.len() == N_NEWLINES + N_SPACES);
412+
assert!(WS.as_bytes()[N_NEWLINES - 1] == b'\n');
413+
assert!(WS.as_bytes()[N_NEWLINES] == b' ');
414+
};
398415

399416
#[derive(Clone, Copy, Debug)]
400417
#[repr(u8)]
@@ -428,18 +445,15 @@ enum InlineSize {
428445
#[derive(Clone, Debug)]
429446
enum Repr {
430447
Heap(Arc<str>),
448+
Static(&'static str),
431449
Inline {
432450
len: InlineSize,
433451
buf: [u8; INLINE_CAP],
434452
},
435-
Substring {
436-
newlines: usize,
437-
spaces: usize,
438-
},
439453
}
440454

441455
impl Repr {
442-
/// This function tries to create a new Repr::Inline or Repr::Substring
456+
/// This function tries to create a new Repr::Inline or Repr::Static
443457
/// If it isn't possible, this function returns None
444458
fn new_on_stack<T>(text: T) -> Option<Self>
445459
where
@@ -467,7 +481,8 @@ impl Repr {
467481
let possible_space_count = len - newlines;
468482
if possible_space_count <= N_SPACES && bytes[newlines..].iter().all(|&b| b == b' ') {
469483
let spaces = possible_space_count;
470-
return Some(Repr::Substring { newlines, spaces });
484+
let substring = &WS[N_NEWLINES - newlines..N_NEWLINES + spaces];
485+
return Some(Repr::Static(substring));
471486
}
472487
}
473488
None
@@ -484,36 +499,30 @@ impl Repr {
484499
fn len(&self) -> usize {
485500
match self {
486501
Repr::Heap(data) => data.len(),
502+
Repr::Static(data) => data.len(),
487503
Repr::Inline { len, .. } => *len as usize,
488-
Repr::Substring { newlines, spaces } => *newlines + *spaces,
489504
}
490505
}
491506

492507
#[inline(always)]
493508
fn is_empty(&self) -> bool {
494509
match self {
495510
Repr::Heap(data) => data.is_empty(),
511+
Repr::Static(data) => data.is_empty(),
496512
Repr::Inline { len, .. } => *len as u8 == 0,
497-
// A substring isn't created for an empty string.
498-
Repr::Substring { .. } => false,
499513
}
500514
}
501515

502516
#[inline]
503517
fn as_str(&self) -> &str {
504518
match self {
505519
Repr::Heap(data) => &*data,
520+
Repr::Static(data) => data,
506521
Repr::Inline { len, buf } => {
507522
let len = *len as usize;
508523
let buf = &buf[..len];
509524
unsafe { ::core::str::from_utf8_unchecked(buf) }
510525
}
511-
Repr::Substring { newlines, spaces } => {
512-
let newlines = *newlines;
513-
let spaces = *spaces;
514-
assert!(newlines <= N_NEWLINES && spaces <= N_SPACES);
515-
&WS[N_NEWLINES - newlines..N_NEWLINES + spaces]
516-
}
517526
}
518527
}
519528
}

0 commit comments

Comments
 (0)