26 个版本 (8 个破坏性更新)
0.9.2 | 2022 年 4 月 18 日 |
---|---|
0.9.1 | 2022 年 3 月 24 日 |
0.8.1 |
|
#78 in 内存管理
16,303 每月下载量
用于 19 个 crates (13 个直接使用)
89KB
1.5K SLoC
flexstr
为 Rust 提供一个灵活、易于使用、不可变、克隆高效的 String
替代方案。它将字面量、内联和堆分配的字符串统一成一个单一类型。
目录
概述
Rust 很棒,但它的 String
类型被优化为可变字符串缓冲区,而不是针对典型的字符串使用案例。大多数字符串使用案例不会修改其内容,通常需要像整数一样复制字符串,通常进行连接而不是修改,并且通常最终被克隆为内容相同的副本。此外,String
无法在不进行额外分配和复制的情况下包装字符串字面量,这迫使用户在效率和存储两种不同类型之间做出选择。
我认为 Rust 需要一个新的字符串类型来统一字面量和分配字符串的典型字符串使用案例。此 crate 包含一个针对这些用例进行优化的新字符串类型,同时保留了 String
的使用简单性。
示例
字符串常量可以轻松地包装到统一字符串类型中。字符串数据在可能的情况下自动内联,否则在堆上分配。
use flexstr::{local_str, LocalStr, ToLocalStr};
fn main() {
// Use `local_str` macro to wrap literals as compile-time constants
const STATIC_STR: LocalStr = local_str!("This will not allocate or copy");
assert!(STATIC_STR.is_static());
// Strings up to 22 bytes (on 64-bit) will be inlined automatically
// (demo only, use macro or `from_static` for literals as above)
let inline_str = "inlined".to_local_str();
assert!(inline_str.is_inline());
// When a string is too long to be wrapped/inlined, it will heap allocate
// (demo only, use macro or `from_static` for literals as above)
let rc_str = "This is too long to be inlined".to_local_str();
assert!(rc_str.is_heap());
}
安装
可选功能
fast_format
= 启用local_ufmt!
和shared_ufmt!
format!
类的宏,以实现非常快速的格式化(有一些限制)fp_convert
= 将浮点类型直接转换为FlexStr
int_convert
= 将整型类型直接转换为FlexStr
serde
= 为FlexStr
提供序列化支持std
= 默认启用(使用default-features=false
启用#[no_std]
)
[dependencies.flexstr]
version = "0.9"
features = ["fast_format, fp_convert", "int_convert", "serde"]
它是如何工作的?
内部,FlexStr
使用一个联合体,包含以下变体
Static
- 一个静态字符串字面量的简单包装(&'static str
)Inline
- 内联字符串(小字符串不需要堆分配)Heap
- 堆分配(引用计数)的字符串
类型会自动选择最佳存储,并允许您将其互换使用,作为一个单一的字符串类型。
特性
- 针对不可变性和低成本的克隆进行了优化
- 允许同一字符串内存内容的多重所有者
- 充当通用字符串类型(统一字面量和分配的字符串)
- 对于字面量和短字符串不进行分配(64位:最多22字节)
- 与
String
具有相同的内联大小(64位:24字节) - 可选的
serde
序列化支持(功能 = "serde") - 与嵌入式系统兼容(支持
#[no_std]
) - 高效的条件所有者(借用可以不进行分配/复制而获得所有权)
- 单线程兼容(
LocalStr
)和多线程安全(SharedStr
)选项 - 所有依赖项都是可选的,并且基于功能使用
- 使用非常简单!
类型
注意:两种类型在处理字面量和内联字符串方面完全相同。唯一的区别发生在需要堆分配时。
LocalStr
- 在本地线程中使用的超快Heap
存储基于Rc
SharedStr
- 提供多线程使用时的Send
/Sync
Heap
存储基于Arc
用法
你好,世界
use flexstr::local_str;
fn main() {
// From literal - no copying or allocation
let world = local_str!("world!");
println!("Hello {world}");
}
创建场景
use flexstr::{local_str, LocalStr, IntoSharedStr, IntoLocalStr, ToLocalStr};
fn main() {
// From literal - no runtime, all compile-time
const literal: LocalStr = local_str!("literal");
// From borrowed string - Copied into inline string
let owned = "inlined".to_string();
let str_to_inlined = owned.to_local_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_local_str();
// From String - copied into inline string (`String` storage released)
let inlined = "inlined".to_string().into_local_str();
// From String - `str` wrapped in `Rc` (`String` storage released)
let counted = "A bit too long to be inlined!!!".to_string().into_local_str();
// *** If you want a Send/Sync type you need `SharedStr` instead ***
// From LocalStr wrapped literal - no copying or allocation
let literal2 = literal.into_shared_str();
// From LocalStr inlined string - no allocation
let inlined = inlined.into_shared_str();
// From LocalStr `Rc` wrapped `str` - copies into `str` wrapped in `Arc`
let counted = counted.into_shared_str();
}
将 FlexStr 传递给条件所有权函数
这始终是 Rust 中的一个令人困惑的情况,但使用 FlexStr
非常简单,因为多重所有权成本低。通过将 &LocalStr
而不是 &str
作为参数传递,您可以保留非常快速的多重所有权的选项。
use flexstr::{local_str, IntoLocalStr, LocalStr};
struct MyStruct {
s: LocalStr
}
impl MyStruct {
fn to_own_or_not_to_own(s: &LocalStr) -> Self {
let s = if s == "own me" {
// Since a wrapped literal, no copy or allocation
s.clone()
} else {
// Wrapped literal - no copy or allocation
local_str!("own me")
};
Self { s }
}
}
fn main() {
// Wrapped literals - compile time constant
const S: LocalStr = local_str!("borrow me");
const S2: LocalStr = local_str!("own me");
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);
}
创建您自己的字符串类型
您需要做的只是选择一个存储类型。存储类型必须实现 Deref<Target = str>
、From<&str>
和 Clone
。几乎所有的智能指针都这样做。
注意
自定义混凝土类型需要指定一个堆类型,其大小正好为两个机器字(在64位系统上为16字节,在32位系统上为8字节)。任何其他大小的参数都将在字符串创建时导致运行时恐慌错误信息。
use flexstr::{FlexStrBase, Repeat, ToFlex};
type BoxStr = FlexStrBase<Box<str>>;
fn main() {
// Any need for a heap string will now be allocated in a `Box` instead of `Rc`
// However, the below uses static and inline storage...because we can!
let my_str = BoxStr::from_static("cool!").repeat_n(3);
assert_eq!(my_str, "cool!cool!cool!");
}
性能特征
- 克隆成本低,且从不分配。
- 它们至少是联合的副本,最多增加一个引用计数。
- 字面量仅在用于
into()
时被包装,永远不会复制。 - 在
String
上调用into()
将产生内联字符串(如果短)否则复制到str
包装在Rc
/Arc
中(这将分配、复制然后释放原始String
存储空间) into_local_str()
和into_shared_str()
等同于在字面量和String
上调用into()
(它们主要存在于let
绑定,因此无需声明类型)to_local_str()
和to_shared_str()
用于接收借用字符串的所有权,并且始终复制到内联字符串(对于短字符串)或包装在Rc
/Arc
中的str
(这将分配)to_string
总是复制到一个新的String
- 使用
into()
在SharedStr
和LocalStr
之间来回转换,当使用包装字面量或内联字符串时成本低。- 内联字符串和包装字面量仅创建一个新的联合包装器。
- 引用计数的包装字符串始终需要为新
Rc
或Arc
进行分配和复制。
基准测试
通常,内联/静态创建速度快,但堆创建比 String
略慢。克隆速度非常快,不进行分配/复制。其他操作(重复、添加等)的性能大致相同,但会根据字符串大小有所差异。
缺点
没有免费的午餐
- 由于使用了
Rc
(或Arc
),当引入String
时,它将需要重新分配和复制。 - 由于联合包装器,每个字符串操作都有额外分支操作的开销。
- 由于
LocalStr
不是Send
或Sync
,需要考虑单线程(LocalStr
)和多线程(SharedStr
)的使用案例并相应地进行转换。
状态
这目前是测试版质量,还需要测试。API可能会发生变化,但将遵循语义版本。
许可
此项目可以选择性地根据以下任一许可进行许可:
- Apache License, Version 2.0, (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- 麻省理工学院许可证 (LICENSE-MIT 或 https://opensource.org/licenses/MIT)
依赖项
~38–275KB