flexstr

Crate Docs

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.

toml [dependencies] flexstr = { version = "0.7", features = ["serde"] }

Examples

```rust use flexstr::{flex_fmt, FlexStr, IntoFlexStr, ToCase, ToFlexStr};

fn main() { // Use an into function to wrap a literal, no allocation or copying let staticstr = "This will not allocate or copy".intoflexstr(); assert!(staticstr.is_static());

// Strings up to 22 bytes (on 64-bit) will be inlined automatically // (demo only, use into for literals as above) let inlinestr = "inlined".toflexstr(); assert!(inlinestr.is_inlined());

// When a string is too long to be wrapped/inlined, it will heap allocate // (demo only, use into for literals as above) let rcstr = "This is too long to be inlined".toflexstr(); assert!(rcstr.is_heap());

// You can efficiently create a new FlexStr (without creating a String) // This is equivalent to the stdlib format! macro let inlinestr2 = flexfmt!("in{}", "lined"); assert!(inlinestr2.isinlined()); asserteq!(inlinestr, inline_str2);

// We can upper/lowercase strings without converting to a String // This doesn't heap allocate let inlinestr3: FlexStr = "INLINED".toasciilower(); assert!(inlinestr3.isinlined()); asserteq!(inlinestr, inlinestr3);

// Concatenation doesn't even copy if we can fit it in the inline string let inlinestr4 = inlinestr3 + "!!!"; assert!(inlinestr4.isinlined()); asserteq!(inlinestr4, "inlined!!!");

// Clone is almost free, and never allocates // (at most it is a ref count increment for heap allocated strings) let staticstr2 = staticstr.clone(); assert!(staticstr2.isstatic());

// Regardless of storage type, these all operate seamlessly together // and choose storage as required let heapstr2 = staticstr2 + &inlinestr; assert!(heapstr2.isheap()); asserteq!(heap_str2, "This will not allocate or copyinlined"); } ```

How Does It Work?

Internally, FlexStr uses an enum with these variants:

The type automatically chooses the best storage and allows you to use them interchangeably as a single string type.

Features

Types

Usage

Hello World

```rust use flexstr::IntoFlexStr;

fn main() { // From literal - no copying or allocation let world = "world!".intoflexstr();

println!("Hello {world}"); } ```

Creation Scenarios

```rust 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".intoflexstr();

// From borrowed string - Copied into inline string let owned = "inlined".tostring(); let strtoinlined = (&owned).toflex_str();

// From borrowed String - copied into str wrapped in Rc let owned = "A bit too long to be inlined!!!".tostring(); let strtowrapped = (&owned).toflex_str();

// From String - copied into inline string (String storage released) let inlined = "inlined".tostring().intoflex_str();

// From String - str wrapped in Rc (String storage released) let counted = "A bit too long to be inlined!!!".tostring().intoflex_str();

// * If you want a Send/Sync type you need AFlexStr instead *

// From FlexStr wrapped literal - no copying or allocation let literal = literal.intoaflex_str();

// From FlexStr inlined string - no allocation let inlined = inlined.intoaflex_str();

// From FlexStr Rc wrapped str - copies into str wrapped in Arc let counted = counted.intoaflex_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. By passing as &FlexStr instead of &str, you retain the option for very fast multi ownership.

```rust use flexstr::{IntoFlexStr, FlexStr};

struct MyStruct { s: FlexStr }

impl MyStruct { fn toownornottoown(s: &FlexStr) -> Self { let s = if s == "ownme" { // 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".intoflexstr(); let s2 = "own me".intoflexstr();

let struct1 = MyStruct::toownornottoown(&s); let struct2 = MyStruct::toownornottoown(&s2);

asserteq!(s2, struct1.s); asserteq!(s2, struct2.s); } ```

Make Your Own String Type

All you need to do is pick an inline size (the default STRING_SIZED_INLINE will result in a type the same size as a String) and a storage type. The storage type must implement Deref<Target = str>, From<String>, From<&str>, and Clone. Pretty much all smart pointers do this already.

```rust use flexstr::{FlexStr, STRINGSIZEDINLINE, Repeat, ToFlex};

type BoxFlexStr = FlexStr>;

fn main() { // This will be allocated in a box instead of the default Rc // Demo only: normally use into_flex with literals just to wrap them let mystr: BoxFlexStr = "cool!".toflex().repeatn(3); asserteq!(my_str, "cool!cool!cool!"); } ```

Performance Characteristics

Benchmarks

In general, creates are somewhat slower than String, but clones and conversions from primitive types are vastly faster.

Full benchmarks

Negatives

There is no free lunch:

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: