Added ArcStr::with_mut.

This prevents copying out of an `Arc`, then right back into another.
This commit is contained in:
Filipe Rodrigues 2024-08-25 16:50:42 +01:00
parent c13e92c004
commit c131983c39
Signed by: zenithsiz
SSH Key Fingerprint: SHA256:Mb5ppb3Sh7IarBO/sBTXLHbYEOz37hJAlslLQPPAPaU
2 changed files with 58 additions and 15 deletions

View File

@ -7,7 +7,7 @@ use {
pattern::{Pattern, PatternOp},
},
crate::{ast, util::ArcStr},
std::{fmt, mem},
std::fmt,
};
/// Expression component
@ -51,12 +51,7 @@ impl Expr {
/// Pushes a string into this expression
pub fn push_str(&mut self, s: &ArcStr) {
match self.cmpts.last_mut() {
Some(ExprCmpt::String(last)) => {
let mut last_s = String::from(mem::take(last));
last_s.push_str(s);
*last = last_s.into();
},
Some(ExprCmpt::String(last)) => last.with_mut(|last| last.push_str(s)),
_ => self.cmpts.push(ExprCmpt::String(s.clone())),
}
}

View File

@ -10,7 +10,7 @@ use std::{
fmt,
hash::{Hash, Hasher},
ops::Deref,
ptr::{self, NonNull},
ptr::NonNull,
str::pattern::{Pattern, ReverseSearcher},
sync::Arc,
};
@ -33,6 +33,56 @@ pub struct ArcStr {
}
impl ArcStr {
/// Returns the offset of this string compared to the base
fn base_offset(&self) -> usize {
// SAFETY: `self.ptr` was derived from `inner.base_ptr`
let start = unsafe { self.ptr.as_ptr().byte_offset_from(self.inner.as_ptr()) };
usize::try_from(start).expect("String pointer was before base pointer")
}
/// Updates this string as a `&mut String`.
///
/// Copies the string unless no other copies exist
pub fn with_mut<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut String) -> R,
{
// Get the offset and length of our specific string
let start = self.base_offset();
let len = self.len();
// Get the inner string
let s = match Arc::get_mut(&mut self.inner) {
// If we're unique, slice the parts we don't care about and return
Some(s) => {
// Since we're invalidating `self.inner`, replace `ptr`
// with a dummy value in case of panics.
self.ptr = NonNull::from("");
s.truncate(start + len);
let _ = s.drain(..start);
s
},
// Otherwise copy
None => {
self.inner = Arc::new(self.to_string());
self.ptr = NonNull::from(self.inner.as_str());
Arc::get_mut(&mut self.inner).expect("Should be unique")
},
};
// Then mutate
let output = f(s);
// And finally, reconstruct ourselves
self.ptr = NonNull::from(s.as_str());
output
}
/// Creates a sub-slice of `self` containing `s`.
///
/// # Panics
@ -145,15 +195,13 @@ impl From<String> for ArcStr {
impl From<ArcStr> for String {
fn from(s: ArcStr) -> Self {
// Get the offset and length of our specific string
let start = s.base_offset();
let len = s.len();
match Arc::try_unwrap(s.inner) {
Ok(mut inner) => {
// Get the offset and length of our specific string
// SAFETY: `s.ptr` was derived from `inner.base_ptr`
let start = unsafe { s.ptr.as_ptr().byte_offset_from(inner.as_ptr()) };
let start = usize::try_from(start).expect("String pointer was before base pointer");
let len = ptr::metadata(s.ptr.as_ptr());
// And slice the string to fit
// Slice the string to fit
let _ = inner.drain(start + len..);
let _ = inner.drain(..start);