本文是Rust学习笔记基础篇,主要包括Rust基础概念、所有权、枚举与模式匹配等相关内容。
环境搭建 学习资料 1 2 3 4 5 6 https: https: https: # Rust程序设计语言中文版 https: https:
搭建环境 在wsl上搭建rust的开发环境
1 curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash
下载rustlings练习
1 2 3 4 git clone -b 5.5.1 --depth 1 https://github.com/rust-lang/rustlings cd rustlingscargo install --force --path .
如果不开代理,可能速度会很慢或者出现下载失败。
git开启代理的方法:
1 2 3 4 5 6 7 8 9 Host github.com Hostname ssh.github.com Port 443 Host github.com User git ProxyCommand connect -H 127.0.0.1:4780 %h %p
其中4780是科学上网软件的代理端口。
基础编程概念 1 2 3 4 5 6 7 8 9 10 11 12 13 use std::io;fn main () { println! ("Please input your guess." ); let mut guess = String ::new (); io::stdin () .read_line (&mut guess) .expect ("Failed to read line" ); println! ("You guessed: {}" , guess); }
变量默认不可变,如果要可变,需要添加 mut 关键字修饰。
引用默认是不可变的,如果要修改,也需要写成&mut guess
变量与可变性 1 2 3 4 5 6 7 8 9 10 11 12 13 fn main () { let x = 5 ; let x = 6 ; let mut y = 1 ; y = 2 ; const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 30 ; { let x = x + 1 ; } }
数据类型 Rust是静态类型的语言,需要在编译期就知道所有变量的类型。
标量类型 字符类型
char,4个字节,表示的是一个Unicode标量值。
复合类型 将多个值组合成一个类型。
元组类型
1 2 3 4 5 fn main () { let tup : (i32 , f64 , u8 ) = (500 , 6.4 , 1 ); let (x, y, z) = tup; let a = tup.0 }
元组类型的长度是固定的。
数组类型
数组的元素类型必须是相同的。
1 2 3 4 5 6 7 fn main () { let a = [1 , 2 , 3 , 4 , 5 ]; let a : [i32 ; 5 ] = [1 , 2 , 3 , 4 , 5 ]; let a = [3 ; 5 ]; let first = a[0 ]; let ele = a[5 ]; }
函数
因为ley y = 6这个语句并不返回值。
1 2 3 4 let y = { let x = 3 ; x + 1 };
ok。注意x+1后面不带分号,它是一个表达式。表达式后面加上分号,就会转换成为语句,而语句不会返回值。
带返回值的函数
控制流 if语句
if 语句的条件必须是 bool 类型。
1 2 3 4 5 6 fn main () { let number = 3 ; if number { println! ("number was three" ); } }
if 和 else 需要有相同的值类型。
1 let number = if condition {5 } else {"six" };
loop循环
1 2 3 4 5 fn main () { loop { println! ("again!" )'' } }
一般地,break和continue作用域内部的循环,如果希望作用在外部的循环,可以在后面添加loop label。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fn main () { let mut count = 0 ; 'counting_u p' : loop { println!("count = {}", count); let mut remaining = 10; loop { println!("remaining = {}", remaining); if remaining == 9 { break; } if count == 2 { break ' counting_up'; } remaining -= 1; } rount += 1; } println!("End count = {}", count); }
带返回值的loop
1 2 3 4 5 6 7 8 let mut count = 0 ;let result = loop { count += 1 ; if counter == 10 { break counter * 2 ; } } println! ("The result is {}" , result);
带条件的循环
1 2 3 4 let mut number = 3 ;while number != 0 { num -= 1 ; }
也可以用for循环:
1 2 3 4 5 let a = [1 ,2 ,3 ,4 ,5 ];for ele in a { println! ("the value is {}" , ele); }
所有权 本节内容:
所有权让Rust无需垃圾回收期即可保证内存安全。理解了Rust的所有权,将能更自然地编写出安全和高效的代码。
什么是所有权 所有权规则
Rust中的每一个值都有一个被成为其所有者 的变量
值在任意时刻有且只有一个所有者
当所有者离开作用域时,这个值将被丢弃
变量作用域
从声明的那一刻开始直到当前作用域 结束时都是有效的。
String类型
1 2 3 4 { let s = String ::from ("hello" ); }
当s离开作用域的时候,Rust为我们调用一个特殊的函数,这个函数叫做drop。
变量与数据交互的方式一:移动
1 2 3 4 5 fn main () { let s1 = String ::from ("hello" ); let s2 = s1; println! ("{}, world!" , s1); }
结果是两个变量都绑定到同一块内存数据。且s1不再有效!
变量与数据交互的方式二:克隆
1 2 3 4 let s1 = String ::from ("hello" );let s2 = s1.clone ();println! ("s1 = {}, s2 = {}" , s1, s2);
只在栈上的数据:拷贝
任何一组简单标量的组合都可以实现Copy。如果一个类型实现了Copt trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。
1 2 3 let x = 5 ;let y = x;println! ("x = {}, y = {}" , x, y);
如果已经实现了Drop trait,就不能再使用Copy trait。
所有权与函数
将值传递给函数在语义上与给变量赋值类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fn main () { let s = String ::from ("hello" ); takes_ownership (s); let x = 5 ; makes_copy (x); } fn takes_ownership (some_string: String ) { println! ("{}" , some_string); } fn makes_copy (some_interger: i32 ) { println! ("{}" , some_interger); }
也可以返回所有权:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 fn main () { let s1 = gives_ownership (); let s2 = String ::from ("hello" ); let s3 = takes_and_gives_back (s2); } fn gives_ownership () -> String { let some_string = String ::from ("yours" ); some_string } fn takes_and_gives_back (a_string: String ) -> String { a_string }
但是这样就很麻烦了。传递一个变量,后续还想继续用,怎么办呢?Rust对此提供了一个功能,叫做引用 。
引用和借用 1 2 3 4 5 6 7 8 9 10 11 fn main () { let s1 = String ::from ("hello" ); let len = calculate_length (&s1); println! ("The length of '{}' is {}." , s1, len); } fn calculate_length (s: &String ) -> usize { s.len () }
但是,借用 别人的东西,不可以改变!
1 2 3 4 5 6 7 8 fn main () { let s = String ::from ("hello" ); change (&s); } fn change (some_string: &String ) { some_string.push_str (", world" ); }
如果要改变,要怎么办呢?
可变引用
1 2 3 fn change (some_string: &mut String ) { some_string.push_str (", world" ); }
不过可变引用有一个很大的限制:同一时间,只能由一个对某一个特定数据的可变引用。
1 2 3 4 5 let mut s = String :from ("hello" );let r1 = &mut s;let r2 = &mut s; println! ("{}, {}" , r1, r2);
这样会导致出现数据竞争。
1 2 3 4 5 6 let mut s = String :from ("hello" );{ let r1 = &mut s; } let r2 = &mut s;
也不能同时有可变与不可变引用:
1 2 3 4 5 6 7 8 9 fn main () { let mut s = String ::from ("hello" ); let r1 = &s; let r2 = &s; let r3 = &mut s; println! ("{}, {}, and {}" , r1, r2, r3); }
但是如果确定不可变引用不再使用,那也是没有问题的。
1 2 3 4 5 6 7 8 9 10 11 fn main () { let mut s = String ::from ("hello" ); let r1 = &s; let r2 = &s; println! ("{} and {}" , r1, r2); let r3 = &mut s; println! ("{}" , r3); }
悬垂引用
1 2 3 4 fn dangle () -> &String { let s = String ::from ("hello" ); &s }
为什么这里是危险的?因为引用是不会把所有权移出去的,所以s会在函数返回时被丢弃。
1 2 3 4 5 6 7 8 9 fn main () { let string = no_dangle (); } fn no_dangle () -> String { let s = String ::from ("hello" ); s }
这样确是可以的。因为s的所有权被移交出去了。
总结
在任意给定的时间,要么只能有一个可变引用,要么只能由多个不可变引用
引用必须总是有效的
引用不会传递所有权
切片 slice允许引用集合中一段连续的元素序列。slice 也没有所有权。
字符串slice
1 2 3 4 5 6 7 8 let s = String ::from ("hello world" );let hello = &s[0 ..5 ]; let world = &s[6 ..11 ]; let slice = &s[..2 ]; let slice = &s[3 ..]; s.push_str (" and more" );
返回一个slice:
1 2 3 4 5 6 7 8 fn first_world (s: &String ) -> &str { let bytes = s.as_bytes (); for (i, &item) in bytes.iter ().enumerate () { if item == b' ' { return &s[..i]; } } }
字符串字面量就是slice
这里的s的类型就是&str:指向二进制程序特定位置的slice。
其他类型的slice
1 2 let a = [1 , 2 , 3 , 4 , 5 ];let slice = &a[1 ..3 ];
枚举与模式匹配 通用概念 模式匹配(Pattern Match)是指判断一个给定的标记序列(Token Sequence)是否存在某种模式(Pattern)的结构的匹配(Match)行为。
Token Sequence:对应Rust中的表达式:在rust中几乎一切都可以视为表达式
Pattern:用来匹配表达式的模式语法,用于匹配和绑定变量
Match:用来触发模式匹配的语法结构
最终效果可以是:1. 变量绑定,2. 条件选择,3. 结构解构
枚举类型 最简单的枚举定义:
1 2 3 4 5 6 enum IpAddr { V4, V6, } let four = IpAddr::V4;
枚举也可以关联数据:
1 2 3 4 5 enum IpAddr { V4 (String ), V6 (String ), } let home = IpAddr::V4 (String ::from ("127.0.0.1" ));
还可以关联不同的类型:
1 2 3 4 5 6 7 8 9 10 11 enum IpAddr { V4 (u8 , u8 , u8 , u8 ), V6 (String ), } enum Message { Quit, Move { x: i32 , y: i32 }, Write (String ), ChangeColor (i32 , i32 , i32 ), }
枚举也可以定义方法:
1 2 3 4 5 6 7 impl Message { fn call (&self ) { } } let m = Message::Write (String ::from ("hello" ))‘m.call ();
Option枚举
Option是标准库定义的另一个枚举。
Rust中没有空值功能,但是它有一个可以编码存在或不存在概念的枚举,就是Option<T>。
1 2 3 4 enum Option <T> { Some (T), None , }
例如
1 2 let some_number = Some (5 );let absent_num : Option <i32 > = None ;
为什么 absent_num 不定义为 i32 类型? 这表示它可以为 None!这种i情况下,我们需要处理为 None 的情况。
换个角度:如果一个变量可能为空,那么我们需要把它装在Option中,这样我们就必须显式处理它为 None 的情况。
模式匹配 match表达式 1 2 3 4 5 6 7 8 9 10 enum Coin { Penny, Nickel, } fn value_in_cents (coin: Coin) -> u8 { match coin { Coin::Penny => 1 , Coin::Nickel => 5 , } }
也可以绑定值,在匹配时我们可以提取值出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #[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 {:?}" , state); 25 } } }
可以看到,在上面的模式匹配中,除了条件判断外,还实现了变量的绑定
匹配是穷尽的
这里有一个重要的概念,在Rust中,匹配的时候,我们不能只匹配部分。
1 2 3 4 5 6 fn plus_one (x: Option <i32 >) -> Option <i32 > { match x { Some (i) => Some (i+1 ), None => None , } }
通配模式和占位模式
1 2 3 4 5 6 7 8 9 let dice_roll = 9 ;match dice_roll { 3 => add_fancy_hat (), 7 => remove_fancy_hat (), other => move_player (other), } fn add_fancy_hat () {}fn remove_fancy_hat () {}fn move_player (num_spaces: u8 ) {}
1 2 3 4 5 6 7 8 9 let dice_roll = 9 ;match dice_roll { 3 => add_fancy_hat (), 7 => remove_fancy_hat (), _ => reroll (), fn add_fancy_hat () {}fn remove_fancy_hat () {}fn reroll () {}
if let 为什么要引入if let?回看match,所有分支都要处理,是不是很麻烦?如果只关心特定分支,怎么办?
match 表达式有时候就可以用 if let 来简化,看例子:
1 2 3 4 5 6 7 8 9 let some_u8_value = Some (0u8 );match some_u8_value { Some (3 ) => println! ("three" ), _ => (), } if let Some (3 ) = some_u8_value { println! ("three" ); }
与match不同,if let 后面跟的是pattern,语法:if let <pattern> = <Token Sequenc>。