Rust类型系统

1.Rust类型系统

计算机就是用来存储信息和处理信息的。在编译期间进行类型检查的语言属于静态类型;在运行期间进行类型检查的语言属于动态类型。如果一门语言在不存在类型的隐式转换,要求不同类型之间的类型在执行运算之前必须要进行显式的强制类型转换的话,那么这门语言就属于强类型,反之就属于弱类型。

静态语言在编译期间会执行类型检查,而类型检查离不开类型系统。所以如果一门语言的类型系统设计的足够好的话,那么将能够规避很多bug问题。举个反例:尽管c,cplusplus属于静态语言,但是在编译期间却不能检测出数组越界行为,这属于c,cplusplus类型系统之外的未定义行为。而在rust语言的类型系统中,则不存在这个问题,无需担心。

同时,强大的类型系统能够进行类型推导,比如说Haskell。Rust类型系统受其启发,但是做的并没有那么强大,类型推导还是稍有不足。

动态类型的语言如果要做类型检查的话,那么就只能在运行期间做了,动态类型的语言也能够做到类型安全。

如果类型系统允许一段代码在不同的上下文中具有不同的类型的话,那么这个类型系统便具备多态特性。如果按照多态的时间来划分的话,那么多态又可以分为静多态和动多态。静多态发生在编译期间,静多态牺牲灵活获取性能;而动多态发生在运行期,牺牲性能获取灵活。rust既支持静多态也支持动多态,静多态是一种零成本抽象。

多态目前主要有三种形式:分别是参数化多态,Ad-hoc多态,子类型多态。

  • 1.参数化多态就是指泛型,泛型能够获得更好的表达力,同时能够保证静态类型安全。

  • 2.Ad-hoc多态:同一种行为定义,在不同的上下文中会有不同的行为表现。Haskell中使用Typeclass来支持Ad-hoc多态,rust中使用trait来实现。trait定义了行为,将其impl到不同的struct中可以实现不同的表现。

  • 3.子类型多态一般用在面向对象的编程语言中,比如Java中的继承。在Rust中并没有Java中的继承概念,所以不存在子类型多态。

总结:rust多态只支持参数化多态和Ad-hoc多态,凭借泛型和trait。

Rust中一切皆表达式,表达式皆有值,值皆有类型,所以可以说,Rust中一切皆有类型。

为什么说Rust是类型安全?除了一些基本的原生类型和复合类型,Rust把作用域也纳入了类型系统。Rust中有一些表达式,有的时候没有返回值(也就是返回单元值),有时返回错误的值,Rust也将这些纳入了类型系统,也就是Option和Result<T,E>这样的类型。甚者,一些极端情况,比如说线程崩溃,break,continue等行为,也被纳入了类型系统,这种类型叫做never类型。

2.类型大小

存信息,首先需要先知道大小。Rust中没有GC,内存首先通过编译器来分配,Rust被编译为LLVM IR,被编译的产物中会携带内存分配的信息。因此Rust编译器需要知道类型大小,才能分配合理的内存。

  • 1.Sized Type: Rust中大部分类型都是在编译器就可以确定大小的类型,比如u32固定4个字节。

  • 2.Dynamic Sized Type: DST,动态大小的类型。

这里着重介绍一下DST,动态类型。比如说字符串,字符串到底占多少字节这个是说不定的。对于这种情况,Rust提供了引用类型,引用的类型大小总是能够确定的,字符串切片&str就是一种引用类型,引用类型由指针和长度信息组成。引用存在栈上面,引用所指向的数据信息存储在堆上面。为什么引用的类型就能够确定类型大小了?因为指针是固定的大小,长度信息也是可以知道的。因此编译器就可以正确的为其分配栈内存空间,而引用所指向的数据在运行的时候也会在堆上开辟出内存空间。举例如下所示:

1
2
3
4
5
6
7
fn main() {
let str = "hello world";
let ptr = str.as_ptr();
let len = str.len();
println!("{:p}", ptr); // 本机此时输出0x10ce51840
println!("{:?}", len); // 11
}

&str这种引用类型,它所指向的内容是动态大小类型,同时携带了长度信息,在Rust中又被叫做胖指针(Fat Pointer)。

  • 3.Zero Sized Type:零大小类型。比如单元类型和单元结构体,大小都是0。0+0还会等于0,如果一个结构体或者数组是由单元类型组成的话,那么其大小也为0。

  • 4.Bottom Type:底类型。never就是底类型,底类型的特点是1.没有值;2.是其它任意类型的子类型。如果说ZST类型是空的话,那么底类型就表示无。底类型没有值,而且可以等价于任意类型,Rust使用叹号!来表示底类型。

底类型举例使用:在Rust中,要求if的每一个分支都返回同一种类型的值。

1
2
3
4
5
6
7
8
9
10
11
fn foo() -> ! {
loop { println!("ok"); }
}
fn main() {
let i = if false {
foo()
} else {
100
};
assert_eq!(i, 100);
}

3.泛型

泛型是一种参数化多态,使用泛型可以编写更为抽象的代码。下面是两个使用泛型的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn foo<T>(x: T) -> T {
x
}
struct Point<T> { x: T, y: T }
impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Point{x: x, y: y}
}
}
fn main() {
let point1 = Point::new(1, 2);
let point2 = Point::new("1", "2");
assert_eq!(foo(1), 1);
assert_eq!(foo("2"), "2");
}

Rust中的泛型属于静多态,它是一种编译器多态:在编译期间,泛型会被单态化。单态化是编译器进行静态分发的一种策略。单态化意味着编译器要将一个泛型函数生成两个具体类型对应的函数。泛型和单态化是rust中很重要的两个功能,单态化静态分发的好处是性能好,没有运行时开销,缺点是容易造成二进制文件变大。