这是【零基础 Rust 入门】系列的第 3 章。本系列由前端技术专家零弌分享。想要探索前端技术的无限可能,就请关注我们吧!🤗
- 🧑💻 我们是谁:支付宝体验技术部-基础服务团队
- 📖 我们的专栏:前沿视点
Scalar Types
长度 | 有符号 | 无符号 |
---|---|---|
8bit | i8 | u8 |
16bit | i16 | u16 |
32bit | i32 | u32 |
64bit | i64 | u64 |
128bit | i128 | u128 |
arch(平台相关) | isize | usize |
32bit | f32 | - |
64bit | f64 | - |
8bit | boolean | - |
4 bytes(32 bit) | char(Unicode) |
let a = 10u8; // 这个是 u8 类型
let a = 10u64; // 这个是 u64 类型
let heart_eyed_cat = '😻'; // 这是一个 char
Rust 冷知识:溢出
关于溢出的问题,在 debug 模式下会有 panic 产生,release 模式下还是和其他语言一样的溢出,如果要依赖溢出这个特性,应该要用标准库中的 wrapping.
Compound Type
tuple
tuple 类似数组,长度是固定的,又有些类似 struct,每一位都有独立的类型。
具体写法如下
// 可以声明类型,也可以不声明
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 可以直接访问第一位
let five_hundred = tup.0;
// 也可以解构
let (a, b, c) = tup;
在 ts 里也可以实现类似的效果。
let a: [ Number, String ] = [ 2333, '2333' ];
array
数组是固定长度的,并且是每一个元素都必须是相同类型的。并且数组的内存是分配在栈上的。
数组有两种声明的方式:
let a = [1, 2, 3, 4, 5];
字面的形式。let a = [0; 3];
代表数组的长度为 3,每个元素为 0。要写这种形式的话,要保证元素是实现Copy
的。
Rust 冷知识:不同长度的数组类型其实是不同的。(以下来自 Rust 1.76.0)
[expr; 0]
长度为 0 的数组是允许的,并且 expr 还会被执行- 长度为 [0, 32] 的数组默认实现了以下 Trait
- Copy
- Clone
- Debug
- IntoIterator (implemented for [T; N], &[T; N] and &mut [T; N])
- PartialEq, PartialOrd, Eq, Ord
- Hash
- AsRef, AsMut
- Borrow, BorrowMut
- 长度为 [1, 12] 的数组默认实现了 From
<Tuple>
Struct
定义
struct User {
username: String,
email: String,
sign_in_count: u64,
active: boolean,
}
初始化
let user = User {
email: String::from("zhubin.gzb@antgroup.com"),
username: String::from("zhubin.gzb"),
active: true,
sign_in_count: 1,
};
let user2 = User {
...user,
sign_in_count: 2,
};
let email = String::from("zhubin.gzb@antgroup.com");
let username = String::from("zhubin.gzb");
let active = true;
let sign_in_count = 1;
User {
email,
username,
active,
sign_in_count,
}
Debug
给 Struct 增加 derive attribute 来使用 Rust 实现的默认 debug 实现。
所有支持的 derivable traits 可以参考 doc.rust-lang.org/book/append…。
#[derive(Debug)]
struct User {
username: String,
email: String,
sign_in_count: u64,
active: boolean,
}
Function&Method
- method: 在 impl 上下文中实现的
fn
- function: 没有在 impl 上下文中实现的
fn
impl User {
// Method
fn sign_in(&mut self) -> u32 {
self.sign_in_count +=1;
}
}
// Function
fn sign_in_user(user: &mut User) {
self.sign_in_count +=1;
}
sign_in_user(user);
user.sign_in();
大家一定有一个疑惑构造函数去哪了?rust 中并没有构造函数这种概念,只有 struct 的实例化,一般通过一个 new
方法来表示构造函数。
impl User {
fn new(email: String, user_name: String) -> Self {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
}
Enum
rust 中的 enum 非常强大,到处都会使用,加上模式匹配,真香。match 大概比 switch 强 100 倍。
最基础的 enum 使用方法和传统的 enum 没啥区别。这里定义了 Message enum,其有不同的枚举值。
enum Message {
Quit,
Move,
Write,
ChangeColor,
}
这里将变量和枚举值关联在了一起,拿到一个 enum 之后就可以通过 match 来做不同情况的处理。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
演示一个经典操作。rust 为了避免经典的 billion-dollar mistake
,不提供 null 这个特性,提供了 Option
这个枚举。
pub enum Option<T> {
None,
Some(T),
}
模式匹配
下面是一个模式匹配的实例
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
// 这样就获取到了 enum 中的值
// 要注意值的类型,这里会 copy/move
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
match 时也可以匹配具体的值
fn print_only_three(x: Options<i32>) {
match x {
Some(3) => println("val is 3"),
_ => (),
};
}
也可以通过 if 来实现
fn print_only_three(x: Options<i32>) {
if let Some(3) = x {
print("val is 3");
}
}
错误处理
和 Option 同等地位的一个枚举。
enum Result<T, E> {
Ok(T),
Err(E),
}
可以让程序直接挂掉。
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
可以转换为另一个错误。
use std::io;
use std::io::Read;
use std::fs::File;
// 定义一个自定义错误
enum MyError {
IoError(std::io::Error)
}
// 实现 IoError 到 MyError 的转换逻辑
impl From<std::io::Error> for MyError {
fn from(e: std::io::Error) -> Self {
MyError::IoError(e)
}
}
type Result<T> = std::result::Result<T, MyError>;
fn read_username_from_file() -> Result<String> {
// ? 会调用 From::from 方法
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
问:为什么不能实现 impl Into<MyError> for std::io::Error
?
答:不可以实现自己项目之外的类型。
Trait & Generic
Trait 定义了一个特定的类型具备的能力,和 interface 是类似的。
定义 Trait
定义了一个 trait Summary 具有一个 method summarize。
pub trait Summary {
fn summarize(&self) -> String;
}
实现 Trait
impl Summary for User {
fn summarize(&self) -> String {
format!("{}@{}", self.user_name, self.email)
}
}
默认实现 Trait
pub trait Summary {
fn summarize(&self) -> String {
String::from("...")
}
}
Trait Bound
pub fn print<T: Summary>(item: &T) {
println!("show {}", item.summarize());
}
可以将多个 Trait 绑在一起
pub fn print<T: Summary + Display>(item: &T) {
println!("show {}", item.summarize());
}
在范型很多的时候可以写的更加优雅
pub fn print<T>(item: &T)
where T : Summary + Display{
println!("show {}", item.summarize());
}
Associated Type
Associated Type
可以在范型中定义类型占位符,这些类型占位符可以在 trait 中的函数签名中使用。
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {
num: u32
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
let num = self.num
self.num += 1
Some(num)
}
}
在举个使用范型的例子,这里用 T 去替换了 Associated Type
。
pub trait Iterator<T> {
fn next(&mut self) -> Option<T>;
}
struct Counter {
num: u32
}
impl Iterator<u32> for Counter {
fn next(&mut self) -> Option<u32> {
let num = self.num
self.num += 1
Some(num)
}
}
Associated Type
使范型使用的时候不用声明其参数,比如 Counter 这里的例子明显是没必要的。特别是Associated Type
多的时候,如果用范型就会很麻烦。Associated Type
只能有一个,在 impl 中就指定了。不像范型可以指定多个实现。
操作符重载与默认范型
为 Point
实现了 +
操作。
use std::ops::Add;
#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
}
其实 Add
还有范型,默认类型是其自己,也可以为其他类型实现。
trait Add<Rhs=Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
Generic Associated Type
在 GAT 面世之前,Associated Type
是不可以加范型的,而上一章也提过 'a
也是一个范型。blog.rust-lang.org/2022/10/28/…
trait LendingIterator {
type Item<'a> where Self: 'a;
fn next<'a>(&'a mut self) -> Self::Item<'a>;
}
补充阅读
- type alias:
type Result<T> = std::result::Result<T, MyError>;
- never type:
!
fn bar() -> !
,比如说exec
、unimplement
就需要这样的类型。 - Dynamically Sized Types: 前一章提到 rust 必需知道所有类型的大小,而 str 是没有大小的,只能使用 &str 类型。哪如果要用到 str 怎么办?
fn generic<T: ?Sized>(t: &T)
。注意:?
只能对Sized
可以用。
本章习题
定义一个 Iterator 范型,并为 Apple 实现,支持遍历 Apple 的切片。