2 个版本
使用旧的 Rust 2015
0.1.1 | 2019年12月22日 |
---|---|
0.1.0 | 2017年11月16日 |
#898 in Rust 模式
50KB
538 行
重要:目前基于 extern type
的 CStr
不会在涉及 size_of_val
的容器中工作,例如 Box<CStr>
。在使用瘦 CStr
之前,请阅读 https://github.com/kennytm/thin_cstr/issues/1 和 https://github.com/rust-lang/rust/pull/64021。
预-RFC:将 *CStr
转换为瘦指针
摘要
通过 extern type (RFC 1861) 将 *CStr
转换为瘦指针。 CStr::from_ptr()
将成为零成本,而 CStr::to_bytes()
将需要计算长度。
动机
CStr
类型是在 Rust 1.0-alpha 期间,作为 [c_char]
类型替代品而引入的,动机之一是
…为了构建一个切片(或一个包裹切片的动态大小新类型),需要确定其长度,这对于仅接收瘦指针的消耗 FFI 函数是不必要的……
然而,当时的 Rust 只支持三种动态大小类型:str
、[T]
和 trait 对象,当它们被引用时都会变成胖指针。尝试通过 RFC 709 引入具有瘦指针的 DST,但由于 1.0 版本发布的时间限制,它被推迟并保留为低优先级问题。
因此,CStr
的实现选择将 [c_char]
封装起来,并提供了以下 FIXME
pub struct CStr {
// FIXME: this should not be represented with a DST slice but rather with
// just a raw `c_char` along with some form of marker to make
// this an unsized type. Essentially `sizeof(&CStr)` should be the
// same as `sizeof(&c_char)` but `CStr` should be an unsized type.
inner: [c_char]
}
快进到 2017 年,为了表示在 C 中广泛使用的隐藏实现细节的不可见 FFI 类型,引入了 extern type
(RFC 1861)。这些类型在公共接口中具有未指定的尺寸,并且作为瘦指针表示。该 extern type
RFC 被接受,并在 Rust 1.23 中作为不稳定功能实现。
引入 extern type
之后,我们突然有了一种通过将内部切片更改为这种外部类型来修复 FIXME 的方法。
extern {
type CStrInner;
}
#[repr(C)]
pub struct CStr {
inner: CStrInner,
}
因此,本 RFC 提出衡量对修复此问题的兴趣,并在将其合并到标准库之前整理潜在的不安全性。
指南级解释
使 *CStr
变得瘦的主要影响是长度不再与指针一起存储。一些重要变化包括
CStr
变为#[repr(C)]
,其指针类型应与 C 中的char*
兼容。CStr::from_ptr
变为免费。CStr::to_bytes
和其他获取器方法现在需要计算长度。
幸运的是,std::ffi::CStr
的文档已经包含大量关于未来变化的警告,因此我们可以假设用户不会依赖这些性能特性来编写代码。
参考级解释
这种更改的实现可以作为 thin_cstr
crate 提供,源代码可在 https://github.com/kennytm/thin_cstr 获得。
此更改仅影响无尺寸的 CStr
类型。拥有的 CString
类型将不会被修改。
缺点
假设 C 字符串的长度为 n ,
函数 | 之前 | 之后 |
---|---|---|
from_ptr |
O(n) | O(1) |
from_bytes_with_nul |
O(n) | O(n) |
from_bytes_with_nul_unchecked |
O(1) | O(1) |
as_ptr |
O(1) | O(1) |
to_bytes |
O(1) | O(n) |
to_bytes_with_nul |
O(1) | O(n) |
to_str |
O(n) | O(n) |
to_string_lossy |
O(n) | O(n) |
into_c_string |
O(1) | O(n) |
在这里,只有 CStr::from_ptr
成为一个零成本函数,所有其他方法要么仍然具有相同的成本,要么变得更快。一个特别的问题是 CStr::into_c_string
,它在 1.20 中得到稳定,但没有性能警告。
在 rustc
中,对 CStr
的大多数使用都会立即将其转换为字节切片或字符串,这不会带来性能优势或劣势。更糟糕的是,如果我们通过 CStr::from_bytes_with_nul
创建 &CStr
,长度计算的成本将加倍。
let s = CStr::from_ptr(last_error).to_bytes();
理由和替代方案
本RFC的主要理由是认为*CStr
是胖的被视为一个bug。一个明显的替代方案是“不做这个”,接受一个胖的*CStr
作为一个特性。在这种情况下,我们将修改文档并删除所有关于潜在性能变化的提及。
我们目前使用extern类型,因为这这是获得瘦DST的唯一方法。extern类型不会自动实现auto traits(如Send
、Sync
、UnwindSafe
、RefUnwindSafe
等),而一个[c_char]
切片会自动实现。目前Freeze
无法实现,因为它是在libcore中是私有的(尽管它被期望,丢失它不会影响语言语义)。此外,这意味着每当引入一个新的auto-trait(可能是由第三方引入)时,都需要手动为CStr
实现它。如果无法忍受extern类型的这种语义,我们可能需要考虑恢复自定义DST RFC(RFC 1524)以获得更多控制。
未解决的问题
如何使瘦的(无关紧要)CStr
实现Freeze
。