#![allow(
// We follow libstd's lead and prefer to define both.
clippy::partialeq_ne_impl,
// This is a really annoying clippy lint, since it's required for so many cases...
clippy::cast_ptr_alignment,
// For macros
clippy::redundant_slicing,
)]
use core::alloc::Layout;
use core::mem::{align_of, size_of};
use core::ptr::NonNull;
#[cfg(not(all(loom, test)))]
pub(crate) use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(all(loom, test))]
pub(crate) use loom::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "substr")]
use crate::Substr;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::String;
#[repr(transparent)]
pub struct ArcStr(NonNull<ThinInner>);
unsafe impl Sync for ArcStr {}
unsafe impl Send for ArcStr {}
impl ArcStr {
#[inline]
pub const fn new() -> Self {
EMPTY
}
#[inline]
pub fn as_str(&self) -> &str {
self
}
#[inline]
pub fn len(&self) -> usize {
unsafe { ThinInner::get_len_flags(self.0.as_ptr()).len() }
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
#[allow(clippy::inherent_to_string_shadow_display)]
pub fn to_string(&self) -> String {
#[cfg(not(feature = "std"))]
use alloc::borrow::ToOwned;
self.as_str().to_owned()
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let p = self.0.as_ptr();
unsafe {
let len = ThinInner::get_len_flags(p).len();
let data = (p as *const u8).add(OFFSET_DATA);
debug_assert_eq!(&(*p).data as *const [u8; 0] as usize, data as usize);
core::slice::from_raw_parts(data, len)
}
}
#[inline]
pub fn into_raw(this: Self) -> NonNull<()> {
let p = this.0;
core::mem::forget(this);
p.cast()
}
#[inline]
pub unsafe fn from_raw(ptr: NonNull<()>) -> Self {
Self(ptr.cast())
}
#[inline]
pub fn ptr_eq(lhs: &Self, rhs: &Self) -> bool {
core::ptr::eq(lhs.0.as_ptr(), rhs.0.as_ptr())
}
#[inline]
pub fn strong_count(this: &Self) -> Option<usize> {
let this = this.0.as_ptr();
if unsafe { ThinInner::get_len_flags(this).is_static() } {
None
} else {
unsafe { Some((*this).strong.load(Ordering::SeqCst)) }
}
}
#[inline]
pub fn is_static(this: &Self) -> bool {
unsafe { ThinInner::get_len_flags(this.0.as_ptr()).is_static() }
}
#[inline]
pub fn as_static(this: &Self) -> Option<&'static str> {
if unsafe { ThinInner::get_len_flags(this.0.as_ptr()).is_static() } {
Some(unsafe { &*(this.as_str() as *const str) })
} else {
None
}
}
#[inline]
#[doc(hidden)]
pub const unsafe fn _private_new_from_static_data<B>(
ptr: &'static StaticArcStrInner<B>,
) -> Self {
Self(NonNull::new_unchecked(ptr as *const _ as *mut ThinInner))
}
#[cfg(feature = "substr")]
#[inline]
pub fn substr(&self, range: impl core::ops::RangeBounds<usize>) -> Substr {
Substr::from_parts(self, range)
}
#[cfg(feature = "substr")]
pub fn substr_from(&self, substr: &str) -> Substr {
if substr.is_empty() {
return Substr::new();
}
let self_start = self.as_ptr() as usize;
let self_end = self_start + self.len();
let substr_start = substr.as_ptr() as usize;
let substr_end = substr_start + substr.len();
if substr_start < self_start || substr_end > self_end {
out_of_range(self, &substr);
}
let index = substr_start - self_start;
let end = index + substr.len();
self.substr(index..end)
}
#[cfg(feature = "substr")]
pub fn try_substr_from(&self, substr: &str) -> Option<Substr> {
if substr.is_empty() {
return Some(Substr::new());
}
let self_start = self.as_ptr() as usize;
let self_end = self_start + self.len();
let substr_start = substr.as_ptr() as usize;
let substr_end = substr_start + substr.len();
if substr_start < self_start || substr_end > self_end {
return None;
}
let index = substr_start - self_start;
let end = index + substr.len();
debug_assert!(self.get(index..end).is_some());
Some(self.substr(index..end))
}
#[cfg(feature = "substr")]
pub fn try_substr_using(&self, f: impl FnOnce(&str) -> &str) -> Option<Substr> {
self.try_substr_from(f(self.as_str()))
}
#[cfg(feature = "substr")]
pub fn substr_using(&self, f: impl FnOnce(&str) -> &str) -> Substr {
self.substr_from(f(self.as_str()))
}
}
#[cold]
#[inline(never)]
#[cfg(feature = "substr")]
fn out_of_range(arc: &ArcStr, substr: &&str) -> ! {
let arc_start = arc.as_ptr() as usize;
let arc_end = arc_start + arc.len();
let substr_start = substr.as_ptr() as usize;
let substr_end = substr_start + substr.len();
panic!(
"ArcStr over ({:p}..{:p}) does not contain substr over ({:p}..{:p})",
arc_start as *const u8,
arc_end as *const u8,
substr_start as *const u8,
substr_end as *const u8,
)
}
impl Clone for ArcStr {
#[inline]
fn clone(&self) -> Self {
let this = self.0.as_ptr();
unsafe {
let is_static = ThinInner::get_len_flags(this).is_static();
if !is_static {
let n = (*this).strong.fetch_add(1, Ordering::Relaxed);
if n > (isize::MAX as usize) {
abort();
}
}
}
Self(self.0)
}
}
impl Drop for ArcStr {
#[inline]
fn drop(&mut self) {
let this = self.0.as_ptr();
unsafe {
if ThinInner::get_len_flags(this).is_static() {
return;
}
if (*this).strong.fetch_sub(1, Ordering::Release) == 1 {
let _ = (*this).strong.load(Ordering::Acquire);
ThinInner::destroy_cold(this)
}
}
}
}
#[repr(C, align(8))]
struct ThinInner {
len_flags: LenFlags,
strong: AtomicUsize,
data: [u8; 0],
}
const OFFSET_LENFLAGS: usize = 0;
const OFFSET_STRONGCOUNT: usize = size_of::<LenFlags>();
const OFFSET_DATA: usize = OFFSET_STRONGCOUNT + size_of::<AtomicUsize>();
#[repr(C, align(8))]
#[doc(hidden)]
pub struct StaticArcStrInner<Buf> {
pub len_flags: usize,
pub count: usize,
pub data: Buf,
}
const _: [(); size_of::<StaticArcStrInner<[u8; 0]>>()] = [(); 2 * size_of::<usize>()];
const _: [(); align_of::<StaticArcStrInner<[u8; 0]>>()] = [(); 8];
const _: [(); size_of::<StaticArcStrInner<[u8; 2 * size_of::<usize>()]>>()] =
[(); 4 * size_of::<usize>()];
const _: [(); align_of::<StaticArcStrInner<[u8; 2 * size_of::<usize>()]>>()] = [(); 8];
const _: [(); size_of::<ThinInner>()] = [(); 2 * size_of::<usize>()];
const _: [(); align_of::<ThinInner>()] = [(); 8];
const _: [(); align_of::<AtomicUsize>()] = [(); align_of::<usize>()];
const _: [(); align_of::<AtomicUsize>()] = [(); size_of::<usize>()];
const _: [(); size_of::<AtomicUsize>()] = [(); size_of::<usize>()];
const _: [(); align_of::<LenFlags>()] = [(); align_of::<usize>()];
const _: [(); size_of::<LenFlags>()] = [(); size_of::<usize>()];
#[derive(Clone, Copy)]
#[repr(transparent)]
struct LenFlags(usize);
impl LenFlags {
#[inline]
const fn len(self) -> usize {
self.0 >> 1
}
#[inline]
const fn is_static(self) -> bool {
(self.0 & 1) == 0
}
#[inline]
fn from_len_static(l: usize, is_static: bool) -> Option<Self> {
l.checked_mul(2).map(|l| Self(l | (!is_static as usize)))
}
#[inline]
const fn from_len_static_raw(l: usize, is_static: bool) -> Self {
Self(l << 1 | (!is_static as usize))
}
}
const EMPTY: ArcStr = literal!("");
impl ThinInner {
fn allocate(data: &str) -> NonNull<Self> {
const ALIGN: usize = align_of::<ThinInner>();
let num_bytes = data.len();
debug_assert_ne!(num_bytes, 0);
let mo = OFFSET_DATA;
if num_bytes >= (isize::MAX as usize) - (mo + ALIGN) {
alloc_overflow();
}
unsafe {
debug_assert!(Layout::from_size_align(num_bytes + mo, ALIGN).is_ok());
let layout = Layout::from_size_align_unchecked(num_bytes + mo, ALIGN);
let alloced = alloc::alloc::alloc(layout);
if alloced.is_null() {
alloc::alloc::handle_alloc_error(layout);
}
let ptr = alloced as *mut ThinInner;
debug_assert!(LenFlags::from_len_static(num_bytes, false).is_some());
let lf = LenFlags::from_len_static_raw(num_bytes, false);
debug_assert_eq!(lf.len(), num_bytes);
debug_assert!(!lf.is_static());
core::ptr::write(&mut (*ptr).len_flags, lf);
core::ptr::write(&mut (*ptr).strong, AtomicUsize::new(1));
debug_assert_eq!(
(alloced as *const u8).wrapping_add(mo),
(*ptr).data.as_ptr(),
);
debug_assert_eq!(&(*ptr).data as *const _ as *const u8, (*ptr).data.as_ptr());
core::ptr::copy_nonoverlapping(data.as_ptr(), alloced.add(mo), num_bytes);
NonNull::new_unchecked(ptr)
}
}
#[inline]
unsafe fn get_len_flags(p: *const ThinInner) -> LenFlags {
debug_assert_eq!(OFFSET_LENFLAGS, 0);
*p.cast()
}
#[cold]
unsafe fn destroy_cold(p: *mut ThinInner) {
let lf = Self::get_len_flags(p);
debug_assert!(!lf.is_static());
let len = lf.len();
let layout = {
let size = len + OFFSET_DATA;
let align = align_of::<ThinInner>();
Layout::from_size_align_unchecked(size, align)
};
alloc::alloc::dealloc(p as *mut _, layout);
}
}
#[inline(never)]
#[cold]
fn alloc_overflow() -> ! {
panic!("overflow during Layout computation")
}
impl From<&str> for ArcStr {
#[inline]
fn from(s: &str) -> Self {
if s.is_empty() {
Self::new()
} else {
Self(ThinInner::allocate(s))
}
}
}
impl core::ops::Deref for ArcStr {
type Target = str;
#[inline]
fn deref(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
}
impl Default for ArcStr {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl From<String> for ArcStr {
#[inline]
fn from(v: String) -> Self {
v.as_str().into()
}
}
impl From<&mut str> for ArcStr {
#[inline]
fn from(s: &mut str) -> Self {
let s: &str = s;
Self::from(s)
}
}
impl From<Box<str>> for ArcStr {
#[inline]
fn from(s: Box<str>) -> Self {
Self::from(&s[..])
}
}
impl From<ArcStr> for Box<str> {
#[inline]
fn from(s: ArcStr) -> Self {
s.as_str().into()
}
}
impl From<ArcStr> for alloc::rc::Rc<str> {
#[inline]
fn from(s: ArcStr) -> Self {
s.as_str().into()
}
}
impl From<ArcStr> for alloc::sync::Arc<str> {
#[inline]
fn from(s: ArcStr) -> Self {
s.as_str().into()
}
}
impl From<alloc::rc::Rc<str>> for ArcStr {
#[inline]
fn from(s: alloc::rc::Rc<str>) -> Self {
let s: &str = &*s;
Self::from(s)
}
}
impl From<alloc::sync::Arc<str>> for ArcStr {
#[inline]
fn from(s: alloc::sync::Arc<str>) -> Self {
let s: &str = &*s;
Self::from(s)
}
}
impl<'a> From<Cow<'a, str>> for ArcStr {
#[inline]
fn from(s: Cow<'a, str>) -> Self {
let s: &str = &*s;
Self::from(s)
}
}
impl<'a> From<&'a ArcStr> for Cow<'a, str> {
#[inline]
fn from(s: &'a ArcStr) -> Self {
Cow::Borrowed(s)
}
}
impl<'a> From<ArcStr> for Cow<'a, str> {
#[inline]
fn from(s: ArcStr) -> Self {
if let Some(st) = ArcStr::as_static(&s) {
Cow::Borrowed(st)
} else {
Cow::Owned(s.to_string())
}
}
}
impl From<&String> for ArcStr {
#[inline]
fn from(s: &String) -> Self {
Self::from(s.as_str())
}
}
impl From<&ArcStr> for ArcStr {
#[inline]
fn from(s: &ArcStr) -> Self {
s.clone()
}
}
impl core::fmt::Debug for ArcStr {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self.as_str(), f)
}
}
impl core::fmt::Display for ArcStr {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self.as_str(), f)
}
}
impl PartialEq for ArcStr {
#[inline]
fn eq(&self, o: &Self) -> bool {
ArcStr::ptr_eq(self, o) || PartialEq::eq(self.as_str(), o.as_str())
}
#[inline]
fn ne(&self, o: &Self) -> bool {
!ArcStr::ptr_eq(self, o) && PartialEq::ne(self.as_str(), o.as_str())
}
}
impl Eq for ArcStr {}
macro_rules! impl_peq {
(@one $a:ty, $b:ty) => {
impl<'a> PartialEq<$b> for $a {
#[inline]
fn eq(&self, s: &$b) -> bool {
PartialEq::eq(&self[..], &s[..])
}
#[inline]
fn ne(&self, s: &$b) -> bool {
PartialEq::ne(&self[..], &s[..])
}
}
};
($(($a:ty, $b:ty),)+) => {$(
impl_peq!(@one $a, $b);
impl_peq!(@one $b, $a);
)+};
}
impl_peq! {
(ArcStr, str),
(ArcStr, &'a str),
(ArcStr, String),
(ArcStr, Cow<'a, str>),
(ArcStr, Box<str>),
(ArcStr, alloc::sync::Arc<str>),
(ArcStr, alloc::rc::Rc<str>),
}
impl PartialOrd for ArcStr {
#[inline]
fn partial_cmp(&self, s: &Self) -> Option<core::cmp::Ordering> {
Some(self.as_str().cmp(s.as_str()))
}
}
impl Ord for ArcStr {
#[inline]
fn cmp(&self, s: &Self) -> core::cmp::Ordering {
self.as_str().cmp(s.as_str())
}
}
impl core::hash::Hash for ArcStr {
#[inline]
fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
self.as_str().hash(h)
}
}
macro_rules! impl_index {
($($IdxT:ty,)*) => {$(
impl core::ops::Index<$IdxT> for ArcStr {
type Output = str;
#[inline]
fn index(&self, i: $IdxT) -> &Self::Output {
&self.as_str()[i]
}
}
)*};
}
impl_index! {
core::ops::RangeFull,
core::ops::Range<usize>,
core::ops::RangeFrom<usize>,
core::ops::RangeTo<usize>,
core::ops::RangeInclusive<usize>,
core::ops::RangeToInclusive<usize>,
}
impl AsRef<str> for ArcStr {
#[inline]
fn as_ref(&self) -> &str {
self
}
}
impl AsRef<[u8]> for ArcStr {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl core::borrow::Borrow<str> for ArcStr {
#[inline]
fn borrow(&self) -> &str {
self
}
}
impl core::str::FromStr for ArcStr {
type Err = core::convert::Infallible;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s))
}
}
#[cold]
#[inline(never)]
#[cfg(not(feature = "std"))]
fn abort() -> ! {
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("fatal error: second panic")
}
}
let _double_panicer = PanicOnDrop;
panic!("fatal error: aborting via double panic");
}
#[cfg(feature = "std")]
use std::process::abort;
#[cfg(test)]
mod test {
use super::*;
fn sasi_layout_check<Buf>() {
assert!(align_of::<StaticArcStrInner<Buf>>() >= 8);
assert_eq!(
memoffset::offset_of!(StaticArcStrInner<Buf>, count),
OFFSET_STRONGCOUNT
);
assert_eq!(
memoffset::offset_of!(StaticArcStrInner<Buf>, len_flags),
OFFSET_LENFLAGS
);
assert_eq!(
memoffset::offset_of!(StaticArcStrInner<Buf>, data),
OFFSET_DATA
);
assert_eq!(
memoffset::offset_of!(ThinInner, strong),
memoffset::offset_of!(StaticArcStrInner::<Buf>, count),
);
assert_eq!(
memoffset::offset_of!(ThinInner, len_flags),
memoffset::offset_of!(StaticArcStrInner::<Buf>, len_flags),
);
assert_eq!(
memoffset::offset_of!(ThinInner, data),
memoffset::offset_of!(StaticArcStrInner::<Buf>, data),
);
}
#[test]
fn verify_type_pun_offsets_sasi_big_bufs() {
assert_eq!(memoffset::offset_of!(ThinInner, strong), OFFSET_STRONGCOUNT);
assert_eq!(memoffset::offset_of!(ThinInner, len_flags), OFFSET_LENFLAGS);
assert_eq!(memoffset::offset_of!(ThinInner, data), OFFSET_DATA);
assert!(align_of::<ThinInner>() >= 8);
sasi_layout_check::<[u8; 0]>();
sasi_layout_check::<[u8; 1]>();
sasi_layout_check::<[u8; 2]>();
sasi_layout_check::<[u8; 3]>();
sasi_layout_check::<[u8; 4]>();
sasi_layout_check::<[u8; 5]>();
sasi_layout_check::<[u8; 15]>();
sasi_layout_check::<[u8; 16]>();
sasi_layout_check::<[u8; 64]>();
sasi_layout_check::<[u8; 128]>();
sasi_layout_check::<[u8; 1024]>();
sasi_layout_check::<[u8; 4095]>();
sasi_layout_check::<[u8; 4096]>();
}
}
#[cfg(all(test, loom))]
mod loomtest {
use super::ArcStr;
use loom::sync::Arc;
use loom::thread;
#[test]
fn cloning_threads() {
loom::model(|| {
let a = ArcStr::from("abcdefgh");
let addr = a.as_ptr() as usize;
let a1 = Arc::new(a);
let a2 = a1.clone();
let t1 = thread::spawn(move || {
let b: ArcStr = (*a1).clone();
assert_eq!(b.as_ptr() as usize, addr);
});
let t2 = thread::spawn(move || {
let b: ArcStr = (*a2).clone();
assert_eq!(b.as_ptr() as usize, addr);
});
t1.join().unwrap();
t2.join().unwrap();
});
}
#[test]
fn drop_timing() {
loom::model(|| {
let a1 = (0..5)
.map(|i| ArcStr::from(alloc::format!("s{}", i)))
.cycle()
.take(10)
.collect::<alloc::vec::Vec<_>>();
let a2 = a1.clone();
let t1 = thread::spawn(move || {
let mut a1 = a1;
while let Some(s) = a1.pop() {
assert!(s.starts_with("s"));
}
});
let t2 = thread::spawn(move || {
let mut a2 = a2;
while let Some(s) = a2.pop() {
assert!(s.starts_with("s"));
}
});
t1.join().unwrap();
t2.join().unwrap();
});
}
}