开始

每门语言都有它晦涩难以理解的地方,学习rust越深越会发现这些灰暗的地方,慢慢确实有一种劝退的感觉,也怪不得别人把rust定义为一门学习曲线陡峭的语言了;但是既然都投入了时间和精力,就打破砂锅问到底,尽自己能力去理解这门语言(想想当初刚学习js的时候,也是各种无法理解,但是习惯就好)。

Object Safe

为什么会接触到Object Safe这个概念,首先得从万恶之源Trait说起,初学Trait的时候很容易把它当成Java接口类似的东西来理解,很轻松就翻过去了,但是事实是too young too simple。
在rust里面调用方法存在静态分配和动态分配这两种,这两个概念跟大多数语言是相同的:静态分配在编译阶段就确定了调用的方法,动态分配是在运行阶段才确定的。在rust中实现动态分配就必须得依赖Trait Object,而Trait Object是个什么东西来的尼,是怎么创建的尼?
首先Trait Object其实是一种胖指针,类似以下的结构:

pub struct TraitObject {
    pub data: *mut (), //*mut ()类似c的void *
    pub vtable: *mut ()
}

包含一个指向data的指针和指向虚表的指针,rust跟c++内存布局不一样,c++把虚表放到对象上,而rust则把虚表放到Trait Object上;那哪些是Trait Object尼,假设Foo是一个Trait,那么例如:&dyn Foo, &mut dyn Foo, Box<dyn Foo>,Rc<dyn Foo>, const dyn Foo,mut dyn Foo等等;这里可以看到有Box和Rc类型,其实类似Box这种能够解引用返回dyn Foo的都可以是Trait Object。怎么创建Trait Object?使用as操作符,可以把具体类型的引用转换成类似&dyn Foo的Trait Object。
但是能不能创建Trait Object涉及到一个Object Safe的概念,只有是Object Safe的Trait才能创建Trait Object,否则编译器都会报错。
那什么情况下Trait是Object Safe?

  • 所有方法都是Object Safe的
  • Trait本身没有Self: Sized的约束

那么问题又来了,什么方法是Object Safe的尼?根据RFC:

  1. 函数有Self: Sized约束时,或者或者符合第2点要求
  2. (1)函数不能有泛型参数
    (2)返回值不能使用Self类型

为什么有这样一个奇怪的定义,谷歌了以下,也没有找到满意的答案,RFC也没有很深入的说明。
不过先看一下Sized这个Trait,在rust里面大多类型都是符合Sized的约束的,因为大多类型都能在编译期能确定大小,当然也是有例外的,例如不定长数组,对于那些在编译期不能确定大小的类型,在rust中这些类型只能藏在引用或者指针的背后了,因为编译器也不知道在栈上分配多少空间给它们;介绍了Sized,必须介绍另外一个?Sized,意味着类型可以是任意类型(编译期能确定大小的和编译期不能确定大小的类型),其中Self类型有点特别,默认是满足?Sized,毕竟谁也不知道Self究竟是哪个类型嘛。
那么再回头想一想,为什么当Trait满足Self: Sized约束时就不能创建Trait Object,首先Trait Object目的是为了动态分配,动态分配就意味着调用该方法的当前类型是无法预知的,可能是Sized,也可能是?Sized,所以当Trait受到Sized约束时,明显跟动态分配的目的冲突了,干脆就直接就不让创建Triat Object规避这样的问题。
另外为什么方法不能有泛型参数,因为Rust的泛型是通过代码自动展开实现的,没有运行时的消耗,但是Trait中的每个方法对应在虚表只能生成一个项,是不能根据泛型生成多个方法放到虚表上。
为什么返回值不能是Self类型,因为Trait Object已经把实际的具体类型忘记了,所以返回Self类型编译器也不知道该分配多少内存去接收这个值。

结束

并没有得出什么特别的感悟,纯粹只是笔记。


tain335
576 声望196 粉丝

Keep it simple.