#c-str #cstring #ffi

nightly thin_cstr

这是一个实验性库,它提供了一个真正的瘦 std::ffi::CStr

2 个版本

使用旧的 Rust 2015

0.1.1 2019年12月22日
0.1.0 2017年11月16日

#898 in Rust 模式

MIT/Apache

50KB
538

重要:目前基于 extern typeCStr 不会在涉及 size_of_val 的容器中工作,例如 Box<CStr>。在使用瘦 CStr 之前,请阅读 https://github.com/kennytm/thin_cstr/issues/1https://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(如SendSyncUnwindSafeRefUnwindSafe等),而一个[c_char]切片会自动实现。目前Freeze无法实现,因为它是在libcore中是私有的(尽管它被期望,丢失它不会影响语言语义)。此外,这意味着每当引入一个新的auto-trait(可能是由第三方引入)时,都需要手动为CStr实现它。如果无法忍受extern类型的这种语义,我们可能需要考虑恢复自定义DST RFC(RFC 1524)以获得更多控制。

未解决的问题

如何使瘦的CStr实现Freeze(无关紧要)

无运行时依赖