Sizedness In Rust
译注:文章很长,我也是分了多次在学习&&翻译。不求一口气全部看完理解,但也需要静下心来慢慢看。示例代码部分最好自己敲一遍看看运行报错。可以理解的更好~
1. Intro
Sizedness 的概念可能是 Rust 里最重要也最不起眼的一个,实际编程中冷不防就会碰到类似于 x doesn't have size known at compile time (编译期无法确定x的大小)的错误。
全文涉及术语解释:
| phrase | shorthand for | 翻译 |
|---|---|---|
| sizedness | property of being sized or unsized |
确定大小或不确定大小的这种特性 |
sized type |
type with a known size at compile time | 编译期可确定大小的类型 |
unsized type / DST |
dynamically-sized type, i.e. size not known at compile time | 编译器无法确定大小的类型,或者称之为动态大小类型 |
?sized type |
type that may or may not be sized | 可能是确定大小,也可能是不确定大小的类型 |
| unsized coercion | coercing a sized type into an unsized type |
强制把确定大小类型转为不确定大小类型 |
ZST |
zero-sized type, i.e. instances of the type are 0 bytes in size | 零大小类型 |
width |
single unit of measurement of pointer width |
指针宽度的测量单位 |
thin pointer / single-width pointer |
pointer that is 1 width | 窄指针,1个宽度的指针 |
fat pointer / double-width pointer |
pointer that is 2 widths | 宽指针,2个宽度的指针 |
| pointer / reference | some pointer of some width, width will be clarified by context |
宽度由上下文确定的指针 |
| slice | double-width pointer to a dynamically sized view into some array |
切片,用一个宽指针指向一个动态大小类型的视图 |
2. Sizedness
-
在
Rust中,如果一个类型的大小可以在编译期间确定,那么就说这个类型是sized,能够确定大小才能在栈(stack)上分配内存。确定大小的类型可以通过值传递、通过引用传递 -
如果一个类型的大小,在编译期间无法确定,那就是
unsized,或者DST。unsized类型只能通过引用传递。
2.1. Sized example
2.1.1. 基础类型
基本类型、以及由基本类型通过 结构体(struct)、元组(tuple)、枚举(enum)、定长数组(arrays)等方式(递归得)组成的类型,在考虑了填充、对齐(padding and alignment)后,可以比较直观的把字节数加起来得到确定的结果。
use std::mem::size_of;
fn main() {
// primitives
assert_eq!(4, size_of::<f32>());
assert_eq!(8, size_of::<i64>());
// tuple
assert_eq!(16, size_of::<(i32, f32, i64)>());
// tuple with padding
assert_eq!(16, size_of::<(i32, i64)>());
// fixed-size arrays
assert_eq!(0, size_of::<[i32; 0]>());
assert_eq!(16, size_of::<[i32; 4]>());
struct Point {
x: i32,
y: i32,
}
// struct
assert_eq!(8, size_of::<Point>());
// emums
assert_eq!(8, size_of::<Option<i32>>());
}
2.1.2. 枚举
以下为尝试后能够成功编译运行的代码:
use std::mem::size_of;
fn main() {
//enums of options
assert_eq!(8, size_of::<Option<i32>>());
assert_eq!(16, size_of::<Option<i64>>());
//enums of result
assert_eq!(8, size_of::<Result<i32, i32>>());
assert_eq!(16, size_of::<Result<i32, i64>>());
enum Color {
Blue(i32),
Red(i32),
Green(i32),
}
assert_eq!(8, size_of::<Color>());
enum Color2 {
Blue(i32),
Red(i32),
Green(i64),
}
assert_eq!(16, size_of::<Color2>());
}
Rust 的枚举的size大小就有点复杂了,是一个tagged union,详细的可以看看Rustonomicon: Data Layout。
简单来说就是除了用来保存枚举数据的空间,额外还需要一个表示是哪个枚举的 tag 的空间。
但是当枚举里是一个 None 和一个 &T 引用时,这个枚举的空间就显得没必要了。可以看以下这个例子:
use std::mem::size_of;
fn main() {
enum MustOption<T> {
Value(T),
}
enum MyOption<T> {
Value(T),
None,
}
enum MyOptionInC<T> {
Value(T),
None,
}
assert_eq!(4, size_of::<i32>());
assert_eq!(8, size_of::<&i32>());
assert_eq!(8, size_of::<MustOption<&i32>>());
assert_eq!(8, size_of::<MyOption<&i32>>());
assert_eq!(16, size_of::<MyOptionInC<&i32>>());
}
其中#[repc(C)]表示按照C的内存布局来操作,也就没有了 Rust 优化这种case下不必要的 tag (为了表示 None )而节省的空间。这种标志在 FFI 场景下是很关键的的细节。其它表示方法参考Rustonomicon: Data Layout - others reprs
2.1.3. 指针大小
有点远了收回来,看一下指针的size。指针有窄指针和宽指针,分别用1个和2个 width 表示,一个 width 是8个字节,
-
对于固定大小的对象的指针,都是窄指针,一个
width大小,只需要存储地址
use std::mem::size_of;
const WIDTH: usize = size_of::<&()>();
fn main() {
assert_eq!(WIDTH, size_of::<&i32>());
assert_eq!(WIDTH, size_of::<&&i32>());
assert_eq!(WIDTH, size_of::<&mut i32>());
assert_eq!(WIDTH, size_of::<Box<i32>>());
assert_eq!(WIDTH, size_of::<&[i64; 10]>());
assert_eq!(WIDTH, size_of::<fn(i32) -> i32>());
}
-
对于不固定大小的对象的指针,都是宽指针,两个
width大小,因为需要存储地址+大小
use std::mem::size_of;
const WIDTH: usize = size_of::<&()>();
const DOUBLE_WIDTH: usize = 2 * WIDTH;
fn main() {
struct UnsizedStruct {
_unsized_slice: [i32],
}
assert_eq!(DOUBLE_WIDTH, size_of::<&str>()); // string slice
assert_eq!(DOUBLE_WIDTH, size_of::<&[i32]>()); // i32 slice
assert_eq!(DOUBLE_WIDTH, size_of::<&dyn ToString>()); // trait object
assert_eq!(DOUBLE_WIDTH, size_of::<Box<dyn ToString>>()); // trait object
assert_eq!(DOUBLE_WIDTH, size_of::<&UnsizedStruct>()) // unsized-type-ptr
}
2.2. 总结
Rust智能操作能够确定大小的对象,也就是 Sized 的对象,对于动态大小的对象,只能通过引用来操作,指针类型指向的对象可以是动态大小的,但是指针对象本身肯定也是 Sized 的。
3. Sized Trait
Sized 这个Trait是自动实现的标记Trait (both auto trait && marked trait),auto trait 也一定是 marked trait 但是反之不成立(比如之前的 Eq Trait就是一个 marked trait,需要程序员手动注明,告知编译器该类型具有自反性。
也就是说类型是否有 Sized 的Trait完全由编译器(根据其成员类型)判断并添加上默认空实现,而且也无法手动去掉这个Trait:
// this type is Sized, Send, and Sync
// auto marked trait
struct Struct;
// opt-out of Send trait
impl !Send for Struct {} // ✅
// opt-out of Sync trait
impl !Sync for Struct {} // ✅
// can't opt-out of Sized
impl !Sized for Struct {} // ❌
3.1. 总结
Sized 是无法手动消除的auto marked trait。语言特性下的规则。
4. 在泛型里的 Sized
4.1. 泛型语法糖
-
实际上使用泛型时,编译器会默认加上
Sized的泛型限制: -
当然我们也可以手动限制成
?Sized的泛型类型
// normally we do:
fn func<T>(t: T) {} // ✅
// de-sugar version:
fn func<T: Sized>(t: T) {} // ✅
// try mark `?Sized` ?
fn func<T: ?Sized>(t: T) {} // ❌ the size for values of type `T`
// cannot be known at compilation time
// we can use ?Sized to mark a ref params:
fn func<T: ?Sized>(t: &T) {} // ✅
fn func<T: ?Sized>(t: Box<T>) {} // ✅
4.2. Example
刚开始接触泛型时很可能会出现的问题:
use std::fmt::Debug;
fn debug<T: Debug>(t: T) { // T: Debug + Sized
println!("{:?}", t);
}
fn main() {
debug("my str"); // T = &str, &str: Debug + Sized ✅
}
一切正常,但是发现 debug 函数拿走了 t 的所有权,自然想到改成 &T:
use std::fmt::Debug;
fn debug<T: Debug>(t: &T) {
// T: Debug + Sized
println!("{:?}", t);
}
fn main() {
debug("my str"); // now &T = &str,
// so T = str,
// str: Debug + ?Sized ❌
}
Boom! 编译器告诉你:the trait Sized is not implemented for str
因为此时传入debug的类型 &T 对应的是 &str ,而 str 本身是 Unsized 的,就不符合泛型里默认对 T 的限制了。
强大的rustc甚至直接提示:考虑加上?Sized
--> src/main.rs:3:10
|
3 | fn debug<T: Debug>(t: &T) {
| ^ required by this bound in `debug`
help: consider relaxing the implicit `Sized` restriction
|
3 | fn debug<T: Debug + ?Sized>(t: &T) {
| ++++++++
把 debug 函数的泛型限制加上 ?Sized 后,此时传入的参数虽然是 Unsized 的,但是也在编译器的预期( ?Sized )内,是引用就ok,执行成功。
use std::fmt::Debug;
fn debug<T: Debug + ?Sized>(t: &T) {
// T: Debug + ?Sized
println!("{:?}", t);
}
fn main() {
debug("my str"); // &T = &str, T = str, str: Debug + !Sized ✅
}
4.3. 总结
-
泛型类型会自动加上
T: Sized的类型限制 -
如果我们传入的是泛型
T的引用类型,大多是时候不妨想清楚并写明此时T本身是否可以是动态大小类型,如果可以,加上T: ?Sized
5. Unsized Type
5.1. 切片 Slices
&str 和 &[T] 是最常见的切片类型。切片在Rust里被大量使用,特别是涉及到类型之间的转换时,经常使用切片类型做过度。
在函数/方法参数中经常会出现的强制类型转换:deref coercion && unsized coercion
-
去引用(
deref coercion),类型T通过解引用操作T: Deref<Target = U>强制转换为类型U,比如String.deref() -> str -
去确定大小(
unsized coercion),确定大小类型T通过T: Unsize<U>强制转换为 不确定大小类型U,比如[i32;3] -> [i32]
trait Trait {
fn method(&self) {}
}
impl Trait for str {
// we can call method on
// 1. str
// 2. String (due to String: Deref<Target = str>)
}
impl<T> Trait for [T] {
// we can call method on
// 1. any &[T]
// 2. any U where U: Deref<Target = [T]>, e.g. Vec<T>
// 3. [T; N], since [T; N]: Unsize<[T]>
}
fn str_func(_s: &str) {}
fn slice_func<T>(_s: &[T]) {}
fn main() {
// 1. str slice
let str_slice: &str = "str_slice";
let string: String = "string".to_owned();
// function calls:
str_func(str_slice);
str_func(&string); // deref coercion
// method calls:
str_slice.method();
string.method();
// 2. array slice
let slice: &[i32] = &[1];
let three_arr: [i32; 3] = [1, 2, 3];
let vec: Vec<i32> = vec![1];
// function calls:
slice_func(slice);
slice_func(&three_arr); // unsized coercion
slice_func(&vec); // deref coercion
// method calls
slice.method();
three_arr.method(); // unsized coercion
vec.method(); // deref coercion
}
这两种强制转换经常发生,但不常被注意到。
5.2. Trait Objects
Rust 的 Traits 都会自动加上 ?Sized,也没有办法手动再加上:
trait Trait: ?Sized {} // compile error ❌
// `?Trait` is not premitted in supertraits. traits are `?Sized` by default
Traits 都是 ?Sized 的,去语法糖后是:
trait Trait where Self: ?Sized {}
表示Traits是允许self对象是不定长的类型,但是如果把 self (注意不是&self) 对象作为参数传入,编译还是会报错,因为大小可能不确定:
trait Trait {
fn method(self) {} // compile error ❌
// the size for values of type `Self` cannot be known at compilation time
// help: consider further restricting `Self`
// |
// 2 | fn method(self) where Self: Sized {}
// | +++++++++++++++++
}
我们可以选择把Trait标记为 :Sized,不过这样的话会出现以下问题:
trait Trait: Sized {
fn method(self) {} // ✅
}
impl Trait for str { // compile error ❌
// we don't knonw size of `str`
}
也可以选择把method标记为:Sized类型限定使用:
trait Trait {
fn method(self) where Self: Sized, {
}
}
impl Trait for str { // ✅
}
然是这个 method 方法无法给str实现了:
impl Trait for str { // ✅
fn method(self) {} // compile error ❌
}
如果不实现 method ,即上面的代码,是可以编译通过允许的,当然也不能使用 method 方法,否则还会报错。Rust给这种情况提供了一定的灵活性:Trait 在包含一些 Sized 限定的方法时,也可以给不定长的类型实现,只要我们不去使用这些限定方法。
trait Trait {
fn method(self) where Self: Sized {}
fn method2(&self) {}
}
impl Trait for str {} // ✅
fn main() {
// we never call "method" so no errors
"str".method2(); // ✅
}
绕回来,Rust之所以把Trait设计为默认 ?Sized ,都是为了Trait对象 (Trait Object),Trait对象都是unsized的,因为给对象实现一个Trait并不要求这个对象的大小是确定的。
实际上,当我们写了一个Trait时:
trait Trait {}
可以认为编译器自动的加上了:
trait Trait: ?Sized {}
impl Trait for dyn Trait {
// compiler magic here
}
这样我们才能使用&dyn Trait作为参数,不过和之前一样的,我们不能使用 Sized 限定方法(如果有的话)
trait Trait {
fn method(self) where Self:Sized {}
fn method2(&self) {}
}
fn function(arg: &dyn Trait) {
arg.method(); // compile error ❌
arg.method2(); // ✅
}
而如果我们限制了Trait为 :Sized,那也没法为 dyn Trait 实现Trait了
trait Trait: Sized {}
impl Trait for dyn Trait {} // compile error ❌
5.2.1. 总结
-
所有 Traits 默认都是
?Sized -
想实现
Trait Object,也就是impl Trait for dyn Trait,要求Trait: ?Sized,编译器也会自动实现。反之,如果被Sized约束的Trait,就无法使用Trait Object了 -
我们可以保留Traits的默认
?Sized,但是在方法上限制Self: Sized
5.3. Trait Objects Limitations
即使一个trait是对象安全的,仍然存在 sizedness 相关的边界情况。
介绍两种限制情况,包括转换成 Trait Object 的限制,和 Trait Object 对 Trait 个数的限制。其本质原因都是由于 sizedness。
5.3.1. Cannot Cast Unsized Types to Trait Objects
不能把不确定大小类型转换为 Trait Object
比如这个例子:
fn generic<T: ToString>(t: T) {}
fn trait_object(t: &dyn ToString) {}
fn main() {
generic(String::from("String")); // ✅
generic("str"); // ✅
trait_object(&String::from("String")); // ✅ - unsized coercion
trait_object("str"); // ❌ - unsized coercion impossible
}
String 是 sized type ,所以可以通过 unsized coercion 转换为 unsized type,比如 &dyn ToString。但是 str 已经是一个 unsized type 了,所以会报错: "str" doesn't have a size known at compile-time。因为无法通过 unsized coercion 把一个本就是 unsized type 转换为新的 unsized type。
更详细的说明:
-
String是确定大小类型,所以&String指针是一个宽度(WIDTH)的指针,指向数据 -
str是不确定大小类型, 所以&str指针是2个宽度的(DOUBLE_WIDTH),包括一个执行数据地址的指针和数据的长度。 -
&dyn ToString也是两个宽度的指针,包含指向的数据(另一个指针)的指针和一个指向虚函数表(vtable)的指针
因此可得 =>
-
从
&String转换到&dyn ToString是可行的, -
从
&str转换到&dyn ToString是不可行的。 因为你需要用三个宽度来表示你需要的内容,而Rust不支持。但是从&&str转换到&dyn ToString是可行的(doge)
总结表格:
| Type | Pointer to Data | Data Length | Pointer to Vtable | Total |
|---|---|---|---|---|
&String |
✅ | ❌ | ❌ | 1 ✅ |
&str |
✅ | ✅ | ❌ | 2 ✅ |
&String as &dyn ToString |
✅ | ❌ | ✅ | 2 ✅ |
&str as &dyn ToString |
✅ | ✅ | ✅ | 3 ❌ |
5.3.2. Cannot create Multi-Trait Objects
不能使用多个Trait的 Trait对象:
trait Trait {}
trait Trait2 {}
fn func(t: &(dyn Trait + Trait2)) {}
// ^^^^^^
// ❌ only auto traits can be used as additional traits in a trait object
和上面的原因类似, Trait Object 的指针是2个宽度(DOUBLE_WIDTH),一个指向数据一个指向虚函数表vtable,如果有两个就需要指向两个虚函数表,指针需要3个宽度了。
可以通过使用 supertraits 的方式来满足一部分的需求:
trait Trait {
fn method1(&self) {}
}
trait Trait2 {
fn method2(&self) {}
}
trait Trait3: Trait + Trait2 {}
impl<T: Trait + Trait2> Trait3 for T {}
// auto blanket impl Trait3 for any type that also impls Trait & Trait2
fn func(t: &dyn Trait3) {
t.method1(); // ✅
t.method2(); // ✅
}
但是!Rust 的 supertraits 不支持 upcasting ,也就是在其它OO语言里子类对象转换为父类对象的特性,上面的 &dyn Trait3 无法被用在需要一个 &dyn Trait2 或 &dyn Trait 的地方。比如上面的例子修改一下:
trait Trait {
fn method1(&self) {}
}
trait Trait2 {
fn method2(&self) {}
}
trait Trait3: Trait + Trait2 {}
impl<T: Trait + Trait2> Trait3 for T {}
// auto blanket impl Trait3 for any type that also impls Trait & Trait2
fn take_trait(t: &dyn Trait) {}
fn take_trait2(t: &dyn Trait2) {}
fn func(t: &dyn Trait3) {
t.method1(); // ✅
t.method2(); // ✅
take_trait(t); // ❌
take_trait2(t); // ❌
}
fn main() {}
报错:
cannot cast `dyn Trait3` to `dyn Trait`, trait upcasting coercion is experimental
see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information
add `#![feature(trait_upcasting)]` to the crate attributes to enable
required when coercing `&dyn Trait3` into `&dyn Trait` rustc[E0658]
目前还是实验性的特性,规避的方式是手动加上转换:
trait Trait3: Trait + Trait2 {
fn as_trait(&self) -> &dyn Trait;
fn as_trait2(&self) -> &dyn Trait2;
}
impl<T: Trait + Trait2> Trait3 for T {
fn as_trait(&self) -> &dyn Trait {
self
}
fn as_trait2(&self) -> &dyn Trait2 {
self
}
}
// take_trait(t.as_trait()); // ✅
期待在未来Rust core team会把这个特性完善好。
Update: 2025.03.31 Rust 1.86.0 该特性已 stable。无需手动转换上述 take_trait(t) 可直接使用不会报错。
5.3.3. 总结
Rust 指针不支持超过两个宽度的大小。这意味着
-
无法把
unsized types转换为Trait Object -
无法创建多 Trait 的
Trait Object,可以通过supertraits的方式解决特定需求
5.4. User-Defined Unsized Types
用户自定义的类型可以是不确定大小的类型,我们可以在 struct 的最后一个字段写一个不确定大小的类型。结构里仅能有一个不确定大小的类型,且必须写在最后。
比如我们可以定义一个 struct Unsized:
struct Unsized {
unsized_field: [i32],
}
但是却发现好像写不出来初始化一个 Unsized 对象的代码?因为作为Rust的参数对象必须是 Sized 。一个妥协方式是使用前面提过的 unsized coercion ,把确定大小的 [i32;3] 通过去确定大小强制转换转换为 [i32]:
struct MaybeUnSized<T: ?Sized> {
field: T,
}
fn main() {
let s: &MaybeUnSized<[i32]> = &MaybeUnSized { field: [1, 2, 3] };
}
标准库里的 std::ffi::OsStr 和 std::path::Path 就是 Unsized Type:
pub struct OsStr {
inner: Slice,
}
pub struct Path {
inner: OsStr,
}
impl Path {
pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path {
unsafe { &*(s.as_ref() as *const OsStr as *const Path) }
}
}
不确定大小类型确实目前还是不成熟的特性。使用起来的限制比带来的好处要多。
6. Zero-Sized Types
零大小类型( ZST for short),或许听起来奇怪,但是却被到处使用。
6.1. Unit Type
最常见的 ZST 应该就是单元类型( Unit Type ): () 。
所有的空代码块都等于 ()
非空代码块如果最后的表达式有 ; 分号丢弃结果 ,最终也等于 ()
fn main() {
let a: () = {};
let b: () = {
5; // here 5 means nothing. b == ()
};
}
所有无返回值的函数,实际上也是返回 ()
// with sugar
fn function() {}
// desugared
fn function() -> () {}
既然 () 是类型大小为0, 一些简单的Trait也已经实现了
impl const Default for () {
fn default() -> () {
()
}
}
impl PartialEq for () {
fn eq(&self, _other: &()) -> bool {
true
}
fn ne(&self, _other: &()) -> bool {
false
}
}
impl Ord for () {
fn cmp(&self, _other: &()) -> Ordering {
Ordering::Equal
}
}
因为编译器知道 () 是 ZST ,所以可以针对此做不少优化:比如 Vec<()> 并不会去分配堆内存、push 和 pop 一个 () 到 Vec 只修改它的 len 字段,不会触发容量变化:
fn main() {
// zero capacity is all the capacity we need to "store" infinitely many ()
let mut vec: Vec<()> = Vec::with_capacity(0);
// causes no heap allocations or vec capacity changes
vec.push(()); // len++
vec.push(()); // len++
vec.push(()); // len++
vec.pop(); // len--
assert_eq!(2, vec.len());
assert_eq!(usize::MAX, vec.capacity());
// pub fn capacity(&self) -> usize {
// if mem::size_of::<T>() == 0 { usize::MAX } else { self.cap }
// }
}
实际的应用场景:标准库里的容器,HashSet 是通过 HashMap 实现的: hashbrown/set.rs
pub struct HashSet<T> {
map: HashMap<T, ()>,
}
6.2. User-Defined Unit Structs
用户定义的单元结构体:即不包含任何成员的结构体:
struct Struct;
使用单元结构体的优点:
-
可以给自定义的单元结构体实现任意Trait,因为Rust trait的
orphan rules,我们没法给()实现任何Trait。 - 可以给自定义的单元结构体取一个有意义的名字,增强代码可读性。
- 和其它结构体一样,默认是非拷贝的(non-Copy),在程序中或许有用。
6.3. Never Type
另一个重要的 ZST 就是 Never Type: !,它的重要特性:
- 它可以被强制转换到任意其它类型。
-
无法实例化一个
!
这也是和 () 最大的区别:
!basically means “this can never happen” while()means “there is no value here”.
unimplemented!() todo!() panic!() unreachable!() 等宏都有这个特点,在快速构建原型、标记问题上很有用。
另外 break continue return 等关键字也是 ! 类型。
工程上,可以用 ! 类型系统来标记一些不可能的情况,比如如下的函数签名分别表示:这个函数只要返回就一定是返回成功,和这个函数只要返回就一定是返回失败。
fn function() -> Result<Success, !>;
fn function() -> Result<!, Error>;
标准库在实践中也用到:把 &str 转换为 String 一定会成功:
use std::str::FromStr;
impl FromStr for String {
type Err = !; // actually here uses `Infallible` , will be illustrated later.
fn from_str(s: &str) -> Result<String, Self::Err> {
Ok(String::from(s))
}
}
对于如果返回一定是返回失败的场景:考虑服务器端 loop{} 等待,仅在出错时返回:
fn run_server() -> Result<!, ConnectionError> {
loop {
let (request, response) = get_request()?;
let result = request.process();
response.send(result);
}
}
注意需要写上 #![feature(never_type)] + 使用 nightly rust ,因为目前还是实验性标准。
6.4. User-Defined Pseudo Never Types
写上 feature 标签就需要 nightly 的 Rust,如果一定想在 stable 下实现类似的效果也不是没有办法。
虽然我们没法定义一个类型,使其可以强制转换到任何其它类型。
但是我们确实可以定义一个类型,让它没法实例化出对象。
比如没有任何选择的枚举:
enum Void {}
使用这个 Void 代替 !,就可以在 stable Rust 下,实现上面和 ! 类似的效果:
enum Void {}
// example 1
impl FromStr for String {
type Err = Void;
fn from_str(s: &str) -> Result<String, Self::Err> {
Ok(String::from(s))
}
}
// example 2
fn run_server() -> Result<Void, ConnectionError> {
loop {
let (request, response) = get_request()?;
let result = request.process();
response.send(result);
}
}
rust标准库就是这么做的:
pub enum Infallible {}
上面 FromStr 的例子里,实际标准库的代码是 type Err = Infallible;
6.5. PhantomData
第三个重要的 ZST 就是 PhantomData 幽灵数据 ,0大小标记结构体。用来标记一个结构体拥有一些特定的属性。基本上是为了给编译器看的。
译注:原文在这里用一个去除结构体 Send 和 Sync 的Trait的例子来说明其作用。以后再单独总结下幽灵数据 PhantomData 的用法。
更多的内容可以参考 nomicon/PhantomData
7. Conclusion
啊懒得翻译了贴原文,以上所有内容的总结的总结:
- only instances of sized types can be placed on the stack, i.e. can be passed around by value
- instances of unsized types can’t be placed on the stack and must be passed around by reference
- pointers to unsized types are double-width because aside from pointing to data they need to do an extra bit of bookkeeping to also keep track of the data’s length or point to a vtable
-
Sizedis an “auto” marker trait -
all generic type parameters are auto-bound with
Sizedby default -
if we have a generic function which takes an argument of some
Tbehind a pointer, e.g.&T,Box<T>,Rc<T>, et cetera, then we almost always want to opt-out of the defaultSizedbound withT: ?Sized - leveraging slices and Rust’s auto type coercions allows us to write flexible APIs
-
all traits are
?Sizedby default -
Trait: ?Sizedis required forimpl Trait for dyn Trait -
we can require
Self: Sizedon a per-method basis -
traits bound by
Sizedcan’t be made into trait objects - Rust doesn’t support pointers wider than 2 widths so: #1. we can’t cast unsized types to trait objects #2. we can’t have multi-trait objects, but we can work around this by coalescing multiple traits into a single trait
- user-defined unsized types are a half-baked feature right now and their limitations outweigh any benefits
- all instances of a ZST are equal to each other
- Rust compiler knows to optimize away interactions with ZSTs
-
!can be coerced into any other type -
it’s not possible to create instances of
!which we can use to mark certain states as impossible at a type level -
PhantomDatais a zero-sized marker struct which can be used to “mark” a containing struct as having certain properties