#string #optimization #replace #length #safety #unsafe #globalalloc

olis_string

Rust的短字符串优化,旨在替换std::string::String

4个版本

0.1.3 2023年12月30日
0.1.2 2023年12月30日
0.1.1 2023年12月21日
0.1.0 2023年12月21日

323Rust模式

每月 28 次下载

MIT 许可证

1MB
1.5K SLoC

包含 (WOFF字体, 400KB) NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2,(WOFF字体, 135KB) FiraSans-Medium-8f9a781e4970d388.woff2,(WOFF字体, 130KB) FiraSans-Regular-018c141bf0843ffd.woff2,(WOFF字体, 82KB) SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2,(WOFF字体, 77KB) SourceSerif4-Regular-46f98efaafac5295.ttf.woff2,(WOFF字体, 45KB) SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2更多

SsoString 在 Rust 中

Rust的短字符串优化。它与新的 allocator_api 和旧的 GlobalAlloc 样式分配都兼容。如果您想使用新的 allocator_api,请将 nightly 功能设置为启用。

请注意,这并不意味着 SsoString 在全局分配器上是泛型的,遗憾的是。

仅对长度为23或更短的字符串进行短字符串优化。目标是使其成为 std::string::String 的即插即用替换。

短字符串优化仅在 #[all(target_endian = "little", target_pointer_width = "64"))] 下可用。否则,sso::String 只是 std::string::String 的别名。

我正在实施为 sso::String 的每一个 std::string::String 方法,每个方法都有声明,但大多数只是 todo_impl!。有一个方法,as_mut_vec 是不可能的...但谁会使用它呢?

我认为有用的所有方法都实现了。

安全警告

我认为由于在 SsoString::clone 上使用了 ptr::copy_non_overlapping,我们实际上没有标记该区域为初始化,导致某些内部方法出现未定义行为?我真的不确定。这仅在 long.as_mut_str.mask_ascii_lowercase 处发生...我需要进一步调查。

我能用这个吗?

这是一场我想象中的与一个永远不会存在的人的对话,但我强烈建议你不要使用它,除非你可以保证导出的类型实际上是 std::string::String 呵呵。

但说真的,尽管进行了少量的测试,它还不是严格安全的。一旦我添加关于不安全预条件的调试断言,我就会更有信心它可以安全使用。

目前,一切都 看起来 是安全的,但在 unsafe 的领域中,没有什么是它看起来的那样!

用法

基本字符串操作

use sso::String;

let mut s = String::new();
s += "Hello, world!";
assert_eq!(&s, "Hello, world!");

let exclamation_mark = s.pop();
assert_eq!(exclamation_mark, Some('!'));

字符串类型之间的自动升级

use sso::String;

let mut s = String::from("Hello, world!");
assert!(s.is_short());
assert!(!s.is_long());
assert_eq!(&s, "Hello, world!");

s += " My name is Gregory :)";
assert!(s.is_long());
assert!(!s.is_short());
assert_eq!(&s, "Hello, world! My name is Gregory :)");

使用 is_shortis_long 函数时,应先进行以下条件编译选项

#[cfg(all(target_endian = "little", target_pointer_width = "64"))]

匹配内部结构

sso::String 适用于不需要大量修改的代码。如果你有很多修改并且不想分支,可以匹配内部字符串。例如

use sso::String;

let s = String::new();
// upgrade this string, note that any additional capacity will upgrade this string, because the 
// minimum capacity is 23.
s.reserve(100); 
#[cfg(all(target_endian = "little", target_pointer_width = "64"))]
{
    assert!(s.is_long());
    match s.tagged_mut() {
        TaggedSsoString64Mut::Long(long) => {
            for _ in 0..1000 {
                long.push_str("something");
            }
        }
        TaggedSsoString64Mut::Short(..) => unreachable!(),
    }
}
#[cfg(not(all(target_endian = "little", target_pointer_width = "64")))]
{ unimplemented!() }

但这并不是一个好主意。API 不稳定,在非优化架构上无法用 std::string::String 替换。

为什么你的代码很奇怪?

稍后会有更长的解释。想法是始终维护结构的归纳,而不仅仅是它们可能实际导致未定义行为的时候。基本上,试图使 unsafe 代码真正、真正简单,以便证明安全性。

这就是为什么我的所有代码都有 # 安全性 合同和 SAFETY: 合同清除在每个 unsafe 调用点(我认为)的原因。

这也是我为什么使用 len: UnsafeWrite<usize, 0> 的原因。这样我才能在没有使用 unsafe 的情况下,无法错误地将长度设置为无效值,这让我想起要清除我可能违反的安全性合同。

为什么我不能实现impl Drop,因为否则在capacitybuf上进行语义上同时的写入(实际上并不完全准确,但已经足够好)是不可能的。例如,这段代码将变得不可能(我需要同时写入capacitybuf,以便LongString永远不会无效。

/// free the buffer of this string, setting the `len` and `capacity` to `0`
pub fn free(&mut self) {
    let capacity = self.capacity();
    *self = unsafe {
        Self {
            // SAFETY: 0 always satisfies len's invaraints
            len: UnsafeWrite::new(0),
            // SAFETY: the buffer is dangling and the capacity is 0, which is a valid
            // state for LongString
            capacity: UnsafeWrite::new(0),
            buf: UnsafeWrite::new(
                self.buf
                    .own()
                    // SAFETY: capacity is the exact size of the buffer
                    .dealloc(capacity)
                    .expect("should be the exact capacity"),
            ),
        }
    };
}

项作用域的Unsaf代码

本文档是一个初稿。事物可能没有像我希望的那样准确措辞,但我在尽力!

公理上不安全的操作

Rust将“你可以在不安全代码中做的唯一事情”定义为(重新排序)

  • 取消引用原始指针
  • 访问或修改可变静态变量
  • 访问联合的字段
  • 调用不安全函数或方法
  • 实现不安全特质

这个定义是准确的,但它更多地是从代码语义的角度来看,而不是从正确性的角度来看。当我提到某件事是“公理上不安全的”,这意味着它绝对不能在没有首先检查先决条件的情况下是一个安全操作。

例如,“调用不安全函数或方法”不是公理上不安全的,因为你可以在不检查任何先决条件的情况下证明正确性。

/// # Safety
/// always safe
unsafe fn puts(s: &str) {
    println!("{}", s);
}

// SAFETY: always safe
unsafe {
    puts("Hello, world!");
}

我可以证明这个程序是正确的,而无需检查任何先决条件。调用puts总是正确的,无论形式如何。因此,尽管调用puts是“不安全的”,但它永远不是不正确的。也就是说:我无法以调用puts的方式在另一个安全的环境中引发未定义的行为。

对于我所说的“公理上不安全的操作”来说,情况并非如此。例如,取消引用原始指针始终有一个安全合同。我们可以将此定义为具有安全合同的功能(尽管它实际上不是),但我们不能将其定义为没有安全合同的功能,否则就会是不正确的。

/// # Safety
/// - pointer is non-null
/// - pointer must be within the bounds of the allocated object
/// - the object must not have been deallocated (this is different from never having been
///   allocated. e.g. dereferencing a `NonNull::<ZST>::dangling()` is fine)
/// -
unsafe fn deref<T>(*const T) -> T;

我们可以以某种方式限制T,使此操作有效,但这将是一个不同的操作。

基本上,所有编译器内建的不安全函数都是“公理上不安全的”。例如,std::intrinsics::offset<Ptr, Delta>(Ptr, Delta)作为一个操作,不能在没有安全合同的情况下定义。然而,Rust为这个操作定义的安全合同,在作用域上略有不同。

/// Note: in reality, the type of `Ptr` is enforced by the compiler when we use stabilized 
/// methods. So we ignore this. 
/// 
/// # Safety 
/// - `Ptr` and `Ptr + Delta` must be either in bounds or one byte past the end of an allocated 
/// object 
/// - if the following invariants are not upheld, further use of the returned value will result in 
///  undefined behavior: 
///  - `Ptr` and `Ptr + Delta` must be within bounds (isize::MAX) 
///  - `Ptr + Delta` must not overflow unsafe fn
fn offset<Ptr, Delta>(Ptr, Delta) -> Ptr;

重要提示:我实际上非常不确定这些文档的含义。在core中的实际注释是

返回值的进一步使用将导致未定义的行为

那么offset(ptr, usize::MAX)安全吗?如果允许溢出,它不应该生成超出分配对象范围的指针,但我看不出为什么它们不会在这里强制执行与unchecked_add相同的规则。为了这个理论观点的目的,我将假设溢出按预期工作。

因此,我们可以将其解读为“这不是未定义的行为,但使用该值之后的操作是”。这与先前的安全合同有相当大的不同。在这里,我可以证明对函数的调用是正确的,但我可能无法证明在调用此函数之后整个程序是正确的。

let n: *const i32 = alloc::<i32>();
if random::<bool>() {
    // assignment is (probably) unsound actually...
    n = unsafe {
        // sound operation
        offset(n, usize::MAX);
    };
}
// unsound. Maybe UB, who knows?
println!({n:?});

我们可以以不同的方式组织代码,并在每次想要“使用”它时检查指针是否失效。然而,这份文档的目的在于证明这种安全合约永远不(好吧,几乎永远不)是必需的。我们可以在所谓的“项目作用域”内做所有事情(包括添加unsafe字段,或者允许对规则的少数例外,这些都可以在库中提供)。

什么是项目作用域?

这部分内容尚未编写。

无运行时依赖

特性