写点什么

Rust 类型

作者:Shine
  • 2022 年 3 月 18 日
  • 本文字数:2156 字

    阅读完需:约 7 分钟

类型在内存中的表示

对齐

  1. 如果没有内存对齐,可能导致两次访存的低性能和并发问题。比如 i64 分两次访问读或写;两次写指令之间,另一个线程可能执行对它的读操作

  2. 自然对齐-对齐大小等于类型的字节数。内建原始类型:u8 是单字节对齐,u6 双字节对齐,u32 是 4 字节对齐。。。

  3. 复杂类型的对齐大小等于它成员中最大的对齐大小。

布局

  1. repr(C) 与 C/C++的布局一致。用在以下情况

  2. 与 C/C++的代码交互;

  3. 非安全代码下原始指针的操作;

  4. 相同字段不同类型的转换(cast);

  5. 下面这个 struct 占用多少字节?

#[repr(C)]struct Foo {    tiny: bool,    normal: u32,    small: u8,    long: u64,    short: u16,}
复制代码

1 + 3 + 4 + 1 + 7 + 8 + 2 + 6 = 26 bytes. 最后 6byte padding 是因为整个 struct 是 8 字节对齐的

  1. 如果上面代码使用 rust 的默认布局,占用多少字节?

将各个成员按照他们的字节大小逆序排序,没有 padding. 8 + 4 + 2 + 1 + 1

  1. #[repr(align(n))] 紧凑布局--按代码原始顺序,没有 padding. 在内存吃紧或在慢速带宽传输的情况。

1+4+1+8+2=16 bytes

  1. #[repr(align(n))] 自行指定较大的对齐字节数。避免 False sharing 问题。即不同的值连续存放到一块内存时,可以放到不同的 CPU 缓存行

  2. repr(transparent) 用在某种类型只有一个字段,内部字段的布局与本身一样

复杂类型的布局

  1. Tuple - 跟 struct 一样

  2. Array -- 元素之间没有 padding 的连续序列

  3. Union -- 各个成员的独立布局。选择成员中最大对齐数

  4. Enum -- 跟 Union 相同,但是有一个隐藏的共享字段,其值用来区分各个成员的。

动态大小类型和宽指针

  1. 大部分类型在编译期间确定大小,即是 Sized

  2. trait 对象和 slices, 如 dyn Iterator or [u8] 没有确定的大小。

  3. 用宽指针(或胖指针)代替未确定大小的对象。宽指针是 2 个字大小的指针。

  4. 对 trait object, 宽指针= 数据指针+虚拟方法表指针

  5. 对 slice; 宽指针=指针+长度

  6. Box and Arc 支持宽指针, 如 Box

Trait 和 Trait 约束

编译和指派

  1. 如果定义一种使用泛型参数 T 的类型或函数, 编译器将会对每一个具体类型 T,实现改类型或方法代码的拷贝。类型中没有用到泛型的方法不会拷贝。缺点:

  2. 增加编译时间

  3. 增加了目标代码的大小

  4. 由于多份相同的指令,CPU 指令缓存失效

  5. 静态指派-- 在调用泛型化 trait 的方法中, 对于具体的 T,在编译期间就 知道该方法的地址。

  6. 有些不依赖泛型参数的代码块,可以提取出来作为内部方法。这样编译器不用对每种类型都拷贝一份相同的指令。

  7. 动态指派---调用时并不知道实现 trait 的具体类型是什么。

  8. 宽指针 &dyn trait 的另外一个地址指向 vtable。 vtable 保存具体类型的虚方法地址列表(包括内存布局和对齐),参见std::task::RawWakerVTable

  9. 实现 trait 类型的 trait 对象能够动态指派. 如下图,struct Cat 实现了 trait Mammal

  1. 有些 trait 不能用于宽指针,不能转变为 trait object,即不是 object safety 的对象。 如 dyn Clone

  2. trait 方法中有泛型参数

  3. trait 方法中用到了 Self

  4. 类型 trait 有静态方法(第一个参数不能解引用为 Self 类型)。因为不知道到底调用哪个实例的方法

  5. 有 Self:Sized 约束的 trait。某个方法有 Self:Sized 约束,只是该方法不能用于动态分派。

  6. 节省编译时间,但是运行时需要在 vtable 中查找方法的地址比较费时。

  7. 一般说来, libary 中使用静态指派, binary 中用动态指派??

相干性(Coherence)和孤儿原则

  1. 相干性--对于某个给定的类型和方法,在该类型上只有唯一一种该方法的实现。比如你不能为 bool 类型定义 Display Trait,因为标准库已经实现。

  2. 孤儿原则: 在为某种类型实现某个 trait 时,至少 trait 或类型其中之一是本地 crate 定义的。即,你能够为第三方 typtes 实现本地的 trait,或者在第三方 crate 中为你定义的类型实现第三方定义的 trait。

  3. 孤儿原则的例外:

  4. 毯子(blanket)实现--可以用自定义的 trait 实现一大批的类型:impl MyTrait for T where T:。 切记:为已存在的 trait 定义覆盖性实现是一个 breaking change, 因为依赖它的 crate 可能已经实现,从而产生冲突

  5. 基础类型---可以为一些基础类型实现外部 trait. 基础类型是用 #[fundamental]标注过的类型,目前包括 &,&mut 和 Box. 比如IntoIterator for &MyType,IntoIterator 和 &都来自于标准库。

  6. 覆盖实现--- 要使impl<P1..=Pn> ForeignTraitfor<T1..=Tn> T0被允许, 满足下面条件之一:

  7. 至少存在一个 Ti 是本地类型 而且 第一个 Ti(包括)之前的 T 都不属于泛型参数 P1..=Pn;

  8. 或者泛型参数 Ps 可以出现在 T0...Ti 中,但是 Ps 被一些中间类型覆盖的话,如 Vec<T>

下面实现是合法的:

impl<T> From<T> for MyTypeimpl<T> From<T> for MyType<T>impl<T> From<MyType> for Vec<T>impl<T> ForeignTrait<MyType, T> for Vec<T>
复制代码

下面是不合法的实现:

impl<T> ForeignTrait for Timpl<T> From<T> for Timpl<T> From<Vec<T>> for Timpl<T> From<MyType<T>> for Timpl<T> From<T> for Vec<T>impl<T> ForeignTrait<T, MyType> for Vec<T>
复制代码

标记(Marker)Trait

  1. 没有任何方法的 trait 是标记 Trait。 如 Send, Sync, Copy, Sized, Unpin. 除了 Copy 外,这些 trait 都是自动 Auto-trait,也就是说只要某类型的字段都实现某种标记 trait,编译器自动为这种类型实现这个标记 trait

  2. 没有任何数据和方法的类型称作标记类型。标记类型可以用来标记状态

Existential 类型

  1. 返回实现某个 trait 的类型, 这返回的类型就是existential类型。 如 IntoIterator 的 into_iter(self)方法。参见文章Rust Has Got Existential Types

发布于: 刚刚阅读数: 3
用户头像

Shine

关注

万物负阴而抱阳,冲气以为和 2017.11.23 加入

普通程序员

评论

发布
暂无评论
Rust类型_读书笔记_Shine_InfoQ写作平台