记录一下学习 course.rs 过程中容易忘记的点,内容包括基础入门变量到模式匹配
解构式赋值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct Struct { e: i32 } fn main () { let (a, b, c, d, e); (a, b) = (1 , 2 ); [c, .., d, _] = [1 , 2 , 3 , 4 , 5 ]; Struct { e, .. } = Struct { e: 5 }; assert_eq! ([1 , 2 , 1 , 4 , 5 ], [a, b, c, d, e]); }
基本类型 数值类型 整数
长度
有符号类型
无符号类型
8 位
i8
u8
16 位
i16
u16
32 位
i32
u32
64 位
i64
u64
128 位
i128
u128
视架构而定
isize
usize
类型定义的形式统一为:**有无符号 + 类型大小(位数)**。
整形字面量可以用下表的形式书写:
数字字面量
示例
十进制
98_222
十六进制
0xff
八进制
0o77
二进制
0b1111_0000
字节 (仅限于 u8
)
b'A'
整型溢出:
在当使用 –release 参数进行 release 模式构建时,Rust 不检测溢出。
要显式处理可能的溢出,可以使用标准库针对原始数字类型提供的这些方法:
使用 wrapping_*
方法在所有模式下都按照补码循环溢出规则处理,例如 wrapping_add
如果使用 checked_*
方法时发生溢出,则返回 None 值
使用 overflowing_*
方法返回该值和一个指示是否存在溢出的布尔值
使用 saturating_*
方法使值达到最小值或最大值
浮点 在 Rust 中浮点类型数字也有两种基本类型: f32
和 f64
,分别为 32 位和 64 位大小。默认浮点类型是 f64
。
1 2 3 4 5 fn main () { let x = 2.0 ; let y : f32 = 3.0 ; }
浮点数陷阱:
浮点数往往是你想要数字的近似表达
浮点数在某些特性上是反直觉的,比如某些情况不能比较,因为 f32
, f64
上的比较运算实现的是 std::cmp::PartialEq
特征(类似其他语言的接口),但是并没有实现 std::cmp::Eq
特征,所以 HashMap
不能以浮点作为 key。
NaN 所有跟 NaN
交互的操作,都会返回一个 NaN
,而且 NaN 不能用来比较。可以使用 is_nan()
等方法来判断一个数值是否是 NaN
序列(Range) Rust 中序列(Range)的定义形式为:
1 2 3 let x = 1 ..=5 ; let y = 1 ..10 ; let z = 'a' ..='z' ;
序列的类型为 std::ops::Range
,它实现了 Iterator
特征,因此可以用 for
循环来
字符 1 2 3 4 5 6 fn main () { let c = 'z' ; let z = 'ℤ' ; let g = '国' ; let heart_eyed_cat = '😻' ; }
Rust 的字符不仅仅是 ASCII,所有的 Unicode 值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等。
由于 Unicode 都是 4 个字节编码,因此字符类型也是占用 4 个字节
语句 if 和 match 表达式可以作为值,赋值给变量
1 2 3 4 5 6 7 8 9 10 11 12 let a = if true { 1 } else { 2 }; let b = if x % 2 == 1 { "odd" } else { "even" };let c = match 1 { 1 => 1 , 2 => 2 , _ => 3 , };
所有权 拷贝 可以 Copy
的类型:任何基本类型的组合可以 Copy ,不需要分配内存或某种形式资源的类型是可以 Copy 的。
例子:
所有整数类型,比如 u32
布尔类型,bool
,它的值是 true
和 false
所有浮点数类型,比如 f64
字符类型,char
元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32)
是 Copy
的,但 (i32, String)
就不是
不可变引用 &T ,但是注意: 可变引用 &mut T 是不可以 Copy 的
符合类型 字符串和切片 切片 1 2 3 4 5 6 7 8 let s = String ::from ("hello world" );let hello = &s[0 ..5 ];let hello1 = &s[..5 ]; let world = &s[6 ..11 ];let len = s.len ();let helloWorld = &[0 ..len]; let helloWorld = &s[..];
切片是以字节为单位的,但是对于 UTF8 字符串来说要注意字符边界,下面会报错
1 2 3 let s = "中国人" ;let a = &s[0 ..2 ];println! ("{}" ,a);
要操作字符串可以用以下方法:
1 2 3 4 5 6 for c in "中国人" .chars () { println! ("{}" , c); } for b in "中国人" .bytes () { println! ("{}" , b); }
String 与 &str 的转换 &str
转 String
:
String::from("hello,world")
"hello,world".to_string()
反过来
1 2 3 4 5 6 7 8 9 10 fn main () { let s = String ::from ("hello,world!" ); say_hello (&s); say_hello (&s[..]); say_hello (s.as_str ()); } fn say_hello (s: &str ) { println! ("{}" ,s); }
这是基于 deref
隐式转换实现
字符串操作 追加、插入、替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 fn main () { let mut s = String ::from ("Hello " ); s.push_str ("rust" ); println! ("追加字符串 push_str() -> {}" , s); s.push ('!' ); println! ("追加字符 push() -> {}" , s); s.insert (5 , ',' ); println! ("插入字符 insert() -> {}" , s); s.insert_str (6 , " I like" ); println! ("插入字符串 insert_str() -> {}" , s); let new_string_replace = s.replace ("rust" , "RUST" ); let new_string_replacen = s.replacen ("rust" , "RUST" , 1 ); let mut string_replace_range = String ::from ("I like rust!" ); string_replace_range.replace_range (7 ..8 , "R" ); }
删除
1 2 3 4 5 6 7 8 9 10 11 fn main () { let mut string_pop = String ::from ("rust pop 中文!" ); let p1 = string_pop.pop (); let p2 = string_pop.pop (); dbg!(p1); dbg!(p2); dbg!(string_pop); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 fn main () { let mut string_remove = String ::from ("测试remove方法" ); println! ( "string_remove 占 {} 个字节" , std::mem::size_of_val (string_remove.as_str ()) ); string_remove.remove (0 ); dbg!(string_remove); }
1 2 3 4 5 6 7 8 9 10 11 fn main () { let mut string_truncate = String ::from ("测试truncate" ); string_truncate.truncate (3 ); dbg!(string_truncate); string_truncate.clear (); dbg!(string_truncate); }
连接:
使用 +
或者 +=
连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。其实当调用 + 的操作符时,相当于调用了 std::string
标准库中的 add()
方法,这里 add()
方法的第二个参数是一个引用的类型。因此我们在使用 +
, 必须传递切片引用类型。不能直接传递 String
类型。**+
是返回一个新的字符串,所以变量声明可以不需要 mut
关键字修饰。**
1 2 3 4 5 6 7 8 9 10 11 fn main () { let string_append = String ::from ("hello " ); let string_rust = String ::from ("rust" ); let result = string_append + &string_rust; let mut result = result + "!" ; result += "!!!" ; println! ("连接字符串 + -> {}" , result); }
add() 方法的定义:
1 fn add (self , s: &str ) -> String
关于所有权
1 2 3 4 5 6 7 8 9 fn main () { let s1 = String ::from ("hello," ); let s2 = String ::from ("world!" ); let s3 = s1 + &s2; assert_eq! (s3,"hello,world!" ); }
字符串转义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 fn main () { let byte_escape = "I'm writing \x52\x75\x73\x74!" ; println! ("What are you doing\x3F (\\x3F means ?) {}" , byte_escape); let unicode_codepoint = "\u{211D}" ; let character_name = "\"DOUBLE-STRUCK CAPITAL R\"" ; println! ( "Unicode character {} (U+211D) is called {}" , unicode_codepoint, character_name ); let long_string = "String literals can span multiple lines. The linebreak and indentation here ->\ <- can be escaped too!" ; println! ("{}" , long_string); }
元组和结构体 1 2 3 fn main () { let tup : (i32 , f64 , u8 ) = (500 , 6.4 , 1 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct User { active: bool , username: String , email: String , sign_in_count: u64 , } fn build_user (email: String , username: String ) -> User { User { email: email, username: username, active: true , sign_in_count: 1 , } }
结构体都需要有初始值
结构体更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let user1 = User { email: String ::from ("someone@example.com" ), username: String ::from ("someusername123" ), active: true , sign_in_count: 1 , }; let user2 = User { active: user1.active, username: user1.username, email: String ::from ("another@example.com" ), sign_in_count: user1.sign_in_count, }; println! ("{}" , user1.active);println! ("{:?}" , user1);
元组结构体(Tuple Struct):
1 2 3 4 struct Color (i32 , i32 , i32 );struct Point (i32 , i32 , i32 );let black = Color (0 , 0 , 0 );let origin = Point (0 , 0 , 0 );
单元结构体(Unit-like Struct):
1 2 3 4 5 6 7 8 struct AlwaysEqual ;let subject = AlwaysEqual;impl SomeTrait for AlwaysEqual {}
使用 #[derive(Debug)]
来打印结构体的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #[derive(Debug)] struct Rectangle { width: u32 , height: u32 , } fn main () { let rect1 = Rectangle { width: 30 , height: 50 , }; println! ("rect1 is {:?}" , rect1); }
当结构体较大时,我们可能希望能够有更好的输出表现,此时可以使用 {:#?}
来替代 {:?}
还有一个简单的输出 debug 信息的方法,那就是使用 dbg!
宏,它会拿走表达式的所有权,然后打印出相应的文件名、行号等 debug 信息,当然还有我们需要的表达式的求值结果。除此之外,它最终还会把表达式值的所有权返回!dbg!
输出到标准错误输出 stderr
,而 println!
输出到标准输出 stdout
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #[derive(Debug)] struct Rectangle { width: u32 , height: u32 , } fn main () { let scale = 2 ; let rect1 = Rectangle { width: dbg!(30 * scale), height: 50 , }; dbg!(&rect1); }
枚举 1 2 3 4 5 6 7 8 9 10 11 12 enum Message { Quit, Move { x: i32 , y: i32 }, Write (String ), ChangeColor (i32 , i32 , i32 ), } fn main () { let m1 = Message::Quit; let m2 = Message::Move{x:1 ,y:1 }; let m3 = Message::ChangeColor (255 ,255 ,0 ); }
和 C 语言的联合体类似。
用 Option 枚举处理空值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 enum Option <T> { Some (T), None , } fn plus_one (x: Option <i32 >) -> Option <i32 > { match x { None => None , Some (i) => Some (i + 1 ), } } let five = Some (5 );let six = plus_one (five);let none = plus_one (None );
数组、动态数组 在 Rust 中,最常用的数组有两种,第一种是速度很快但是长度固定的 array
,第二种是可动态增长的但是有性能损耗的 Vector
,在本书中,我们称 array
为数组,Vector
为动态数组。
1 2 3 4 5 6 fn main () { let a = [1 , 2 , 3 , 4 , 5 ]; let a : [i32 ; 5 ] = [1 , 2 , 3 , 4 , 5 ]; let a = [3 ; 5 ]; let a = [0u8 ; 3 ]; }
流程控制
使用方法
等价使用方式
所有权
for item in collection
for item in IntoIterator::into_iter(collection)
转移所有权
for item in &collection
for item in collection.iter()
不可变借用
for item in &mut collection
for item in collection.iter_mut()
可变借用
两种循环方式优劣对比:
1 2 3 4 5 6 7 8 9 10 11 let collection = [1 , 2 , 3 , 4 , 5 ];for i in 0 ..collection.len () { let item = collection[i]; } for item in collection {}
性能 :第一种使用方式中 collection[index]
的索引访问,会因为边界检查(Bounds Checking)导致运行时的性能损耗 —— Rust 会检查并确认 index
是否落在集合内,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的
安全 :第一种方式里对 collection
的索引访问是非连续的,存在一定可能性在两次访问之间,collection
发生了变化,导致脏数据产生。而第二种直接迭代的方式是连续访问,因此不存在这种风险( 由于所有权限制,在访问过程中,数据并不会发生变化)。
while
和 loop
循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let mut n = 0 ;while n <= 5 { println! ("{}!" , n); n = n + 1 ; } let mut n = 0 ;loop { if n > 5 { break } println! ("{}" , n); n+=1 ; }
模式匹配 match 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 match target { 模式1 => 表达式1 , 模式2 => { 语句1 ; 语句2 ; 表达式2 }, _ => 表达式3 } #[derive(Debug)] enum UsState { Alabama, Alaska, } enum Coin { Penny, Nickel, Dime, Quarter (UsState), } fn value_in_cents (coin: Coin) -> u8 { match coin { Coin::Penny => 1 , Coin::Nickel => 5 , Coin::Dime => 10 , Coin::Quarter (state) => { println! ("State quarter from {:?}!" , state); 25 }, } } enum Action { Say (String ), MoveTo (i32 , i32 ), ChangeColorRGB (u16 , u16 , u16 ), } fn main () { let actions = [ Action::Say ("Hello Rust" .to_string ()), Action::MoveTo (1 ,2 ), Action::ChangeColorRGB (255 ,255 ,0 ), ]; for action in actions { match action { Action::Say (s) => { println! ("{}" , s); }, Action::MoveTo (x, y) => { println! ("point from (0, 0) move to ({}, {})" , x, y); }, Action::ChangeColorRGB (r, g, _) => { println! ("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored" , r, g, ); } } } }
if let 匹配 1 2 3 4 5 6 7 8 let v = Some (3u8 );match v { Some (3 ) => println! ("three" ), _ => (), } if let Some (3 ) = v { println! ("three" ); }
当你只要匹配一个条件,且忽略其他条件时就用 if let ,否则都用 match。
matches!宏 1 2 3 4 5 6 7 8 enum MyEnum { Foo, Bar } fn main () { let v = vec! [MyEnum::Foo,MyEnum::Bar,MyEnum::Foo]; }
现在如果想对 v 进行过滤,只保留类型是 MyEnum::Foo 的元素,你可能想这么写:
1 v.iter ().filter (|x| x == MyEnum::Foo);
但是,实际上这行代码会报错,因为你无法将 x 直接跟一个枚举成员进行比较。改成:
1 v.iter ().filter (|x| matches!(x, MyEnum::Foo));
很简单也很简洁,再来看看更多的例子:
1 2 3 4 5 let foo = 'f' ;assert! (matches!(foo, 'A' ..='Z' | 'a' ..='z' ));let bar = Some (4 );assert! (matches!(bar, Some (x) if x > 2 ));
其他模式匹配 解构并且重命名
1 2 3 4 5 6 7 8 9 10 11 12 struct Point { x: i32 , y: i32 , } fn main () { let p = Point { x: 0 , y: 7 }; let Point { x: a, y: b } = p; assert_eq! (0 , a); assert_eq! (7 , b); }
固定某个字段:
1 2 3 4 5 6 7 8 9 fn main () { let p = Point { x: 0 , y: 7 }; match p { Point { x, y: 0 } => println! ("On the x axis at {}" , x), Point { x: 0 , y } => println! ("On the y axis at {}" , y), Point { x, y } => println! ("On neither axis: ({}, {})" , x, y), } }
匹配守卫:匹配守卫 (match guard)是一个位于 match 分支模式之后的额外 if 条件,它能为分支模式提供更进一步的匹配条件。
1 2 3 4 5 6 7 let num = Some (4 );match num { Some (x) if x < 5 => println! ("less than five: {}" , x), Some (x) => println! ("{}" , x), None => (), }
1 2 3 4 5 6 7 8 9 10 11 12 fn main () { let x = Some (5 ); let y = 10 ; match x { Some (50 ) => println! ("Got 50" ), Some (n) if n == y => println! ("Matched, n = {}" , n), _ => println! ("Default case, x = {:?}" , x), } println! ("at the end: x = {:?}, y = {}" , x, y); }
匹配守卫 if n == y 并不是一个模式所以没有引入新变量。这个 y 正是 外部的 y 而不是新的覆盖变量 y,这样就可以通过比较 n 和 y 来表达寻找一个与外部 y 相同的值的概念了。
1 2 3 4 5 6 7 let x = 4 ;let y = false ;match x { 4 | 5 | 6 if y => println! ("yes" ), _ => println! ("no" ), }
因为永远是 false,所以不会进入第一句
@绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 enum Message { Hello { id: i32 }, } let msg = Message::Hello { id: 5 };match msg { Message::Hello { id: id_variable @ 3 ..=7 } => { println! ("Found an id in range: {}" , id_variable) }, Message::Hello { id: 10 ..=12 } => { println! ("Found an id in another range" ) }, Message::Hello { id } => { println! ("Found some other id: {}" , id) }, }
@前绑定后解构(Rust 1.56 新增)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #[derive(Debug)] struct Point { x: i32 , y: i32 , } fn main () { let p @ Point {x: px, y: py } = Point {x: 10 , y: 23 }; println! ("x: {}, y: {}" , px, py); println! ("{:?}" , p); let point = Point {x: 10 , y: 5 }; if let p @ Point {x: 10 , y} = point { println! ("x is 10 and y is {} in {:?}" , y, p); } else { println! ("x was not 10 :(" ); } }
@新特性(Rust 1.53 新增)
1 2 3 4 5 6 7 8 fn main () { match 1 { num @ (1 | 2 ) => { println! ("{}" , num); } _ => {} } }