上一篇:Rust系列(二) 内存管理
本质:类型系统本质是一种工具,用于在编译时对数据做静态检查,或者在运行时对数据做动态检查的时候,以确保对数据进行某个转换/修改操作,符合开发者的期望。
强类型和弱类型:按定义后类型是否可以隐式转换,可以分为强类型和弱类型。Rust 不同类型间不能自动转换,所以是强类型语言,而 C / C++ / JavaScript 会自动转换,是弱类型语言。
一、多态的实现
● 动态类型系统:Duck typing
● 静态类型系统:参数多态、特设多态、子类型多态
简而言之,参数多态是指函数可以有多种不同的参数列表实现;特设多态一般指函数的重载;子类型多态是指,在运行时,子类型可以被当成父类型使用。在 Rust 中,参数多态通过泛型来支持、特设多态通过 trait
来支持、子类型多态可以用 trait object
来支持。
二、类型系统的特点
2.1 不允许隐式转换类型
C/C++/Java 这些定义后数据可以隐式转换类型的弱类型语言,不是内存安全的,而 Rust 由于不允许隐式类型转换,所以不会出现由于隐式类型转换,导致读取不正确的数据,甚至内存访问越界的问题。
2.2 只允许访问被授权的内存
为了做到这么严格的类型安全,Rust 中除了 let / fn / static / const 这些定义性语句外,都是表达式,而一切表达式都有类型,所以可以说在 Rust 中,类型无处不在。
if exist {
do_something();
}
如上面的这段代码所示,虽然没有显示的返回值,但编译器会返回一个unit(),unit是只有一个值的类型,它的值和类型都是 ()。
2.3 支持类型推导
如果你同时写过Java
和go
你会发现Java
的每一个类型都需要手动声明,而 go
则可以直接使用:=
由编译器自动推导出变量的类型。显然,每一个类型都需要手动声明,
似乎不太符合rust
新一代编程语言的B格
,所以rust
也支持局部的自动类型推导,如下图所示:
以上不仅可以推导出num
的数据类型,还可以通过下面map
的insert操作
,推断出map
的类型。
turbofish
语法
use std::net::SocketAddr;
fn main() {
let addr = "127.0.0.1:8080".parse::<SocketAddr>().unwrap();
println!("addr: {:?}, port: {:?}", addr.ip(), addr.port());
}
如上所示,在泛型函数后使用::
来强制使用类型 T=SocketAddr
,这种写法被称为 turbofish
。
2.4 原生类型
具体可以参考官网链接:Primitive Types。
Type | 中文翻译 | Description |
---|---|---|
never | 从不 | The ! type, also called “never”. |
array | 数组 | A fixed-size array, denoted [T; N], for the element type, T, and the non-negative compile-time constant size, N. |
bool | 布尔类型 | The boolean type. |
char | 字符类型 | A character type. |
f32 | 单精度浮点数 | A 32-bit floating point type (specifically, the “binary32” type defined in IEEE 754-2008). |
f64 | 双精度浮点数 | A 64-bit floating point type (specifically, the “binary64” type defined in IEEE 754-2008). |
fn | 函数指针 | Function pointers, like fn(usize) -> bool. |
i8 | 8位有符号整数 | The 8-bit signed integer type. |
i16 | 16位有符号整数 | The 16-bit signed integer type. |
i32 | 32位有符号整数 | The 32-bit signed integer type. |
i64 | 64位有符号整数 | The 64-bit signed integer type. |
i128 | 128位有符号整数 | The 128-bit signed integer type. |
isize | 指针大小有符号整数 | The pointer-sized signed integer type. |
pointer | 指针 | Raw, unsafe pointers, *const T, and *mut T. |
reference | 引用 | References, &T and &mut T. |
slice | 切片 | A dynamically-sized view into a contiguous sequence, [T]. Contiguous here means that elements are laid out so that every element is the same distance from its neighbors. |
str | 字符串切片 | String slices. |
tuple | 元组 | A finite heterogeneous sequence, (T, U, …). |
u8 | 8位无符号整数 | The 8-bit unsigned integer type. |
u16 | 16位无符号整数 | The 16-bit unsigned integer type. |
u32 | 32位无符号整数 | The 32-bit unsigned integer type. |
u64 | 64位无符号整数 | The 64-bit unsigned integer type. |
u128 | 128位无符号整数 | The 128-bit unsigned integer type. |
unit | 空类型 | The () type, also called “unit”. |
usize | 指针大小无符号整数 | The pointer-sized unsigned integer type. |
2.5 泛型函数
对于泛型函数,Rust 会进行单态化(Monomorphization)
处理,也就是在编译时,把所有用到的泛型函数的泛型参数展开,生成若干个函数。
单态化的好处是,泛型函数的调用是静态分派(static dispatch),在编译时就一一对应,既保有多态的灵活性,又没有任何效率的损失,和普通函数调用一样高效。
但单态化有很明显的坏处,就是编译速度很慢,一个泛型函数,编译器需要找到所有用到的不同类型,一个个编译,所以 Rust 编译代码的速度总被人吐槽,这和单态化脱不开干系(另一个重要因素是宏)。
三、如何使用trait来定义接口?
3.1 基本 trait
话不多说都在代码注释中了,可以和Java
的Interface
对比学习。需要注意的一个点是,rust
的各种trait
限定规则,总是会莫名奇妙的踩坑。
use regex::Regex;
pub trait Parse {
type Error;
// parse 方法是 trait 的静态方法,因为它的第一个参数和 self 无关,所以在调用时需要使用 T::parse(str)
fn parse(s: &str) -> Result<Self, Self::Error>
// 因为返回值包含 Parse trait 的关联类型 Error,
// 所以需要显式地添加 Sized 限定来确保函数的返回值类型是固定大小的
where Self: Sized;
}
impl Parse for u8 {
type Error = String;
fn parse(s: &str) -> Result<Self, Self::Error> {
// r开头的字符串表示原始字符串字面量(raw string literal)
// 在原始字符串字面量中,转义字符会被忽略,也就是说反斜杠 '\' 不会被解释为转义字符
let re: Regex = Regex::new(r"^[0-9]+").unwrap();
if let Some(captures) = re.captures(s) {
captures.get(0)
.map_or(Err("failed to captures".to_string()),
|s| s.as_str().parse()
.map_err(|_err| "failed to parse captured string".to_string()))
} else {
Err("failed to parse string".to_string())
}
}
}
#[test]
fn parse_should_work() {
assert_eq!(u8::parse("123abc").unwrap(), 123);
assert_eq!(u8::parse("1234abcd").unwrap_or(0), 0);
assert_eq!(u8::parse("abcd").unwrap_or(0), 0);
}
fn main() {
// 255
println!("result: {}", u8::parse("255 hello world").unwrap_or(0));
}
3.2 泛型 trait
把上面为普通实现的trait改写为泛型trait即可:
use std::str::FromStr;
use regex::Regex;
pub trait Parse {
type Error;
fn parse(s: &str) -> Result<Self, Self::Error>
where Self: Sized;
}
impl<T> Parse for T
where T: FromStr {
type Error = String;
fn parse(s: &str) -> Result<Self, Self::Error> {
let re: Regex = Regex::new(r"^[0-9]+(\.[0-9]+)?").unwrap();
if let Some(captures) = re.captures(s) {
captures.get(0)
.map_or(Err("failed to captures".to_string()),
|s| s.as_str().parse()
.map_err(|_err| "failed to parse captured string".to_string()))
} else {
Err("failed to parse string".to_string())
}
}
}
#[test]
fn parse_should_work() {
assert_eq!(u8::parse("123abc").unwrap(), 123);
assert_eq!(u8::parse("1234abcd").unwrap_or(0), 0);
assert_eq!(u8::parse("abcd").unwrap_or(0), 0);
assert_eq!(u32::parse("123.45abcd").unwrap_or(0), 0);
assert_eq!(u32::parse("123abcd").unwrap(), 123);
assert_eq!(f32::parse("123.45abcd").unwrap(), 123.45);
}
fn main() {
// 255
println!("result: {}", u8::parse("255 hello world").unwrap_or(0));
// 123.45
println!("result: {}", f32::parse("123.45abcd").unwrap());
}
四、常用的trait
用好trait
,会让代码结构更加清晰,阅读和使用都更加符合 Rust
生态的习惯。比如数据结构实现了 Debug trait
,就可以用 {:?}
来打印;数据结构实现了 From<T> trait
,就可以直接使用 into()
方法做数据转换。
4.1 Clone
● 官方文档:https://doc.rust-lang.org/std/clone/trait.Clone.html
● Clone
支持使用派生宏#[derive]
,前提条件是,结构体中的所有的子类型都实现了Clone trait
,如下所示:
// `derive` implements Clone for Reading<T> when T is Clone.
#[derive(Clone)]
struct Reading<T> {
frequency: T,
}
- 实现
Clone trait
的方式,可以参考下例:
#[derive(Debug)]
struct Generate<T> {
inner: fn() -> T,
}
impl<T> Copy for Generate<T> {}
impl<T> Clone for Generate<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner
}
}
}
impl<T> Generate<T> {
fn new(arg: fn() -> T) -> Self {
Self {
inner: arg,
}
}
}
fn getFunctionTag() -> i8 {
// https://docs.rs/rand/latest/rand/#
let base = rand::random::<i8>();
return base + 1;
}
fn main() {
let old_general = Generate::new(getFunctionTag);
let clone_general = old_general.clone();
let copy_general = old_general.clone();
// inner address:Generate { inner: 0x7ff798931170 } value:86
println!("inner address:{:?} value:{}", old_general, (old_general.inner)());
// inner address:Generate { inner: 0x7ff798931170 } value:-44
println!("inner address:{:?} value:{}", clone_general, (clone_general.inner)());
// inner address:Generate { inner: 0x7ff798931170 } value:-33
println!("inner address:{:?} value:{}", copy_general, (copy_general.inner)());
}
4.2 Copy
- 官方文档:https://doc.rust-lang.org/std/marker/trait.Copy.html
Copy trait
是一种mark trait
,之所以这么说,你看它的实现就明白了:
// 约束必须实现Clone trait
pub trait Copy: Clone { }
- 因为
Copy
约束必须实现Clone trait
, 可以用作trait bound
来进行类型安全检查。
4.3 Drop
- 官方文档:https://doc.rust-lang.org/std/ops/trait.Drop.html
Copy trait
和Drop trait
是互斥的,两者不能共存,当你尝试为同一种数据类型实现Copy
,也实现Drop
,编译器就会报错。这其实很好理解:Copy
是按位做浅拷贝,那么它会默认拷贝的数据没有需要释放的资源;而Drop
恰恰是为了释放额外的资源而生的。- 简单使用示例:
struct HasDrop;
impl Drop for HasDrop {
fn drop(&mut self) {
println!("Dropping HasDrop!");
}
}
struct HasTwoDrops {
one: HasDrop,
two: HasDrop,
}
impl Drop for HasTwoDrops {
fn drop(&mut self) {
println!("Dropping HasTwoDrops!");
}
}
fn main() {
let _x = HasTwoDrops { one: HasDrop, two: HasDrop };
println!("Running!");
// Running!
// Dropping HasTwoDrops!
// Dropping HasDrop!
// Dropping HasDrop!
}