标签
在Rust中存在一种Trait,它内部没有任何方法,关联常量或者关联类型。它仅用作一种标签,标识某个类型具有某种性质,这种Trait就叫做标签。
Rust 一共提供了几个重要的标签:
Sized,用来标识编译期可确定大小的类型。Unsize,目前该 trait 为实验特性,用于标识动态大小类型(DST)。Copy,用来标识可以按位复制其值的类型。Send,用来标识可以跨线程安全通信的类型。Sync,用来标识可以在线程间安全共享引用的类型。Unpin,类型没有“自引用”结构,可被安全地移动
除此之外,Rust 标准库还在增加新的标签以满足变化的需求。此处很多标签都涉及到后面的复杂机制,本博客只深入讲解Sized和Copy。
Sized
编译器用Sized识别可以在编译期确定大小的类型。
Sized 源代码如下:
#![lang = "sized"]
pub trait Sized {
// 代码为空,无具体实现方法
}Sized 是一个空 Trait,其作用仅作为标签 Trait 供编译器识别使用。真正赋予其"标签"功能的是 #![lang = "sized"] 这一属性。
lang 属性表明 Sized 是 Rust 语言内部使用的特殊项,称为语言项 Lang Item,通过这种声明,编译器才能知道 Sized trait 是语言内置的特殊标识,从而理解它的作用和使用方式。
简单说,这个 lang 属性就像给 Sized 盖了个官方印章,告诉编译器:"这是我(Rust 语言)自己用的特殊 trait,编译器要有识别它的能力,并且按照我规定的逻辑去处理带有这个 trait 的类型"。
这样编译器在遇到 Sized 时,就知道它是用来标记"编译期可确定大小的类型"的,而不是一个普通的自定义 Trait。
类似的机制也体现在加号操作中:当执行 a + b 这样的整数相加时,编译器会自动关联到 Add::add(a, b) 实现,这正是因为加号操作对应着 #![lang="add"] 语言项,使得编译器能够正确解析加法运算的逻辑。
在Rust里,多数类型默认都具备Sized特性。这意味着在定义泛型结构体时,即便不特意写明Sized的限制,编译器也会默认加上。
示例:
struct Foo<T>(T);
struct Bar<T: ?Sized>(T);这里的Foo虽然没显式标注,但实际上等同于Foo<T: Sized>。如果想让结构体支持动态大小的类型,就得用<T: ?Sized>来限定,比如Bar的写法。
?Sized是Sized 的一种特殊语法形式。要理解它们的关系,得先分清Sized和Unsize:
Sized用于标记那些在编译时就能确定大小的类型Unsize相反,专门标记动态大小类型
?Sized的作用范围更宽,它既包含Sized类型,也涵盖Unsize类型,可以理解为:不保证这个类型大小,既可能是动态大小,也可能是非动态大小。
不过动态大小类型的使用有严格限制,必须遵守这三条规则:
- 只能通过胖指针(比如
&[T]或&Trait)来操作Unsize类型 - 变量、函数参数和枚举成员都不能直接使用动态大小类型
- 结构体中,只有最后一个字段允许使用动态大小类型,其他字段则不行
#[repr(C)]
struct WithTail {
a: u32,
b: [u8],
}
fn main() {
// let x = WithTail { a: 1, b: [1, 2, 3] };
let _p: &WithTail;
let _p_mut: &mut WithTail;
}以上代码中,WithTail结构体的最后一个字段是 [u8],这是一个动态大小类型,那么WithTail这个结构体也就是一个动态大小类型。
你无法通过let直接接收它,从而把它放在栈区,只能拿到它的借用。对于这种结构体,需要通过非常原始的内存操作,类似于C语言中的mallloc和realloc来操作。你需要在堆区手动开一块内存,然后放入这个结构体,并且内存的大小至少应该是结构除去最后一个字段之前的所有字段的总大小。

超出这些部分的内存,全部变为最后一个元素的动态数组的大小。你可以对这个内存扩容缩容,最终影响到的都是结构体最后一个字段的大小。
Copy
Copy 用来标记可以按位复制其值的类型,按位复制等价于 C 语言中的 memcpy。
Copy源码:
#![lang = "copy"]
pub trait Copy: Clone {
// 代码为空,无具体实现方法
}与Sized相同是一个空Trait,它通过语言项#![lang = "copy"]来完成功能。不一样的是,它继承了Clone。
Clone源码:
pub trait Clone: Sized {
fn clone(&self) -> Self;
fn clone_from(&mut self, source: &Self) {
*self = source.clone()
}
}Clone 继承自 Sized,意味着要实现 Clone 的对象必须是 Sized 类型。
Clone内含两个方法,clone_from 方法有默认的实现,并且其默认实现是调用 clone 方法,所以对于要实现 Clone 的对象,只需要实现 clone 方法就可以了。
如果想让一个类型实现 Copy,就必须同时实现 Clone。
struct MyStruct;
impl Copy for MyStruct {}
impl Clone for MyStruct {
fn clone(&self) -> MyStruct {
*self
}
}在实现Copy时,Clone一律返回*self。如果clone涉及到深拷贝等问题,那么就不能实现Copy进行按位拷贝。
由于对于所有类型,如果要实现copy时,clone的实现是相同的,因此Rust提供了更简单的属性来快速实现:
#[derive(Copy, Clone)]
struct MyStruct;此处的 #[derive(Copy, Clone)]属性,就会给MyStruct自动实现Clone和Copy。
Rust 为很多基本数据类型实现了 Copy,比如常用的数字类型、字符(Char)、布尔类型、单元值、不可变引用等。
但比如说String 这种集合类型类型往往没有实现 Copy,它们涉及到堆区的分配,需要进行深拷贝。
Copy和Clone涉及到的拷贝相关性质,会在所有权章节深入讲解。
