flexstr
A flexible, simple to use, clone-efficient String replacement for Rust. It unifies borrowed, inlined, referenced counted and boxed strings into a single type.
Overview
TL;DR - If you've used Cow, but you wish cloning owned strings was more performant and that being owned didn't always imply heap allocation, this crate might be what you are looking for. The operations are "lazy" (like Cow), and it tries not to do work the user is not expecting.
Longer:
There are now many clone efficient, inlined string crates available at this point, but this crate is a bit different. First, it is simple: it is just an enum, so you are always in control of what type of string it contains. Its basic semantics are modeled after the basic Cow in the stdlib. Cow is pretty handy, but borrowed/owned alone isn't always sufficient (this crate adds ref counted and inlined strings). Clones should ideally not allocate new space (until mutation is required). Also, it would be nice if short strings didn't allocate at all, since short strings are often prevalent. The goal was a unified string type that can handle just about any situation, and bring all those use cases together in a single type.
Each one of the enum variants excels at different use cases, but is brought together in a single type for maximum flexibility:
- Borrowed - Clone/copy performance, memory efficiency and optimal
&strinterop - Inlined - Clone/copy performance for short strings, memory efficiency and mutability
- RefCounted - Clone performance for long strings, memory efficiency and optimal
Arc<str>/Rc<str>interop - Boxed - Mutability and optimal
String/Box<str>interop
If you have used previous versions of this crate, you should be aware this new version is a ground up rewrite with a solidly different thought process, API and design. Even if the previous versions didn't match your needs, this one might. Users should be aware that nearly all the string construction code is not yet present in this version. The new way to do this (workaround?) is to do the work as a String and then import it into a LocalStr or SharedStr. Moving into and out of the boxed variant (from_owned) should be near zero cost.
Lastly, this might be the only inline/clone efficient string crate that is generic over all the Rust string types (str, CStr, OsStr, Path, [u8]).
Features
- Simple: just an enum. You mostly already know how to use it.
- Borrowed, inlined, reference counted, and boxed strings in a single type
- O(1) clone
- NOTE: first
clonewhen variant isBoxedis O(n)
- NOTE: first
- Mutable (Copy-on-write under the hood, if necessary)
- Inlined string type can be used on its own
- Same size a a
String(3 words wide, even inside anOption) - Lazy instantiation (no unexpected allocations)
- No dependencies
- NOTE:
serdeoptional for serialization/deserialization
- NOTE:
- Optional
no_std - Optional
safefeature that forbids anyunsafeusage- NOTE: This does induce a performance penalty, as would be expected
- NOTE 2:
OsStr/Pathsupport on Windows requires at least one unsafe call (win_min_unsafefeature).
- Handles all Rust string types (
str,CStr,OsStr,Path,[u8])
Cargo Features
- safe = Use all safe functions and add
forbid(unsafe_code)(performance penalty) - std = Use
std(default) - serde = add
serdedependency and adds serialization/deserialization - win_min_unsafe = enables the minimum necessary unsafe code on Windows to support
OsStr/Path. No other string types or operating systems are impacted (impliessafefeature).- NOTE: The code will refuse to compile if this is not specified when ALL the following conditions are true:
- The
safefeature is enabled - The
osstrand/orpathfeature(s) are enabled - Compiling for Windows
- The
- NOTE: The code will refuse to compile if this is not specified when ALL the following conditions are true:
String Type Features:
- str = Enable
str-based strings (default) - bytes = Enable byte-based strings (
[u8]) - cstr = Enable
CStr-based strings - osstr = Enable
OsStr-based strings - path = Enable
Path-based strings (impliesosstrfeature)
Example
It is just an enum that looks like this - you can probably guess much of how it works just by looking at it:
// `S` is just the raw string type (typically `str`)
// `R` is just an `Arc` or an `Rc`.
// You would typically use it via one of the type aliases, for example:
pub type LocalStr<'s> = ;
pub type SharedStr<'s> = ;
Even that you don't really need to concern yourself with. You can just use it how you would expect a simple wrapper to behave.
use *;
// This will be a "Borrowed" variant
let hello: SharedStr = "hello".into;
assert!;
// This will be a "Boxed" variant
let world: SharedStr = "world".to_string.into;
assert!;
// This is now "Inlined" (since it is short)
let hello = hello.into_owned;
assert!;
// This is now "Inlined" as well (since it is short)
let world = world.optimize;
assert!;
println!;
Performance
In general, it performs quite well given that it is mostly just a thin wrapper over the stdlib. See the benchmarks page for more details.
AI Usage
The code was written by hand with care (although AI tab completion was used). Any contributions should be completely understood by the contributor, whether AI assisted or not.
The tests on the otherhand were 90%+ generated by AI under my instruction. I've done a cursory review for sanity, but they need more work. Volunteers welcome.
Status
This is currently experimental, however, I will be using this at a startup in production code, so it will become production ready at some point.
Contributions
Contributions are welcome so long as they align to my vision for this crate. Currently, it does most of what I want it to do (outside of string construction and mutation, but I'm not ready to start on that yet).
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)