flexstr
A flexible, simple to use, immutable, clone-efficient String
replacement for Rust
Overview
Rust is great, but it's String
type is optimized as a mutable string buffer, not for typical string use cases. Most string use cases don't modify their string contents, often need to copy strings around as if they were cheap like integers, typically concatenate instead of modify, and often end up being cloned with identical contents. Additionally, String
isn't able to wrap a string literal without additional allocation and copying. Rust needs a new string type to unify usage of both literals and allocated strings in typical use cases. This crate creates a new string type that is optimized for those use cases, while retaining the usage simplicity of String
.
This type is not inherently "better" than String
, but different. It is a higher level type, that can at times mean higher overhead. It really depends on the use case.
Features
- Optimized for immutability and cheap cloning
- Allows for multiple ownership of the same string memory contents
- Serves as a universal string type (unifying literals and allocated strings)
- Doesn't allocate for literals and short strings (64-bit: up to 22 bytes)
- The same size as a
String
(64-bit: 24 bytes) - Optional
serde
serialization support (feature = "serde") - Compatible with embedded systems (doesn't use
std
) - Efficient conditional ownership (borrows can take ownership without allocation/copying)
- It is simple to use!
Types
FlexStr
- Wrapper type for string literals (
&'static str
), inlined strings (InlineFlexStr
), or anRc
wrappedstr
- NOT
Send
orSync
(due to usage ofRc
)
- Wrapper type for string literals (
AFlexStr
- Equivalent to
FlexStr
but usesArc
instead ofRc
for the wrappedstr
- Both
Send
andSync
- Equivalent to
Usage
Hello World
use flexstr::IntoFlexStr;
fn main() {
// Literal - no copying or allocation
let hello = "world!".into_flex_str();
println!("Hello {world}");
}
Conversions
use flexstr::{IntoAFlexStr, IntoFlexStr, ToFlexStr};
fn main() {
// From literal - no copying or allocation
// NOTE: `to_flex_str` will copy, so use `into_flex_str` for literals
let literal = "literal".into_flex_str();
// From borrowed string - Copied into inline string
let owned = "inlined".to_string();
let str_to_inlined = (&owned).to_flex_str();
// From borrowed String - copied into `str` wrapped in `Rc`
let owned = "A bit too long to be inlined!!!".to_string();
let str_to_wrapped = (&owned).to_flex_str();
// From String - copied into inline string (`String` storage released)
let inlined = "inlined".to_string().into_flex_str();
// From String - `str` wrapped in `Rc` (`String` storage released)
let counted = "A bit too long to be inlined!!!".to_string().into_flex_str();
// *** If you want a Send/Sync type you need `AFlexStr` instead ***
// From FlexStr wrapped literal - no copying or allocation
let literal = literal.into_a_flex_str();
// From FlexStr inlined string - no allocation
let inlined = inlined.into_a_flex_str();
// From FlexStr `Rc` wrapped `str` - copies into `str` wrapped in `Arc`
let counted = counted.into_a_flex_str();
}
Borrowing
Works just like String
NOTE: The only benefit to passing as a &str
is more compatibility with existing code. By passing as a &FlexStr
instead, we retain the possibility of cheap multi ownership (see below).
use flexstr::FlexStr;
fn my_func(str: &FlexStr) {
println!("Borrowed string: {str}");
}
fn main() {
// Literal - no copy or allocation
let str: FlexStr = "my string".into();
my_func(&str);
}
Passing FlexStr to Conditional Ownership Functions
This has always been a confusing situation in Rust, but it is easy with FlexStr
since multi ownership is cheap.
use flexstr::{IntoFlexStr, FlexStr};
struct MyStruct {
s: FlexStr
}
impl MyStruct {
fn to_own_or_not_to_own(s: &FlexStr) -> Self {
let s = if s == "own_me" {
// Since a wrapped literal, no copy or allocation
s.clone()
} else {
// Wrapped literal - no copy or allocation
"own_me".into()
};
Self { s }
}
}
fn main() {
// Wrapped literals - no copy or allocation
let s = "borrow me".into_flex_str();
let s2 = "own me".into_flex_str();
let struct1 = MyStruct::to_own_or_not_to_own(&s);
let struct2 = MyStruct::to_own_or_not_to_own(&s2);
assert_eq!(s2, struct1.s);
assert_eq!(s2, struct2.s);
}
Performance Characteristics
NOTE: No benchmarking has yet been done
- Clones are cheap and never allocate
- At minimum, they are just a copy of the enum and at max an additional reference count increment
- Literals are just wrapped when used with
into()
and never copied - Calling
into()
on aString
will result in an inline string (if short) otherwise copied into astr
wrapped inRc
/Arc
(which will allocate, copy, and then release originalString
storage) into_flex_str()
andinto_a_flex_str()
are equivalent to callinginto()
on both literals andString
(they are present primarily forlet
bindings so there is no need to declare a type)to_flex_str()
andto_a_flex_str()
are meant for the on-boarding of borrowed strings and always copy into either an inline string (for short strings) or anRc
/Arc
wrappedstr
(which will allocate)to_string
always copies into a newString
- Conversions back and forth between
AFlexStr
andFlexStr
usinginto()
are cheap when using wrapped literals or inlined strings- Inlined strings and wrapped literals just create a new enum wrapper
- Reference counted wrapped strings will always require an allocation and copy for the new
Rc
orArc
Negatives
There is no free lunch:
- Due to usage of
Rc
(orArc
), when on-boardingString
it will need to reallocate and copy - Due to the enum wrapper, every string operation has the overhead of an extra branching operation
- Since
FlexStr
is notSend
orSync
, there is a need to consider single-threaded (FlexStr
) and multi-threaded (AFlexStr
) use cases and convert accordingly
Status
This is currently Alpha quality and in heavy development. There is much testing and design work still needed. The API may break at any time.
License
This project is licensed optionally under either:
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)