正式开始
- 在机器码的世界中,指令仅仅和立即数或者内存打交道,内存中存放的数据都是字节流
- 类型系统完全是一种工具
类型系统基本概念与分类
- 类型:是对值的区分,它包含了值在内存中的长度、对齐以及值可以进行的操作等信息
- 类型系统其实就是,对类型进行定义、检查和处理的系统
分类
-
按定义后类型是否可以隐式转换,可以分为强类型和弱类型
-
按类型检查的时机,在编译时检查还是运行时检查,可以分为静态类型系统和动态类型系统
a. 对于静态类型系统,还可以进一步分为显式静态和隐式静态
多态
-
在使用相同的接口时,不同类型的对象,会采用不同的实现
-
对于动态类型系统,多态通过**鸭子类型(duck typing)**实现
-
对于静态类型系统,多态可以通过**参数多态(parametric polymorphism)、特设多态(adhoc polymorphism)和子类型多态(subtype polymorphism)**实现
a. 参数多态是指,代码操作的类型是一个满足某些约束的参数,而非具体的类型。
b. 特设多态是指同一种行为有多个不同实现的多态。比如加法,可以 1+1,也可以是 “abc” + “cde”、matrix1 + matrix2、甚至 matrix1 + vector1。在面向对象编程语言中,特设多态一般指函数的重载
c. 子类型多态是指,在运行时,子类型可以被当成父类型使用
-
在 Rust 中,参数多态通过泛型来支持、特设多态通过 trait 来支持、子类型多态可以用 trait object 来支持
Rust类型系统
- 在定义时, Rust 不允许类型的隐式转换,也就是说,Rust 是强类型语言;
- 在检查时,Rust 使用了静态类型系统,在编译期保证类型的正确
- 从内存的角度看,类型安全是指 代码只能按照被允许的方法和被允许的权限,访问它被授权访问的内存
Rust 中除了 let / fn / static / const 这些定义性语句外都是表达式,而一切表达式都有类型
在 Rust 中,对于一个作用域,无论是 if / else / for 循环还是函数,最后一个表达式的返回值就是作用域的返回值,如果表达式或者函数不返回任何值,那么它返回一个 unit() 。unit 是只有一个值的类型,它的值和类型都是 ()
数据类型
- Rust 的原生类型包括字符、整数、浮点数、布尔值、数组(array)、元组(tuple)、切片(slice)、指针、引用、函数等
- 在原生类型的基础上,Rust 标准库还支持非常丰富的组合类型
- 类型分类
类型推导
- 在一个作用域之内,Rust 可以根据变量使用的上下文,推导出变量的类型
- collect 是 Iterator trait 的方法,它把一个 iterator 转换成一个集合
- 在泛型函数后使用 :: 来强制使用类型 T,这种写法被称为 turbofish
- 有些情况下,即使上下文中含有类型的信息,也需要开发者为变量提供类型,比如常量和静态变量的定义
用泛型实现参数多态
泛型数据结构
泛型结构 Vec<T>
pub struct Vec<T, A: Allocator = Global> {
buf: RawVec<T, A>,
len: usize,
}
pub struct RawVec<T, A: Allocator = Global> {
ptr: Unique<T>,
cap: usize,
alloc: A,
}
/*
Vec有两个参数
一个是 T,是列表里的每个数据的类型
另一个是 A,它有进一步的限制 A: Allocator ,也就是说 A 需要满足 Allocator trait
A 这个参数有默认值 Global,它是 Rust 默认的全局分配器
*/
枚举类型 Cow
生命周期标注也是泛型的一部分,一个生命周期 'a 代表任意的生命周期,和 T 代表任意类型是一样的
/*
这里对B 的三个约束分别是:
1. 生命周期 'a
2. 长度可变 ?Sized
3. 符合 ToOwned trait
*/
pub enum Cow<'a, B: ?Sized + 'a> where B: ToOwned,
{
// 借用的数据
Borrowed(&'a B),
// 拥有的数据
// 对 B 做了一个强制类型转换,转成 ToOwned trait
// 然后访问 ToOwned trait 内部的 Owned 类型
Owned(<B as ToOwned>::Owned),
}
含义
Cow(Clone-on-Write)在返回数据的时候,提供了一种可能:
- 要么返回一个借用的数据(只读)
- 要么返回一个拥有所有权的数据(可写)
Cow 泛型约束参数
-
对于拥有所有权的数据 B ,第一个是生命周期约束。
a. 这里 B 的生命周期是 'a,所以 B 需要满足 'a,用 B: 'a 来表示。
b.当 Cow 内部的类型 B 生命周期为 'a 时,Cow 自己的生命周期也是 'a。
-
B 还有两个约束:?Sized 和 “where B: ToOwned”
a. ?Sized 是一种特殊的约束写法,? 代表可以放松问号之后的约束。
b. 由于 Rust 默认的泛型参数都需要是 Sized,也就是固定大小的类型,所以这里 ?Sized 代表用可变大小的类型
c. ToOwned 是一个 trait,它可以把借用的数据克隆出一个拥有所有权的数据
泛型约束的方式
在表述泛型参数的约束时,Rust 允许两种方式
- 一种类似函数参数的类型声明,用 “:” 来表明约束,多个约束之间用 + 来表示;
- 另一种是使用 where 子句,在定义的结尾来表明参数的约束
- 两种方法都可以,且可以共存
泛型函数
-
在声明一个函数的时候,我们可以不指定具体的参数或返回值的类型,而是由泛型参数来代替
-
对于泛型函数,Rust 会进行单态化(Monomorphization)处理,也就是在编译时,把所有用到的泛型函数的泛型参数展开,生成若干个函数
a. 单态化的好处是,泛型函数的调用是静态分派
b. 编译速度很慢,一个泛型函数,编译器需要找到所有用到的不同类型,一个个编译
小结
好用链接
- 鸭子类型 概念
- 参数多态 概念
- 特设多态 概念
- 子类型多态
- Rust 原生类型
- Iterator trait
- 内存分配 Global
- Rust 默认的全局分配器
- ToOwned trait
- 单态化 概念
- 里氏替换原则
- 各个语言实现和处理泛型
精选问答
- 下面这段代码为什么不能编译通过?你可以修改它使其正常工作么?
use std::io::{BufWriter, Write};
use std::net::TcpStream;
#[derive(Debug)]
struct MyWriter<W> {
writer: W,
}
impl<W: Write> MyWriter<W> {
pub fn new(addr: &str) -> Self {
let stream = TcpStream::connect("127.0.0.1:8080").unwrap();
Self {
writer: BufWriter::new(stream),
}
}
pub fn write(&mut self, buf: &str) -> std::io::Result<()> {
self.writer.write_all(buf.as_bytes())
}
}
fn main() {
let writer = MyWriter::new("127.0.0.1:8080");
writer.write("hello world!");
}
代码报错的主要原因是
- 实现 new 方法时,对泛型的约束要求要满足 W: Write,而 new 的声明返回值是 Self,也就是说 self.wirter 必须是 W: Write 类型(泛型)
- 但实际返回值是一个确定的类型 BufWriter<TcpStream>,这不满足要求
修改方法有这么几个思路
- 修改 new 方法的返回值
impl<W: Write> MyWriter<W> {
pub fn new(addr: &str) -> MyWriter<BufWriter<TcpStream>> {
let stream = TcpStream::connect(addr).unwrap();
MyWriter {
writer: BufWriter::new(stream),
}
}
}
fn main() {
let mut writer = MyWriter::<BufWriter<TcpStream>>::new("127.0.0.1:8080");
writer.write("hello world!");
}
- 对确定的类型 MyWriter<BufWriter>实现 new 方法
impl MyWriter<BufWriter<TcpStream>> {
pub fn new(addr: &str) -> Self {
let stream = TcpStream::connect(addr).unwrap();
Self {
writer: BufWriter::new(stream),
}
}
}
fn main() {
let mut writer = MyWriter::new("127.0.0.1:8080");
writer.write("hello world!");
}
- 修改 new 方法的实现,使用依赖注入
impl<W: Write> MyWriter<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
}
}
}
fn main() {
let stream = TcpStream::connect("127.0.0.1:8080").unwrap();
let mut writer = MyWriter::new(BufWriter::new(stream));
writer.write("hello world!");
}