基础类型(Primitive Type)
Rust语言提供了若干基础类型。我们马上会逐一介绍。
基础类型与Rust语言紧密绑定在一起,在很多时候会展现出用户自定义类型无法具有的行为。
布尔类型:bool
布尔类型的名称为bool,包含两个值:真和假。Rust通过两个关键词true和false,分别表示这两个值。请看下面的代码示例:
#[allow(unused)]fn main{let b: bool = true;// 在上面这行语句中,声明了一个bool类型的变量b,并将true值赋给变量b// 对于赋值符号右侧的true,可以从三个方面来理解:// 1. true是Rust语言的一个关键词// 2. true是一个字面量(literal),表示bool类型的真值// 3. true是一个字面量表达式(literal expression)// 对这个表达式求值,得到bool类型的真值let c = false;// 在上面的语句中,没有声明变量c的类型// 但是,这并不会导致编译错误// 因为,Rust编译器会通过类型推断(type inference)确定变量c的类型// 所谓类型推断,大概就是从变量声明和使用的上下文中推断出变量的类型}// 对 #[allow(unused)] 的说明:// 1. 这是一个关于函数main的属性声明(attribute declaration)// 表示允许在main的代码中出现未使用的变量// 2. 如果删除这个属性声明,那么,如果main函数中存在未使用的变量,// 则Rust编译器会报出警告(warning)// 3. 在上面的程序中,如果删除了这个属性声明,// 则编译器会报出警告:unused variable: `b`
布尔类型看起来像是一个枚举(enum)类型,因为它看起来确实只有两个变体(variant)。但是,Rust并没有把布尔类型实现为枚举类型。
内存排布(Memory Layout)
一个bool类型的值,在内存中的排布方式如下:
- 占用一个字节
- 也称
bool类型的size等于1 - 也称
bool类型是一种定长类型(sized type);因为bool类型的所有值都具有相同的size。
- 也称
- 占用的这个字节必须是内存空间中具有编号/地址的一个字节
- 也即:这个被占用的字节,不能分散在内存空间中具有地址的两个字节中
bool类型的这种排布要求,被描述为allignment等于1
true值表示为二进制串0x01;false值表示为二进制串0x00:::info 在后面,我们会看到allignment等于其他值的情况。
Allignment的取值只能是一个正整数,且必须是2的整数次方,也即:1、2、4、8等值。
举例而言,如果类型T的allignment为2,那么,该类型的值只能排布在内存中满足如下条件的位置:该位置的首字节的地址能被2整除
对值的排布位置进行这种限制的目的是什么呢?这个问题大概与计算机硬件系统的特点以及程序的运行效率有关系。时机成熟后,我们再来看这个问题。 :::
自动实现的特性(traits)
Rust为所有基础类型自动实现了5种特性(trait):Sized、Clone、Copy、Send、以及Sync。下面,我们简单介绍前三种特性。后面的两种特性,留到后面合适的地方再介绍。
Sized
如果类型T实现了Sized特性,则表明T是一种定长的数据类型,即:类型T的所有值在内存中占据相同的字节数,且这个字节数在编译时刻就能确定。编译器能够自动根据一个类型的定义,确定它是否是定长类型;如果是,则自动为该类型实现Sized特性。
由bool类型的内存排布可知,bool类型是一种定长类型。因此,编译器自动为它实现了Sized特性。Sized是一种标记型的特性(maker trait)。因为除了对类型做出标记外,Sized并不会在类型上添加什么其他成分。
Clone
如果类型T实现了Clone特性,则对T的任何一个值v,我们可以调用特定的方法生成值v的一个克隆c,即:v和c在语义上是两个相同的值;但是,它们在内存中的排布是完全独立的(改变一个值,并不会导致另外一个值发生变化)。
下面给出了定义Clone特性的源代码:
// 可以将Rust中的trait大概对应到Java中的interface:// 1. 一个trait中声明了若干方法:每一个方法或者仅是一个方法签名,或者带有默认实现// 2. 一个类型如果想要实现一个trait,则必须至少实现trait中的所有仅具有签名的方法// 3. 如果一个类型实现了特定的trait,// 则在这个类型的任何一个值上都可以使用trait中声明的方法// 下面这行代码表示:当前定义的特性的名称为Clone;且特性Sized是Clone的前置特性(supertrait)// 这里,前置特性的含义是:一个类型,若要实现特性A,则必须首先实现A的所有前置特性pub trait Clone: Sized {fn clone(&self) -> Self;// 以上语句声明了一个方法;该方法仅具有签名// 一个类型如果要实现Clone特性,则必须为这个方法提供实现//// 这个方法签名中,具有两个稍显奇怪的语法:Self和&self。其中:// 1. Self表示实现Clone特性的一个类型// 例如:如果我们要为bool类型实现Clone特性,则Self表示bool类型。// 因此,我们在一个bool类型的值调用clone方法,会返回一个bool类型的值// 2. &self是一种语法糖,其无糖形式是 self: &Self// 因此,&self表示clone方法具有一个类型为&Self的参数// 即:self一种引用类型的值;这个值是一个地址/指针,指向一个Self类型的值// 下面的这个方法具有默认实现// 一个类型在实现Clone特性时,如果认为这个默认实现不够好,则可以提供一个更好的实现;// 否则,无需再次实现该方法//// 这个方法实现的功能是:生成source值的一个拷贝,并把它赋值给self。// &mut self是一种语法糖,其无糖形式是 self: &mut Self// 即:self一种引用类型的值;这个值是一个地址/指针,指向一个Self类型的值// 同时,mut表示:我们可以通过self指针,修改其指向的那个Self类型的值fn clone_from(&mut self, source: &Self) {*self = source.clone()// 在上面这行代码中,方法调用source.clone()返回了一个Self类型的值// 然后,这个值被存放在self指针指向的内存位置。// *self也称为"去引用",即:通过一个指针访问到它指向的那个内存位置}}
下面给出了Rust为类型bool实现Clone特性的源代码:
impl Clone for bool {#[inline]fn clone(&self) -> Self {*self}}// 在上面的代码中,方法clone的属性声明 #[inline] 告诉编译器:// 如果合适,请把clone方法内连(inline)到调用它的地方// 即:把方法调用替换为函数体中的等价代码,// 从而在运行时消除对clone方法的调用,提高程序的运行效率
既然bool类型已经实现了Clone特性,那么,我们就可以在任何一个bool类型的值上调用clone方法了。下面的代码示例给出调用clone方法的两种形式:
#[allow(unused)]fn main() {let b: bool = true;let c1 = bool::clone(&b);// 上面的这行语句展示了在bool类型的值上调用clone方法的第一种形式:// 1. 首先,通过表达式bool::clone访问到bool实现的Clone特性中的clone方法// 2. 然后,按照clone方法的参数类型,向它传入实际参数&b// 3. 最后,clone(&b)方法调用返回b中值的一个拷贝,并将该拷贝赋给变量c1let c2 = b.clone();// 上面的这行语句展示了在bool类型的值上调用clone方法的第二种形式// 即:直接在b上通过点操作符访问到clone方法并调用// Rust中的点操作符很聪明:它会根据clone参数的类型,取得变量b的引用,并传入clone方法中//// 一个类型上附着的任何一个方法,// 如果它的第一个参数的名称是self,就可以使用点操作符的方式调用这个方法// 对于这两种调用方式,你偏向于哪一种呢?let c3 = b;// 在impl Clone for bool代码块中,我们请求编译器对bool类型值上的clone调用进行内连// 编译器在一般情况下,确实会进行内连。// 那么,对方法调用bool::clone(&b)进行内连的结果是什么呢?我们来看一下:// 1. 首先,用&b替换clone实现代码中的self,得到等价代码*&b// 2. 然后,对*&b进行进一步优化,得到等价代码b// 原来,我们对clone方法的调用只是调了一个寂寞!}
Copy
如果类型T实现了Copy特性,则对T的任何一个值v,只要把v值的二进制表示复制一遍,就可以得到v值的一个拷贝。与Sized特性类似,Copy也是一种标记型特性。下面给出了Copy特性的源代码:
pub trait Copy: Clone { }// 可以看到:// 1. Clone是Copy特性的一个supertrait,// 即:一个类型,如果要实现Copy特性,必须要先实现Clone特性// 2. Copy特性中不包含任何成分,因此它是一种标记型特性
下面给出了Rust为bool类型实现Copy特性的源代码:
impl Copy for bool {}// 实在无话可说
需要注意一点:即使一个类型实现了Clone特性,不代表它一定可以实现Copy特性。如果你为一个无法实现Copy特性的类型强行实现该特性,编译器会报出一个错误(Error)。
:::info
时机成熟后,我们再来介绍满足什么条件的类型才能实现Copy特性。
:::
一个类型是否实现了Copy特性,对于该类型变量的赋值行为具有重大影响。
- 给定类型为
T的两个变量t和s、以及一个赋值语句t = s;- 行为一:假设类型
T实现了Copy特性,那么,这条语句在执行后s位置中的二进制串会被复制到t位置中s变量没有发生任何变化,我们可以继续从s位置读取其中的值。
- 行为二:假设类型
T没有实现Copy特性,那么,这条语句在执行后s位置中的二进制串会被复制到t位置中- 然后,
s变量处于未初始化(uninitialized)的状态;在没有为s重新赋值的情况下,我们无法再读取s的值。
- 行为一:假设类型
第二种赋值行为称为带所有权转移的赋值行为。所有权是什么?这个问题暂且搁置。
:::info
为了叙述方便,在后文中,我们使用“Copy类型”表示“实现了Copy特性的类型”,使用“非Copy类型”表示“没有实现Copy特性的类型”。
:::
Copy类型和非Copy类型在赋值行为上的这种差异性,会贯穿Rust程序的方方面面- 如果可以,请把这种差异性深深地镌刻在你的Rust世界观中
下面,我们再介绍Rust为bool类型自动实现的其他若干特性。
Default
下面的代码块给出了Default特性的定义。如果类型T实现了Default特性,则我们可以通过在T上调用方法default,获取T的一个缺省值。
pub trait Default {fn default() -> Self;}
下面的代码块给出了Rust为类型bool实现Default特性的源代码:
impl const Default for bool { // const的含义是什么?这个问题暂且搁置#[inline]fn default() -> Self {false}}
下面的代码块给出了在类型bool上调用default方法的示例:
#[allow(unused)]fn main() {let b = bool::default();// default方法的第一个参数不是self;因此,无法使用点操作符的方式调用该方法}
PartialEq
PartialEq特性的定义如下面的代码块所示。PartialEq是一种范型特性(generic trait)。
我们可以从数学中函数的角度来看待范型特性。一个范型特性是一个函数:这个函数具有1到多个以类型为值的参数,返回一个特性。
:::info
注意:这里的函数指的是数学意义上的函数,而不是Rust中的函数。
:::
对于PartialEq这种范型特性而言,它具有一个参数,名称为Rhs。同时,PartialEq还对Rhs的取值做了两点限制:
传入
Rhs的类型,可以是一个定长类型,也可以是一个非定常类型。- 这种限制,在语法上表示为
Rhs: ?Sized:::info 在这个示例中,如果没有为范型参数Rhs声明?Sized,则要求Rhs必须是一个定长类型。
这种约定(convention),对于Rust中其他地方出现的范型参数同样成立。 :::
- 这种限制,在语法上表示为
如果没有为
Rhs的传入一个类型,则Rhs具有一个缺省值Self,即:当前要实现PartialEq特性的那个类型- 这种限制,在语法上表示为
Rhs = Self
- 这种限制,在语法上表示为
调用PartialEq返回的那个特性,包含两个方法。对这两个方法的描述,参见代码块中的注释。
pub trait PartialEq<Rhs: ?Sized = Self> {// 该方法具有两个参数,返回一个bool类型的值// 该方法实现的功能是:判断两个参数的值是否相等;若相等,则返回true;否则,返回false// 该方法仅具有方法签名,需要实现PartialEq特性的类型提供该方法的实现fn eq(&self, other: &Rhs) -> bool;// 该方法具有两个参数,返回一个bool类型的值// 该方法实现的功能是:判断两个参数的值是否不相等// 该方法基于eq方法提供了一个缺省实现#[inline]fn ne(&self, other: &Rhs) -> bool {!self.eq(other)}}
Rust还要求:任何一个PartialEq实现,必须满足:a.eq(&b) XOR a.ne(&b) == true。但是,Rust没有使用任何机制对这种约束进行检查;一句话,全靠特性实现者的自觉 。
下面的代码块给出了Rust为类型bool实现PartialEq特性的源代码:
// 在下面的这行代码中,我们调用了PartialEq,但是没有向它传入一个类型// 因此, PartialEq中的Rhs参数具有缺省值Self;在此处,Self为bool类型impl PartialEq for bool {#[inline]fn eq(&self, other: &Self) -> bool { (*self) == (*other) }#[inline]fn ne(&self, other: &Self) -> bool { (*self) != (*other) }}
有了这种实现,我们就可以在bool类型的值上调用PartialEq中的两个方法了。请看下面的代码示例:
#[allow(unused)]fn main() {let t = true;let f = false;// 下面的三条语句展示了调用PartialEq特性中的eq方法的三种等价形式// 哪一种形式对程序员更为友好,一目了然let c0 = bool::eq(&t, &f);let c1 = t.eq(&f);let c2 = t == f;// 同理,下面的三条语句展示了调用PartialEq特性中的ne方法的三种等价形式let d0 = bool::ne(&t, &f);let d1 = t.ne(&f);let d2 = t != f;}
:::info
Rust中的任何类型,只要它实现了PartialEq特性,就可以采用==和!=操作符调用其中定义的eq和ne方法。这是Rust编译器给程序员带来的便利。
:::
需要注意一点:Rust为类型bool实现的PartialEq特性中,范型参数Rhs取的是默认值Self;因此,我们只能通过这种PartialEq实现中的两个方法,比较两个bool类型的值是否相等。
:::info
那么,有没有可能为bool类型实现另外一种PartialEq特性,使得我们能够使用其中定义的eq或ne方法判断一个bool类型的值与一个其他类型的值是否相等呢?
如果你对这个问题有兴趣,可以自己查找相关的资料,寻找到答案。
:::
下面,我们来介绍PartialEq特性所代表的物理含义。PartialEq声明了一种偏等关系(partial equivalence relation)。称二元关系==为一个偏等关系,当且仅当如下两个性质成立:
- 对称性(Symmetric):如果
a == b,则b == a - 传递性(Transitive):如果
a == b且b == c,则a == c如前所述,
PartialEq特性的实现者应确保上述两个性质的满足。Rust编译器不会去检查(应该也没有能力去检查)这两个性质是否满足。
Eq
Eq声明了一种等价关系(equivalence relation)。称二元关系==为一个等价关系,当且仅当如下三个性质成立:
- 自反性(reflexive):
a == a - 对称性(Symmetric):如果
a == b,则b == a - 传递性(Transitive):如果
a == b且b == c,则a == c
Eq特性的定义如下面的代码块所示:
pub trait Eq: PartialEq<Self> { }
有上述定义可知:
Eq特性中不包含任何方法Eq特性具有一个前置特性(supertrait),即:PartialEq<Self>- 这表示,若要为一个类型实现
Eq特性,则该类型必须首先实现PartialEq<Self>特性
- 这表示,若要为一个类型实现
下面的代码块给出了Rust为类型bool实现Eq特性的源代码:
impl Eq for bool {} // 无话可说
PartialOrd
PartialOrd特性的定义如下面的代码块所示。
// PartialOrd是一个范型特性,具有一个范型参数Rhs// Rhs可以是一个定长参数,也可以是一个不定长参数// Rhs的缺省值是当前类型Self// PartialOrd<Rhs>具有一个supertrait:PartialEq<Rhs>// 也就是说:若要在类型T上实现PartialOrd<Rhs>特性,则类型T必须已经实现PartialEq<Rhs>特性pub trait PartialOrd<Rhs: ?Sized = Self>: PartialEq<Rhs> {fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;#[inline]fn lt(&self, other: &Rhs) -> bool {matches!(self.partial_cmp(other), Some(Less))}#[inline]fn le(&self, other: &Rhs) -> bool {!matches!(self.partial_cmp(other), None | Some(Greater))}#[inline]fn gt(&self, other: &Rhs) -> bool {matches!(self.partial_cmp(other), Some(Greater))}#[inline]fn ge(&self, other: &Rhs) -> bool {matches!(self.partial_cmp(other), Some(Greater | Equal))}}
特性PartialOrd<Rhs>中声明了4个方法:
- 第一个方法:
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>- 该方法的功能是判断两个值之间可能存在的顺序关系(
>、==、或<) - 该方法的返回值类型为
Option<Ordering>,用于表示一个可能存在的顺序关系
- 该方法的功能是判断两个值之间可能存在的顺序关系(
// Option
- `Ordering`也是Rust标准库中定义的一个枚举类型,其源码如下所示:```rust// Ordering是一种枚举类型,具有三个变体:// 1. Less表示一个值小于另一个值// 2. Equal表示一个值等于另一个值// 3. Greater表示一个值大于另一个值#[derive(Clone, Copy, PartialEq, Debug, Hash)]#[repr(i8)]pub enum Ordering {Less = -1,Equal = 0,Greater = 1,}// Ordering上的属性声明#[repr(i8)]表示:请把Ordering类型的值在内存中表示为i8类型的值。// 每个变体后面的整数值,表示这个变体在内存中会被表示为这个i8类型的整数值
partial_cmp方法会判断传入的两个参数之间是否存在顺序关系- 如果不存在,则返回
None值 - 如果存在,例如,第一个数小于第二个数,则返回
Some(Less)值
- 如果不存在,则返回
PartialOrd<Rhs>中partial_cmp方法的实现必须与其前置特性PartialEq<Rhs>中eq方法的实现具有一致性- 即:对于两个值
l和r,l.partial_cmp(&r) == Some(Equal)当且仅当l == r- 第二个方法:
lt(&self, other: &Rhs) -> bool
- 第二个方法:
- 即:对于两个值
- 该方法的名称
lt表示的含义是:less than - 该方法的功能是判断第一个参数是否小于第二个参数
- 该方法提供了默认实现:
matches!(self.partial_cmp(other), Some(Less))- 其中,
matches是Rust标准库提供的一个宏,用于判断两个值是否具有相同的模式(pattern)
- 其中,
- 若类型
T实现了PartialOrd<Rhs>特性,给定T类型的一个值l、以及Rhs类型的一个值r,则存在三种调用lt方法的方式:T::lt(&l, &r)l.lt(&r)l < r// 显然,这种方式具有更好的可读性- 后面三个方法
le、gt、ge的含义分别为less than or equal、greater than、greater than or equal。
- 后面三个方法
- 它们的实现和调用方式与
lt方法类型,我们不再逐一解释
PartialOrd声明了一种偏序关系(partial order relation)。偏序关系在数学上具有严格的定义;我们不在此说明。Rust要求任何PartialOrd<Rhs>特性的实现都必须满足偏序关系的数学定义;然而,如前所述,Rust编译器不会去检查这种满足性。
下面的代码块给出了Rust为类型bool实现PartialOrd特性的源代码:
impl PartialOrd for bool {#[inline]fn partial_cmp(&self, other: &bool) -> Option<Ordering> {Some(self.cmp(other))// 这里的cmp是bool实现的另一个特性Ord定义的方法// 我们马上就要介绍Ord特性}}
Ord
特性Ord的定义如下面的代码块所示。
//特性Ord具有两个前置特性:Eq和PartialOrd<Self>pub trait Ord: Eq + PartialOrd<Self> {fn cmp(&self, other: &Self) -> Ordering;#[inline]fn max(self, other: Self) -> SelfwhereSelf: Sized,{match cmp(&self, &other) {Ordering::Less | Ordering::Equal => v2,Ordering::Greater => v1,}}#[inline]fn min(self, other: Self) -> SelfwhereSelf: Sized,{match cmp(&self, &other) {Ordering::Less | Ordering::Equal => v1,Ordering::Greater => v2,}}fn clamp(self, min: Self, max: Self) -> SelfwhereSelf: Sized,{assert!(min <= max);if self < min {min} else if self > max {max} else {self}}}
Ord声明了一种严格序关系(strict order relation)。Ord特性的实现应该满足严格序的定义,且应该与其前置特性Eq和PartialOrd<Self>相关方法的实现保持一致。具体细节,可查看Rust官方文档。
下面的代码块给出了Rust为类型bool实现Ord特性的源代码:
impl Ord for bool {#[inline]fn cmp(&self, other: &bool) -> Ordering {match (*self as i8) - (*other as i8) {-1 => Less,0 => Equal,1 => Greater,_ => unsafe { unreachable_unchecked() },}}}// 在上面的代码中,unreachable_unchecked函数的功能是:// 告诉编译器这个方法调用不可达,进而对相关代码做出优化
Not
特性Not的定义如下面的代码块所示:
pub trait Not {type Output;fn not(self) -> Self::Output;}// 这个trait定义中出现了一种新的语法:关联类型(associated type)// 在上面的定义中,type OutPut; 语句声明了一个关联类型参数 Output// 任何Not特性的实现都需要为关联类型参数提供一个具体的类型
对于任何实现了Not特性的类型T、以及一个T类型的变量t,存在三种调用not方法的形式:
T::not(t)t.not()!t;对于熟悉C/C++语言的开发者,这种形式具有更好的可读性
下面的代码块给出了Rust为类型bool实现Not特性的源代码:
impl const Not for bool {type Output = bool;#[inline]fn not(self) -> bool { !self }}
你可能会隐约感受到Rust语言的一种风格:操作符的特性化(operator as trait method)。这种风格具有两个基本特点:
- 语言中出现的大部分操作符,都会对应到特定trait的某个方法上
- 确实有一些特殊的操作符,无法对应到特定的方法上
- 例如,具有短路行为的逻辑与/或操作符
&&/||- 一个合法的操作符表达式,等价于对应的方法调用表达式
操作符的特性化,具体一个重要作用,即:支持操作符的重载。如果要在一个自定义类型上支持某种操作符,只需要在这个类型上实现相应的特性即可。
适用于bool类型的操作符,都会对应到bool类型实现的某种特性的某个方法上。已经介绍过的这种对应关系包括:
- 一元操作符
!,对应于Not特性的not方法 - 二元操作符
==/!=,对应于Eq特性的eq/ne方法 - 二元操作符
</<=/>/>=,对应于PartialOrd特性的方法lt/le/gt/ge
类似地,适用于bool类型的其他一些操作符与特性方法的对应关系如下:
- 二元操作符
|,对应于BitOr特性的bitor方法 - 二元操作符
|=,对应于BitOrAssign特性的bitor_assign方法 - 二元操作符
&,对应于BitAnd特性的bitand方法 - 二元操作符
&=,对应于BitAndAssign特性的bitand_assign方法 - 二元操作符
^,对应于BitXor特性的bitxor方法 - 二元操作符
^=,对应于BitXorAssign特性的bitxor_assign方法
对于其中涉及的特性,我们就不再进行介绍了。有兴趣的同学,可以自己阅读相关资料。
数字类型
Rust提供了一组定长的数字类型,具体可分为整数类型和浮点类型两类。
整数类型(Integer Type)
| 类型名 | 可表示的最小值 | 可表示的最大值 | |
|---|---|---|---|
| 无符号 整数类型 |
u8 | ||
| u16 | |||
| u32 | |||
| u64 | |||
| u128 | |||
| usize | |||
| 有符号 整数类型 |
i8 | ||
| i16 | |||
| i32 | |||
| i64 | |||
| i128 | |||
| isize |
