Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions integration-tests/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,25 @@ macro_rules! bench_one (
}
);

(as_ref $x:expr, $_y:expr) => (
(as_str $x:expr, $_y:expr) => (
#[bench]
fn as_ref_x_1000(b: &mut Bencher) {
fn as_str_x_1000(b: &mut Bencher) {
let x = $x;
b.iter(|| {
for _ in 0..1000 {
black_box(x.as_ref());
black_box(x.as_str());
}
});
}
);

(as_bytes $x:expr, $_y:expr) => (
#[bench]
fn as_bytes_x_1000(b: &mut Bencher) {
let x = $x;
b.iter(|| {
for _ in 0..1000 {
black_box(x.as_bytes());
}
});
}
Expand Down Expand Up @@ -152,22 +164,22 @@ bench_all!([eq ne lt clone_string] for medium_string = "xyzzy01", "xyzzy02");
bench_all!([eq ne lt clone_string]
for longer_string = super::longer_dynamic_a, super::longer_dynamic_b);

bench_all!([eq ne intern as_ref clone is_static lt]
bench_all!([eq ne intern as_str as_bytes clone is_static lt]
for static_atom = test_atom!("defaults"), test_atom!("font-weight"));

bench_all!([intern as_ref clone is_inline]
bench_all!([intern as_str as_bytes clone is_inline]
for short_inline_atom = mk("e"), mk("f"));

bench_all!([eq ne intern as_ref clone is_inline lt]
bench_all!([eq ne intern as_str as_bytes clone is_inline lt]
for medium_inline_atom = mk("xyzzy01"), mk("xyzzy02"));

bench_all!([intern as_ref clone is_dynamic]
bench_all!([intern as_str as_bytes clone is_dynamic]
for min_dynamic_atom = mk("xyzzy001"), mk("xyzzy002"));

bench_all!([eq ne intern as_ref clone is_dynamic lt]
bench_all!([eq ne intern as_str as_bytes clone is_dynamic lt]
for longer_dynamic_atom = mk(super::longer_dynamic_a), mk(super::longer_dynamic_b));

bench_all!([intern as_ref clone is_static]
bench_all!([intern as_str as_bytes clone is_static]
for static_at_runtime = mk("defaults"), mk("font-weight"));

bench_all!([ne lt x_static y_inline]
Expand Down
77 changes: 70 additions & 7 deletions integration-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,87 @@ include!(concat!(env!("OUT_DIR"), "/test_atom.rs"));
pub type Atom = TestAtom;

#[test]
fn test_as_slice() {
fn test_as_str() {
let s0 = Atom::from("");
assert!(s0.as_ref() == "");
assert!(s0.as_str() == "");

let s1 = Atom::from("class");
assert!(s1.as_ref() == "class");
assert!(s1.as_str() == "class");

let i0 = Atom::from("blah");
assert!(i0.as_ref() == "blah");
assert!(i0.as_str() == "blah");

let s0 = Atom::from("BLAH");
assert!(s0.as_ref() == "BLAH");
assert!(s0.as_str() == "BLAH");

let d0 = Atom::from("zzzzzzzzzz");
assert!(d0.as_ref() == "zzzzzzzzzz");
assert!(d0.as_str() == "zzzzzzzzzz");

let d1 = Atom::from("ZZZZZZZZZZ");
assert!(d1.as_ref() == "ZZZZZZZZZZ");
assert!(d1.as_str() == "ZZZZZZZZZZ");
}

#[test]
fn test_as_bytes() {
let s0 = Atom::from("");
assert!(s0.as_bytes() == b"");

let s1 = Atom::from("class");
assert!(s1.as_bytes() == b"class");

let i0 = Atom::from("blah");
assert!(i0.as_bytes() == b"blah");

let s0 = Atom::from("BLAH");
assert!(s0.as_bytes() == b"BLAH");

let d0 = Atom::from("zzzzzzzzzz");
assert!(d0.as_bytes() == b"zzzzzzzzzz");

let d1 = Atom::from("ZZZZZZZZZZ");
assert!(d1.as_bytes() == b"ZZZZZZZZZZ");
}

#[test]
fn test_as_ref_str() {
let s0 = Atom::from("");
assert!(AsRef::<str>::as_ref(&s0) == "");

let s1 = Atom::from("class");
assert!(AsRef::<str>::as_ref(&s1) == "class");

let i0 = Atom::from("blah");
assert!(AsRef::<str>::as_ref(&i0) == "blah");

let s0 = Atom::from("BLAH");
assert!(AsRef::<str>::as_ref(&s0) == "BLAH");

let d0 = Atom::from("zzzzzzzzzz");
assert!(AsRef::<str>::as_ref(&d0) == "zzzzzzzzzz");

let d1 = Atom::from("ZZZZZZZZZZ");
assert!(AsRef::<str>::as_ref(&d1) == "ZZZZZZZZZZ");
}

#[test]
fn test_as_ref_bytes() {
let s0 = Atom::from("");
assert!(AsRef::<[u8]>::as_ref(&s0) == b"");

let s1 = Atom::from("class");
assert!(AsRef::<[u8]>::as_ref(&s1) == b"class");

let i0 = Atom::from("blah");
assert!(AsRef::<[u8]>::as_ref(&i0) == b"blah");

let s0 = Atom::from("BLAH");
assert!(AsRef::<[u8]>::as_ref(&s0) == b"BLAH");

let d0 = Atom::from("zzzzzzzzzz");
assert!(AsRef::<[u8]>::as_ref(&d0) == b"zzzzzzzzzz");

let d1 = Atom::from("ZZZZZZZZZZ");
assert!(AsRef::<[u8]>::as_ref(&d1) == b"ZZZZZZZZZZ");
}

#[test]
Expand Down
82 changes: 47 additions & 35 deletions src/atom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,33 @@ impl<Static: StaticAtomSet> Atom<Static> {
Err(hash)
}
}

/// Get a reference to the underlying str.
#[inline]
pub fn as_str(&self) -> &str {
unsafe {
match self.tag() {
DYNAMIC_TAG => {
let entry = self.unsafe_data.get() as *const Entry;
&(*entry).string
}
INLINE_TAG => {
let len = (self.unsafe_data() & LEN_MASK) >> LEN_OFFSET;
debug_assert!(len as usize <= MAX_INLINE_LEN);
let src = inline_atom_slice(&self.unsafe_data);
str::from_utf8_unchecked(src.get_unchecked(..(len as usize)))
}
STATIC_TAG => Static::get().atoms[self.static_index() as usize],
_ => debug_unreachable!(),
}
}
}

Comment on lines +199 to +217

@SimonSapin SimonSapin Aug 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should all optimize away but I’d prefer going through fewer layers of abstraction. The actual logic is in the Deref impl so, here and in the new as_bytes method let’s use this:

Suggested change
pub fn as_str(&self) -> &str {
AsRef::as_ref(self)
}
pub fn as_str(&self) -> &str {
self // auto-deref
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I'm actually just going to implement all of the logic in as_str as as_bytes, and then the traits can call into these. I think that'll make the code much easier to follow.

/// Get a reference to the bytes of the underlying str.
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
Comment on lines +220 to +222

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn as_bytes(&self) -> &[u8] {
AsRef::as_ref(self)
}
pub fn as_bytes(&self) -> &[u8] {
str::as_bytes(self)
}

}

impl<Static: StaticAtomSet> Default for Atom<Static> {
Expand Down Expand Up @@ -276,22 +303,7 @@ impl<Static: StaticAtomSet> ops::Deref for Atom<Static> {

#[inline]
fn deref(&self) -> &str {
unsafe {
match self.tag() {
DYNAMIC_TAG => {
let entry = self.unsafe_data.get() as *const Entry;
&(*entry).string
}
INLINE_TAG => {
let len = (self.unsafe_data() & LEN_MASK) >> LEN_OFFSET;
debug_assert!(len as usize <= MAX_INLINE_LEN);
let src = inline_atom_slice(&self.unsafe_data);
str::from_utf8_unchecked(src.get_unchecked(..(len as usize)))
}
STATIC_TAG => Static::get().atoms[self.static_index() as usize],
_ => debug_unreachable!(),
}
}
self.as_str()
}
}

Expand All @@ -317,7 +329,7 @@ impl<Static: StaticAtomSet> PartialOrd for Atom<Static> {
if self.unsafe_data == other.unsafe_data {
return Some(Equal);
}
self.as_ref().partial_cmp(other.as_ref())
self.as_str().partial_cmp(other.as_ref())
}
}

Expand All @@ -327,7 +339,7 @@ impl<Static: StaticAtomSet> Ord for Atom<Static> {
if self.unsafe_data == other.unsafe_data {
return Equal;
}
self.as_ref().cmp(other.as_ref())
self.as_str().cmp(other.as_ref())
}
}

Expand Down Expand Up @@ -392,24 +404,24 @@ impl<Static: StaticAtomSet> Atom<Static> {

#[inline(always)]
fn inline_atom_slice(x: &NonZeroU64) -> &[u8] {
let x: *const NonZeroU64 = x;
let mut data = x as *const u8;
// All except the lowest byte, which is first in little-endian, last in big-endian.
if cfg!(target_endian = "little") {
data = unsafe { data.offset(1) };
}
let len = 7;
unsafe { slice::from_raw_parts(data, len) }
let x: *const NonZeroU64 = x;
let mut data = x as *const u8;
// All except the lowest byte, which is first in little-endian, last in big-endian.
if cfg!(target_endian = "little") {
data = unsafe { data.offset(1) };
}
let len = 7;
unsafe { slice::from_raw_parts(data, len) }
}

#[inline(always)]
fn inline_atom_slice_mut(x: &mut u64) -> &mut [u8] {
let x: *mut u64 = x;
let mut data = x as *mut u8;
// All except the lowest byte, which is first in little-endian, last in big-endian.
if cfg!(target_endian = "little") {
data = unsafe { data.offset(1) };
}
let len = 7;
unsafe { slice::from_raw_parts_mut(data, len) }
fn inline_atom_slice_mut(x: &mut u64) -> &mut [u8] {
let x: *mut u64 = x;
let mut data = x as *mut u8;
// All except the lowest byte, which is first in little-endian, last in big-endian.
if cfg!(target_endian = "little") {
data = unsafe { data.offset(1) };
}
let len = 7;
unsafe { slice::from_raw_parts_mut(data, len) }
}
10 changes: 9 additions & 1 deletion src/trivial_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,16 @@ impl<Static: StaticAtomSet> fmt::Display for Atom<Static> {
}

impl<Static: StaticAtomSet> AsRef<str> for Atom<Static> {
#[inline]
fn as_ref(&self) -> &str {
self
self.as_str()
}
}

impl<Static: StaticAtomSet> AsRef<[u8]> for Atom<Static> {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
Comment thread
SimonSapin marked this conversation as resolved.
}
}

Expand Down
Loading