A flexible, simple to use, immutable, clone-efficient String replacement for Rust

Overview

flexstr

Crate Docs

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 an Rc wrapped str
    • NOT Send or Sync (due to usage of Rc)
  • AFlexStr
    • Equivalent to FlexStr but uses Arc instead of Rc for the wrapped str
    • Both Send and Sync

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 a String will result in an inline string (if short) otherwise copied into a str wrapped in Rc/Arc (which will allocate, copy, and then release original String storage)
  • into_flex_str() and into_a_flex_str() are equivalent to calling into() on both literals and String (they are present primarily for let bindings so there is no need to declare a type)
  • to_flex_str() and to_a_flex_str() are meant for the on-boarding of borrowed strings and always copy into either an inline string (for short strings) or an Rc/Arc wrapped str (which will allocate)
  • to_string always copies into a new String
  • Conversions back and forth between AFlexStr and FlexStr using into() 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 or Arc

Negatives

There is no free lunch:

  • Due to usage of Rc (or Arc), when on-boarding String 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 not Send or Sync, 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:

Comments
  • Minor unsafe code cleanup suggestion

    Minor unsafe code cleanup suggestion

    I've been meaning to give unions a try on kstring and finally did. A couple of differences in my implementation

    • I extract out the padding to a dedicated type. This helps enforce the unsafe invariant at compile time because "nothing" can access the MaybeUninit (yeah technically you can since its in the same file but it'll be more obvious)
    • I created an unused TagVariant that exists just to extract the tag from the union. I feel this makes the intent of the code clearer than arbitrarily using a specific union variant to access the tag.

    If not interested; thats fine. I just figured I'd share in case you felt it improved things.

    enhancement wontfix 
    opened by epage 7
  • Store the length in the tag?

    Store the length in the tag?

    This could allow FlexStr to store 23 bytes, instead of 22 bytes. Unsure if there is extra overhead from doing this that would could make the benefits iffy.

    One step further, compact_str stores the length, tag. and the final byte in the 24th byte, leveraging unused bits in a terminating UTF-8 character.

    wontfix 
    opened by epage 5
  • Could not compile flexstr

    Could not compile flexstr

    I'm unable to compile flexStr. cargo reports 64 errors such as:

    error: type parameters must be declared prior to const parameters
      --> /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/flexstr-0.9.1/src/traits.rs:12:75
       |
    12 | pub trait Repeat<const SIZE: usize, const PAD1: usize, const PAD2: usize, HEAP> {
       |                 ----------------------------------------------------------^^^^- help: reorder the parameters: lifetimes, then types, then consts: `<HEAP, const SIZE: usize, const PAD1: usize, const PAD2: usize>`
    
    error: type parameters must be declared prior to const parameters
      --> /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/flexstr-0.9.1/src/traits.rs:17:63
       |
    17 | impl<const SIZE: usize, const PAD1: usize, const PAD2: usize, HEAP> Repeat<SIZE, PAD1, PAD2, HEAP>
       |     ----------------------------------------------------------^^^^- help: reorder the parameters: lifetimes, then types, then consts: `<HEAP, const SIZE: usize, const PAD1: usize, const PAD2: usize>`
    
    error: type parameters must be declared prior to const parameters
      --> /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/flexstr-0.9.1/src/traits.rs:35:63
       |
    35 | impl<const SIZE: usize, const PAD1: usize, const PAD2: usize, HEAP> Repeat<SIZE, PAD1, PAD2, HEAP>
       |     ----------------------------------------------------------^^^^- help: reorder the parameters: lifetimes, then types, then consts: `<HEAP, const SIZE: usize, const PAD1: usize, const PAD2: usize>`
    

    My env:

    cargo 1.58.0 (f01b232bc 2022-01-19)
    release: 1.58.0
    commit-hash: f01b232bc7f4d94f0c4603930a5a96277715eb8c
    commit-date: 2022-01-19
    host: x86_64-unknown-linux-gnu
    libgit2: 1.3.0 (sys:0.13.23 vendored)
    libcurl: 7.80.0-DEV (sys:0.4.51+curl-7.80.0 vendored ssl:OpenSSL/1.1.1l)
    os: Ubuntu 20.04 (focal) [64-bit]
    
    opened by CyriacBr 4
  • FlexStr should implement Borrow<str>

    FlexStr should implement Borrow

    It would be good if FlexStr would implement std::borrow::Borrow<str>. Right now you cannot use it as the key in a hashmap and look up the associated value with a regular &str.

    bug 
    opened by oliver-giersch 3
  • Fix typo in features list

    Fix typo in features list

    It took me a while to realize what the issue was, as the error output was correct, but also easy to miss the actual typo/reason:

    the package `x` depends on `flexstr`, with features: `fast_format, fp_convert` but `flexstr` does not have these features.
    
    opened by asaaki 0
  • Update compact_str to v0.4

    Update compact_str to v0.4

    Context In the most recent version of compact_str, we renamed CompactStr to CompactString. You can continue to use CompactStr but there is a deprecation warning on it.

    Changes This PR updates compact_str to v0.4, renaming uses of CompactStr to CompactString to prevent the warning

    opened by ParkMyCar 2
  • Byte and OsStr variants?

    Byte and OsStr variants?

    I'm still undecided on what type I'll use in clap (Cow<'static, T> or a more specialized crate) but I'll be needing both str and OsStr support.

    On reddit, it sounded like there is also interest in byte strings. Hopefully byte strings and OsStr will be merged soon but unsure if/when that'll happen.

    enhancement 
    opened by epage 5
  • Custom Ref/Cow to preserve `&'static str`?

    Custom Ref/Cow to preserve `&'static str`?

    This was also one of they key early features for KString so that liquid could pass around data throughout the program and avoid allocations for this data.

    enhancement 
    opened by epage 13
Owner
Scott Meeuwsen
Scott Meeuwsen
A (mostly) drop-in replacement for Rust's Result that provides backtrace support

Errant A (mostly) drop-in replacement for Rust's Result that provides backtrace support. Please note that Errant is still very early in development an

Joshua Barretto 17 Dec 26, 2022
Rustyread is a drop in replacement of badread simulate.

Rustyread is a drop in replacement of badread simulate. Rustyread is very heavily inspired by badread, it reuses the same error and quality model file. But Rustyreads is multi-threaded and benefits from other optimizations.

Pierre Marijon 20 Oct 1, 2022
A direct replacement for `assert_eq` for unordered collections

assert_unordered A direct replacement for assert_eq for unordered collections This macro is useful for any situation where the ordering of the collect

Scott Meeuwsen 10 Nov 29, 2022
Modern Drop-in Replacement for Nginx AutoIndex / FancyIndex!

MeowIndex A cute, feature-rich file listing module to replace nginx's autoindex / fancyindex. Features List files Show file icons Clickable, length-sa

Hykilpikonna 4 Feb 25, 2023
Rust library for program synthesis of string transformations from input-output examples ๐Ÿ”ฎ

Synox implements program synthesis of string transformations from input-output examples. Perhaps the most well-known use of string program synthesis in end-user programs is the Flash Fill feature in Excel. These string transformations are learned from input-output examples.

Anish Athalye 21 Apr 27, 2022
Annoyed that Rust has many string types? Well it doesn't have to

generic-str The one true string type in Rust! This project intends to be a proof-of-concept for an idea I had a few months back. There is lots of unsa

Conrad Ludgate 40 Apr 9, 2022
Novus - A blazingly fast and efficient package manager for windows.

Novus - A blazingly fast and efficient package manager for windows. Why Novus Swift Unlike any other package manager, Novus uses multithreaded downloads

Novus 197 Dec 18, 2022
๐Ÿฆ€๐Ÿš€๐Ÿ”ฅ A blazingly fast and memory-efficient implementation of `if err != nil` ๐Ÿ”ฅ๐Ÿš€๐Ÿฆ€

?????? A blazingly fast and memory-efficient implementation of `if err != nil` ??????

Federico Damiรกn Schonborn 6 Dec 30, 2022
Achieve it! How you ask? Well, it's pretty simple; just use greatness!

Greatness! Achieve it! How you ask? Well, it's pretty simple; just use greatness! Disclaimer I do not believe that greatness is the best. It fits a me

Isacc Barker (Milo Banks) 107 Sep 28, 2022
This is a simple Telegram bot with interface to Firefly III to process and store simple transactions.

Firefly Telegram Bot Fireflies are free, so beautiful. (Les lucioles sont libres, donc belles.) โ€• Charles de Leusse, Les Contes de la nuit This is a s

null 13 Dec 14, 2022
Easy-to-use optional function arguments for Rust

OptArgs uses const generics to ensure compile-time correctness. I've taken the liberty of expanding and humanizing the macros in the reference examples.

Jonathan Kelley 37 Nov 18, 2022
Use explicit container types with Scrypto! Leverage the Rust compiler's type checking to increase security and productivity when developing Radix blueprints.

Scrypto Static Types Use explicit container types with Scrypto! Leverage the Rust compiler's type checking to increase security and productivity when

null 7 Aug 5, 2022
A high-level Rust crate around the Discord API, aimed to be easy and straight-forward to use.

rs-cord A high-level Rust crate around the Discord API, aimed to be easy and straight-forward to use. Documentation โ€ข Crates.io โ€ข Discord Navigation M

Jay3332 4 Sep 24, 2022
The easiest way to use BotiCord API in Rust

The easiest way to use BotiCord API in Rust ยท Docs Usage [dependencies]

BotiCord 6 Feb 14, 2022
Powerfull Discord Raid Bot written in Rust, use VPN / Proxy because creating 200 channels in 10s Will ratelimit you.

Harakiri-Rust This the first Discord Raid Bot made in RustLang I recommend you use with a VPN or a Proxy to evade Discord Ratelimit. If bot doesn't st

Marco 6 May 1, 2023
๐Ÿ’ก Use the right package manager by rust

n ?? Use the right package manager by rust ?? Inspired by ni Why ni is nice , but ni is based on Node. it is difficult to collaborate well with node v

SHEIN 3 Jul 24, 2023
Rust macro to use a match-like syntax as a elegant alternative to nesting if-else statement

cond Rust macro to use a match-like syntax as an elegant alternative to many if-else statements. I got the idea from empty Go switch statements. I tho

CheckM4te 3 Nov 23, 2023
MeiliSearch is a powerful, fast, open-source, easy to use and deploy search engine

MeiliSearch is a powerful, fast, open-source, easy to use and deploy search engine. Both searching and indexing are highly customizable. Features such as typo-tolerance, filters, and synonyms are provided out-of-the-box. For more information about features go to our documentation.

MeiliSearch 31.6k Dec 30, 2022
very cool esoteric language pls use

okfrick has one memory pointer has less than 5 characters hopefully works well is turing complete (possibly) + - increase memeory pointer value ( - st

null 3 Jun 24, 2021