12|类型系统:Rust的类型系统有什么特点?
该思维导图由 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-202084 - 核桃老师你好,这里遇到一个需求,就是想实现一个类似二维动态数组的,然后数组里面的元素是一个指针,指向另外一个随机数组,因为这里是同事强制要求的,后面调用了一个封装第三方的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-22419 - 荒野林克老师,这里有一个疑惑: ```impl<W: Write> MyWriter<W>``` 里,impl 后面以及指明约束,为什么 MyWriter 后面还要单独写一次呢?
作者回复: 因为 impl<W> 相当于声明了一个泛型参数, MyWriter<W> 是使用这个泛型参数
2021-09-2810 - 罗杰追老师的更新比追剧有趣多了,配合张老师的视频课,还有陈老师 B 站的 Rust 培训视频,真的很舒服。
作者回复: 哈哈,感谢支持!
2021-09-2033 - 核桃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-172 - MarvichovW会被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-202 - 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-201 - 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-201 - 随风wli老师,因为单态化,代码以二进制分发会损失泛型的信息,这里以二进制分发是指库代码编译成了类似可执行文件,然后rust直接调用吗
作者回复: 对,Rust 编译成二进制库
2021-12-30 - Geek_2b06a7文中说 Rust 是显式静态类型,支持作用域内的类型推导,而 ML 和 Haskell 是隐式类型。 这其中的差异提现在什么地方?我没有明白他们类型系统的差异。
作者回复: Haskell 一切类型都能帮你推断出来,所以它是隐式静态类型
2021-12-22