陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

12|类型系统:Rust的类型系统有什么特点?

用泛型实现参数多态
类型推导
数据类型
类型系统基本概念与分类
Rust 类型系统
类型系统

该思维导图由 AI 生成,仅供参考

你好,我是陈天。今天我们就开始类型系统的学习。
如果你用 C/Golang 这样不支持泛型的静态语言,或者用 Python/Ruby/JavaScript 这样的动态语言,这个部分可能是个难点,希望你做好要转换思维的准备;如果你用 C++/Java/Swift 等支持泛型的静态语言,可以比较一下 Rust 和它们的异同。
其实在之前的课程中,我们已经写了不少 Rust 代码,使用了各种各样的数据结构,相信你对 Rust 的类型系统已经有了一个非常粗浅的印象。那类型系统到底是什么?能用来干什么?什么时候用呢?今天就来一探究竟。
作为一门语言的核心要素,类型系统很大程度上塑造了语言的用户体验以及程序的安全性。为什么这么说?因为,在机器码的世界中,没有类型而言,指令仅仅和立即数或者内存打交道,内存中存放的数据都是字节流。
所以,可以说类型系统完全是一种工具,编译器在编译时对数据做静态检查,或者语言在运行时对数据做动态检查的时候,来保证某个操作处理的数据是开发者期望的数据类型。
现在你是不是能理解,为什么 Rust 类型系统对类型问题的检查格外严格(总是报错)。

类型系统基本概念与分类

在具体讲 Rust 的类型系统之前,我们先来澄清一些类型系统的概念,在基本理解上达成一致。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Rust的类型系统具有强类型和静态类型的特点,保证了语言的类型安全。类型系统对值的区分,包括值在内存中的长度、对齐以及可进行的操作等信息。Rust的类型系统支持参数多态、特设多态和子类型多态,通过泛型和trait来实现。Rust的类型系统严格保证内存访问的安全性,不允许隐式类型转换,且对内存的读写进行了分开的授权。在Rust中,类型无处不在,类型推导和泛型支持使得类型标注更加便捷。Rust支持局部的类型推导,根据变量使用的上下文推导出变量的类型,减轻了开发者的负担。泛型函数通过泛型实现参数多态,避免了为不同的类型提供不同的算法。总体来说,Rust的类型系统在保证类型安全的同时,提供了丰富的数据类型和灵活的类型定义方式。文章还介绍了泛型数据结构和泛型函数的定义和使用,以及单态化的优缺点。文章内容丰富,涵盖了Rust类型系统的基本概念和高级特性,对于想要深入了解Rust类型系统的读者来说,是一篇很有价值的文章。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《陈天 · Rust 编程第一课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(31)

  • 最新
  • 精选
  • lisiur
    思考题代码报错的主要原因是,实现 new 方法时,对泛型的约束要求要满足 W: Write,而 new 的声明返回值是 Self,也就是说 self.wirter 必须是 W: Write 类型(泛型),但实际返回值是一个确定的类型 BufWriter<TcpStream>,这不满足要求。 修改方法有这么几个思路 1. 修改 new 方法的返回值 ```rust 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!"); } ``` 2. 对确定的类型 MyWriter<BufWriter<TcpStream>>实现 new 方法: ```rust 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!"); } ``` 3. 修改 new 方法的实现,使用依赖注入 ```rust 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!"); } ``` PS:第2种解法还可以对不同具体类型实现多个new方法: ```rust impl MyWriter<BufWriter<TcpStream>> { pub fn new(addr: &str) -> Self { let stream = TcpStream::connect(addr).unwrap(); Self { writer: BufWriter::new(stream), } } } impl MyWriter<File> { pub fn new(addr: &str) -> Self { let file = File::open(addr).unwrap(); Self { writer: file } } } fn main() { let mut writer = MyWriter::<BufWriter<TcpStream>>::new("127.0.0.1:8080"); writer.write("hello world!"); let mut writer = MyWriter::<File>::new("/etc/hosts"); writer.write("127.0.0.1 localhost"); } ```

    作者回复: 非常棒!最佳答案!

    2021-09-20
    20
    84
  • 核桃
    老师你好,这里遇到一个需求,就是想实现一个类似二维动态数组的,然后数组里面的元素是一个指针,指向另外一个随机数组,因为这里是同事强制要求的,后面调用了一个封装第三方的C库接口,因此这里我的代码实现思路如下所示: 代码: let mut data :Vec<* const u8> = Vec::new(); for i in 0..5{ let mut num: Vec<u8>= Vec::new(); for i in 0..16 { unsafe{ let rand_num :u8 = rand::thread_rng().gen(); num.push(rand_num) } } println!("num is : {:?},num.as_ptr(): {:?}",num,num.as_ptr()); data.push(num.as_ptr()); } println!("data is: {:?}",data); 结果: num is : [248, 69, 170, 238, 134, 89, 77, 32, 116, 106, 68, 213, 113, 19, 213, 231],num.as_ptr(): 0x7ff6ec000bc0 num is : [138, 101, 105, 192, 81, 32, 133, 80, 94, 6, 205, 164, 178, 95, 60, 45],num.as_ptr(): 0x7ff6ec000bc0 num is : [204, 173, 46, 246, 72, 25, 171, 186, 167, 175, 154, 4, 219, 78, 78, 227],num.as_ptr(): 0x7ff6ec000bc0 num is : [252, 123, 170, 107, 232, 186, 203, 91, 130, 11, 92, 48, 39, 36, 10, 193],num.as_ptr(): 0x7ff6ec000bc0 num is : [143, 52, 155, 135, 50, 50, 133, 105, 143, 62, 120, 125, 88, 58, 99, 19],num.as_ptr(): 0x7ff6ec000bc0 data is: [0x7ff6ec000bc0, 0x7ff6ec000bc0, 0x7ff6ec000bc0, 0x7ff6ec000bc0, 0x7ff6ec000bc0] 搞不明白的是,这里为什么new了之后,还是会导致地址不变,有没有办法强制让变量num每次new之后就是会改变的?多谢了

    作者回复: 你的代码有四个问题: 1. 应该是 Vec<* const [u8]>,而不是 Vec<* const u8>,你的指针是指向一个 slice 的,而非一个 u8。 2. as_ptr() 拿到的是栈上的指针,你应该需要堆上指向实际内容的指针。所以应该用 Vec::into_boxed_slice(), 然后再用 Box::into_raw 拿到裸指针。 3. 不要乱用 unsafe。这里没有需要 unsafe 的地方。 4. 循环的变量虽然没有用到,但不应该用同一个,很容易导致误用。 至于你的问题,你可以想象一下,栈上数据是如何存取的。每次循环结束,num 的作用域就结束了,它这 24 个字节的栈内存就可以重新使用,所以你拿到的都是同一个地址(而且作为裸指针是没有用处的地址)。 更新的代码: ```rust use rand::Rng; fn main() { let mut data :Vec<* const [u8]> = Vec::new(); for _i in 0..5 { let mut num: Vec<u8>= Vec::new(); for _j in 0..16 { let rand_num :u8 = rand::thread_rng().gen(); num.push(rand_num); } println!("num({:p}) is : {:?}", &*num, num); let boxed = num.into_boxed_slice(); data.push(Box::into_raw(boxed) as _); } println!("data is: {:?}",data); } ``` playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8c353b42610dd3610ab944b4a02fd572

    2021-09-22
    4
    19
  • 荒野林克
    老师,这里有一个疑惑: ```impl<W: Write> MyWriter<W>``` 里,impl 后面以及指明约束,为什么 MyWriter 后面还要单独写一次呢?

    作者回复: 因为 impl<W> 相当于声明了一个泛型参数, MyWriter<W> 是使用这个泛型参数

    2021-09-28
    10
  • 罗杰
    追老师的更新比追剧有趣多了,配合张老师的视频课,还有陈老师 B 站的 Rust 培训视频,真的很舒服。

    作者回复: 哈哈,感谢支持!

    2021-09-20
    3
    3
  • 核桃
    fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let even_numbers = numbers .into_iter() .filter(|n| n % 2 == 0) .collect(); println!("{:?}", even_numbers); } 请教一下这段代码为什么就无法推导类型呢?对于编译器来说,应该是知道number是vec的呀。不太懂,这里可能是我对编译原理理解不够扎实。

    作者回复: 为啥知道 collect() 必定 collect 出 Vec 呢?你也可以 collect 出 VecDeque 啊? ``` use std::collections::VecDeque; fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let even_numbers: VecDeque<_> = numbers .into_iter() .filter(|n| n % 2 == 0) .collect(); println!("{:?}", even_numbers); } ``` playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b8b8ca784dbd379c599bee79ddd72482

    2021-11-17
    2
  • Marvichov
    W会被resolve成一个实际类型, 也就是`BufferWriter::new(stream)`的类型. 而rust要求W是一个泛型. 我的下面的改动有点多... 有一点我就有点不明白了, 声明的时候都说了W: Write, 为啥impl的时候还要说一遍呢 (不说不让编译)? ``` use std::io::{BufWriter, Write}; use std::net::TcpStream; #[derive(Debug)] struct MyWriter<W> where W: Write, { writer: BufWriter<W>, } impl<W: Write> MyWriter<W> { pub fn new(stream: W) -> Self { 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 addr = "127.0.0.1:8080"; let stream = TcpStream::connect(addr).unwrap(); let mut writer = MyWriter::new(stream); writer.write("hello world!").unwrap(); } ```

    作者回复: 正确! 至于声明 struct 和 impl 时为何要重复声明,我觉得有两个原因:1. 文中谈到声明 struct 时的约束和实现时的约束可以不一样。2. 这样编译和类型声明代码和实现代码以及 类型对 trait 的实现代码可以彼此独立。

    2021-09-20
    2
  • qinsi
    不确定最后的代码里为什么要用泛型,改了几个不同的版本: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f21447a10eac4e3b77d39aefbd93ab6b https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2d8037d4059efe15c3d15cfa2267bb70 https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c57f2abe9756bf021241c63e1b944af8

    作者回复: 👍 非常好!

    2021-09-20
    1
  • c4f
    不能编译是因为第 14 行 writer 期待的值的类型也是 W,但收到的却是 BufWriter<TcpStream> 因此不匹配。 参考 MyReader 进行了修改,不过这种方法修改了 MyWriter 对外提供的接口(new),应该还有别的方法,不过没找出来 hh ``` use std::io::{BufWriter, Write}; use std::net::TcpStream; #[derive(Debug)] struct MyWriter<W> { writer: W, } impl<W> MyWriter<W> { pub fn new(writer: W) -> Self { Self { writer } } } impl<W: Write> MyWriter<W> { pub fn write(&mut self, buf: &str) -> std::io::Result<()> { self.writer.write_all(buf.as_bytes()) } } fn main() { let mut writer = MyWriter::new( BufWriter::new(TcpStream::connect("127.0.0.1:8080").unwrap()) ); writer.write("hello world!").unwrap(); } ``` 另外还有一个问题想请教老师:通过在冒号后面对范型进行限制和使用 where 对范型进行限制是否是等价的。比如下面的例子 ``` impl<W: Write> MyWriter<W> { ... } impl<W> MyWriter<W> where W: Write { ... } ```

    作者回复: 这么改是可以的!原代码的接口本身就有问题,所以需要动接口。对泛型的限制,用 : 限制和 where 是等价的,where 更灵活一些,表述更优雅一些。一般短小的限制,我们用 : 就足够,但对多个参数的复杂的限制,用 where 可读性会更强。

    2021-09-20
    1
  • 随风wli
    老师,因为单态化,代码以二进制分发会损失泛型的信息,这里以二进制分发是指库代码编译成了类似可执行文件,然后rust直接调用吗

    作者回复: 对,Rust 编译成二进制库

    2021-12-30
  • Geek_2b06a7
    文中说 Rust 是显式静态类型,支持作用域内的类型推导,而 ML 和 Haskell 是隐式类型。 这其中的差异提现在什么地方?我没有明白他们类型系统的差异。

    作者回复: Haskell 一切类型都能帮你推断出来,所以它是隐式静态类型

    2021-12-22
收起评论
显示
设置
留言
31
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部