gpui_shared_string: Implement SharedString via smol_str (#54649)

This reduces the size of Sharedstring from 32 bytes to 24 while also
allowing for small-string optimization, meaning strings with length < 23
bytes will not actually allocate.

Release Notes:

- N/A or Added/Fixed/Improved ...
This commit is contained in:
Lukas Wirth 2026-04-24 10:58:26 +02:00 committed by GitHub
parent b9b50e3b9e
commit 58d3a9eef4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 100 additions and 43 deletions

11
Cargo.lock generated
View file

@ -7839,10 +7839,9 @@ dependencies = [
name = "gpui_shared_string"
version = "0.1.0"
dependencies = [
"derive_more",
"gpui_util",
"schemars 1.0.4",
"serde",
"smol_str",
]
[[package]]
@ -16527,9 +16526,13 @@ dependencies = [
[[package]]
name = "smol_str"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f7a918bd2a9951d18ee6e48f076843e8e73a9a5d22cf05bcd4b7a81bdd04e17"
checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523"
dependencies = [
"borsh",
"serde_core",
]
[[package]]
name = "snafu"

View file

@ -291,7 +291,7 @@ impl StyledText {
/// Set the text runs for this piece of text.
pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
let mut text = &**self.text;
let mut text = &*self.text;
for run in &runs {
text = text.get(run.len..).unwrap_or_else(|| {
#[cfg(debug_assertions)]

View file

@ -784,7 +784,7 @@ mod tests {
}],
len: text.len(),
}),
text: SharedString::new(text.to_string()),
text: SharedString::new(text),
decoration_runs: SmallVec::from(decorations.to_vec()),
}
}

View file

@ -2933,9 +2933,6 @@ impl Window {
/// Push a text style onto the stack, and call a function with that style active.
/// Use [`Window::text_style`] to get the current, combined text style. This method
/// should only be called as part of element drawing.
// This function is called in a highly recursive manner in editor
// prepainting, make sure its inlined to reduce the stack burden
#[inline]
pub fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
where
F: FnOnce(&mut Self) -> R,

View file

@ -8,10 +8,9 @@ edition.workspace = true
path = "gpui_shared_string.rs"
[dependencies]
derive_more.workspace = true
gpui_util.workspace = true
schemars.workspace = true
serde.workspace = true
smol_str = "0.3.6"
[lints]
workspace = true

View file

@ -1,27 +1,35 @@
use derive_more::{Deref, DerefMut};
use gpui_util::arc_cow::ArcCow;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
borrow::{Borrow, Cow},
sync::Arc,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
/// A shared string is an immutable string that can be cheaply cloned in GPUI
/// tasks. Essentially an abstraction over an `Arc<str>` and `&'static str`,
#[derive(Deref, DerefMut, Eq, PartialEq, PartialOrd, Ord, Hash, Clone)]
pub struct SharedString(ArcCow<'static, str>);
/// currently backed by a [`SmolStr`].
#[derive(Eq, PartialEq, PartialOrd, Ord, Hash, Clone)]
pub struct SharedString(SmolStr);
impl std::ops::Deref for SharedString {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.as_str()
}
}
impl SharedString {
/// Creates a static [`SharedString`] from a `&'static str`.
pub const fn new_static(str: &'static str) -> Self {
Self(ArcCow::Borrowed(str))
Self(SmolStr::new_static(str))
}
/// Creates a [`SharedString`] from anything that can become an `Arc<str>`
pub fn new(str: impl Into<Arc<str>>) -> Self {
SharedString(ArcCow::Owned(str.into()))
/// Creates a [`SharedString`].
pub fn new(str: impl AsRef<str>) -> Self {
SharedString(SmolStr::new(str))
}
/// Get a &str from the underlying string.
@ -46,7 +54,7 @@ impl JsonSchema for SharedString {
impl Default for SharedString {
fn default() -> Self {
Self(ArcCow::Borrowed(""))
Self::new_static("")
}
}
@ -70,7 +78,7 @@ impl std::fmt::Debug for SharedString {
impl std::fmt::Display for SharedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_ref())
write!(f, "{}", self.0.as_str())
}
}
@ -99,29 +107,79 @@ impl<'a> PartialEq<&'a str> for SharedString {
}
impl From<&SharedString> for SharedString {
fn from(value: &SharedString) -> Self {
value.clone()
#[inline]
fn from(s: &SharedString) -> SharedString {
s.clone()
}
}
impl From<&str> for SharedString {
#[inline]
fn from(s: &str) -> SharedString {
SharedString(SmolStr::from(s))
}
}
impl From<&mut str> for SharedString {
#[inline]
fn from(s: &mut str) -> SharedString {
SharedString(SmolStr::from(s))
}
}
impl From<&String> for SharedString {
#[inline]
fn from(s: &String) -> SharedString {
SharedString(SmolStr::from(s))
}
}
impl From<String> for SharedString {
#[inline(always)]
fn from(text: String) -> Self {
SharedString(SmolStr::from(text))
}
}
impl From<Box<str>> for SharedString {
#[inline]
fn from(s: Box<str>) -> SharedString {
SharedString(SmolStr::from(s))
}
}
impl From<Arc<str>> for SharedString {
#[inline]
fn from(s: Arc<str>) -> SharedString {
SharedString(SmolStr::from(s))
}
}
impl From<&Arc<str>> for SharedString {
#[inline]
fn from(s: &Arc<str>) -> SharedString {
SharedString(SmolStr::from(s.clone()))
}
}
impl<'a> From<Cow<'a, str>> for SharedString {
#[inline]
fn from(s: Cow<'a, str>) -> SharedString {
SharedString(SmolStr::from(s))
}
}
impl From<SharedString> for Arc<str> {
fn from(val: SharedString) -> Self {
match val.0 {
ArcCow::Borrowed(borrowed) => Arc::from(borrowed),
ArcCow::Owned(owned) => owned,
}
}
}
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
fn from(value: T) -> Self {
Self(value.into())
#[inline(always)]
fn from(text: SharedString) -> Self {
text.0.into()
}
}
impl From<SharedString> for String {
fn from(val: SharedString) -> Self {
val.0.to_string()
#[inline(always)]
fn from(text: SharedString) -> Self {
text.0.into()
}
}
@ -140,6 +198,6 @@ impl<'de> Deserialize<'de> for SharedString {
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(SharedString::from(s))
Ok(SharedString::new(&s))
}
}

View file

@ -3627,7 +3627,7 @@ async fn save_keybinding_update(
};
let source = settings::KeybindUpdateTarget {
context: action_mapping.context.as_ref().map(|a| &***a),
context: action_mapping.context.as_deref(),
keystrokes: &action_mapping.keystrokes,
action_name: existing.action().name,
action_arguments: new_args,

View file

@ -223,7 +223,7 @@ impl ParseStepLanguage {
fn name(&self) -> SharedString {
match self {
ParseStepLanguage::Loaded { language } => language.name().0,
ParseStepLanguage::Pending { name } => name.into(),
ParseStepLanguage::Pending { name } => name.clone().into(),
}
}

View file

@ -433,7 +433,7 @@ impl PromptStore {
let metadata = metadata_cache
.metadata
.iter()
.find(|metadata| metadata.title.as_ref().map(|title| &***title) == Some(title))?;
.find(|metadata| metadata.title.as_deref() == Some(title))?;
Some(metadata.id)
}