“
原文链接: https://github.com/pretzelhammer/rust-blog/blob/master/posts/sizedness-in-rust.md
原文标题: Sizedness in Rust
公众号: Rust碎碎念
用户定义的伪Never类型(User-Defined Pseudo Never Types)
尽管定义一个能够强制转换到任意其他类型的类型是不可能的,但是定义一个无法创建实例的类型是有可能的,例如一个没有任何variant的enum:
enum Void {}
这可以让我们从前面的两个例子中移除feature标记并且使用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标准库里使用的技术,因为String的FromStr实现里的Err类型是std::convert::Infallible, 其定义如下:
pub enum Infallible {}
PhantomData
第三常见的使用ZST的应该是PhantomData。PhantomData是一个零大小标记结构体,可以用于“标记(mark)”一个包含(containing)结构体含有特定的属性。在使用目的上类似于像Sized,Send和Sync这样的自动标记trait,但是作为一个标记结构体在使用上略有不同。对PhantdomData作一个详尽的解释并探讨它的各种用例已经超出了本文的范围,所以,让我们仅仅来看一个简单的示例。回顾一下前面出现的这个代码示例:
#![feature(negative_impls)]
// this type is Send and Sync
struct Struct;
// opt-out of Send trait
impl !Send for Struct {}
// opt-out of Sync trait
impl !Sync for Struct {}
很不幸地,我们不得不使用一个feature标记,我们能否只使用stable版本的Rust就实现相同的结果?正如我们所知,一个类型只有在它所有的成员都是Send和Sync的时候,它才是Send和Sync,因此,我们可以在结构体中添加一个!Send和!Sync成员,比如Rc<()>:
use std::rc::Rc;
// this type is not Send or Sync
struct Struct {
// adds 8 bytes to every instance
_not_send_or_sync: Rc<()>,
}
这样并不理想,因为它为Struct的每个实例都增加了大小,而且现在每次要创建一个Struct的时候,我们不得不凭空构造出一个Rc<()>。而PhantomData是一个ZST,同时解决了这两个问题:
use std::rc::Rc;
use std::marker::PhantomData;
type NotSendOrSyncPhantom = PhantomData<Rc<()>>;
// this type is not Send or Sync
struct Struct {
// adds no additional size to instances
_not_send_or_sync: NotSendOrSyncPhantom,
}
关键点(Key Takeaway)
PhantomData是一个零大小标记结构体,可以用于标记一个包含结构体为拥有特定的属性
译者注:因不确定PhantomData译为幻影数据是否合适,故此处保留原词。
总结(Conclusion)
只有确定大小类型(sized type)的实例才可以放到栈上,也就是,可以通过传值的方式传递
不确定大小类型(unsized tpe)的实例不能放到栈上而且必须通过传引用的方式传递
指向不确定大小类型(unsized tpe)的指针是双宽度的,因为除了保存指向数据的指针外,还需要额外的比特位来追踪数据的长度或者指向一个vtable
Sized是一个"自动(auto)"标记trait所有的泛型类型参数默认是被
Sized自动约束如果我们有一个泛型函数,它接收隐于指针后的类型
T为参数,例如&T,Box<T>,Rc<T>等,那么我们总是选择退出默认的Sized约束而选用T:?Sized约束利用切片和Rust的自动类型强制转换能够让我们写出灵活的API
所有的trait默认都是
?Sized对于
impl Trait for dyn Trait,要求Trait: ?Sized我们可以在每个方法上要求
Self:Sized由
Sized约束的trait不能转为trait对象Rust不支持超过2个宽度的指针,因此
- 我们不能把不确定大小类型转为trait对象
- 我们不能有多trait对象,但是我们可以通过把多个trait合并到一个trait里来解决这个问题
用户定义的不确定类型大小类型是个不成熟的特性,现在其局限性超过所能带来的益处
ZST的所有实例都相等
Rust编译器会去优化和ZST相关的交互
!可以被强制转换为其他类型无法创建一个
!的实例,我们可以利用这一点在类型级别把特定状态标记为不可能PhantomData是一个零大小标记结构体,可以用于把一个包含结构体标记为含有特定属性
讨论(Discuss)
可以在下面这些地方讨论这篇文章
- official Rust users forum[1]
- learnrust subreddit[2]
- Twitter[3]
- rust subreddit[4]
Further Reading
- Common Rust Lifetime Misconceptions[5]
- Learning Rust in 2020[6]
本文禁止转载,谢谢配合!欢迎关注我的微信公众号: Rust碎碎念
参考资料
[1]official Rust users forum: https://users.rust-lang.org/t/blog-post-sizedness-in-rust/46293?u=pretzelhammer
[2]learnrust subreddit: https://www.reddit.com/r/learnrust/comments/hx2jd0/sizedness_in_rust/
[3]Twitter: https://twitter.com/pretzelhammer/status/1286669073137491973
[4]rust subreddit: https://www.reddit.com/r/rust/comments/hxips7/sizedness_in_rust/
[5]Common Rust Lifetime Misconceptions: ./common-rust-lifetime-misconceptions.md
[6]Learning Rust in 2020: ./learning-rust-in-2020.md