flexstr
A flexible, simple to use, immutable, clone-efficient String replacement for
Rust. It unifies literals, inlined, and heap allocated strings into a single
type.
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 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 these 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
works best in 'typical' string use cases (immutability, concatenation, cheap
multi ownership) whereas String works better in "string buffer" use cases
(mutability, string building, single ownership).
Installation
NOTE: The serde feature is optional and only included when specified.
[]
= { = "0.4", = ["serde"] }
Examples
use ;
How Does It Work?
Internally, FlexStr uses an enum with these variants:
Static- A simple wrapper around a static string literal (&'static str)Inlined- An inlined string (no heap allocation for small strings)Heap- A heap allocated (reference counted) string
The type automatically chooses the best storage and allows you to use them interchangeably as a single string type.
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
serdeserialization support (feature = "serde") - Compatible with embedded systems (doesn't use
std) - Efficient conditional ownership (borrows can take ownership without allocation/copying)
- Both single threaded compatible (
FlexStr) and multi-thread safe (AFlexStr) options - It is simple to use!
Types
FlexStr- regular usageHeapstorage based onRc
AFlexStr- providesSend/Syncfor multi-threaded useHeapstorage based onArc
Usage
Hello World
use IntoFlexStr;
Conversions
use ;
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 ;
Performance Characteristics
- 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 aStringwill result in an inline string (if short) otherwise copied into astrwrapped inRc/Arc(which will allocate, copy, and then release originalStringstorage) into_flex_str()andinto_a_flex_str()are equivalent to callinginto()on both literals andString(they are present primarily forletbindings so there is no need to declare a type)to_flex_str()andto_a_flex_str()are meant for taking ownership of borrowed strings and always copy into either an inline string (for short strings) or anRc/Arcwrappedstr(which will allocate)to_stringalways copies into a newString- Conversions back and forth between
AFlexStrandFlexStrusinginto()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
RcorArc
Benchmarks
Summmary: Creates are fairly expensive (yet) compared to String, but clones
are MUCH cheaper.
Keep in mind even though creates are more expensive that depending on your workload you may earn that back via clones and it will save memory as well.
Create
create_static_normal time: [3.6473 ns 3.6613 ns 3.6782 ns]
create_inline_small time: [9.4807 ns 9.4990 ns 9.5192 ns]
create_heap_normal time: [13.597 ns 13.620 ns 13.647 ns]
create_heap_large time: [19.031 ns 19.062 ns 19.095 ns]
create_heap_arc_normal time: [18.617 ns 18.640 ns 18.664 ns]
create_heap_arc_large time: [24.490 ns 24.532 ns 24.578 ns]
create_string_small time: [7.2761 ns 7.2809 ns 7.2860 ns]
create_string_normal time: [7.6338 ns 7.6401 ns 7.6475 ns]
create_string_large time: [13.227 ns 13.318 ns 13.406 ns]
Clone
clone_static_normal time: [3.8704 ns 3.8750 ns 3.8799 ns]
clone_inline_small time: [4.5057 ns 4.5090 ns 4.5125 ns]
clone_heap_normal time: [4.4501 ns 4.4546 ns 4.4597 ns]
clone_heap_arc_normal time: [10.701 ns 10.717 ns 10.735 ns]
clone_string_small time: [10.986 ns 11.074 ns 11.164 ns]
clone_string_normal time: [12.817 ns 12.828 ns 12.842 ns]
clone_string_large time: [14.659 ns 14.780 ns 14.889 ns]
Negatives
There is no free lunch:
- Due to usage of
Rc(orArc), when on-boardingStringit will need to reallocate and copy - Due to the enum wrapper, every string operation has the overhead of an extra branching operation
- Since
FlexStris notSendorSync, there is a need to consider single-threaded (FlexStr) and multi-threaded (AFlexStr) use cases and convert accordingly
Status
This is currently beta quality and still needs testing. The API may very possibly change but semantic versioning will be followed.
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)