【Rust基础】Rustlings答案及解析
系列综述:
💞目的:本篇文章是个人通过Rustlings学习Rust过程中整理的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料主要源于Rustlings
进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。
🤭结语:如果有帮到你的地方,就点个赞和关注一下呗,谢谢🎈🎄🌷!!!
文章目录
-
- 使用前提
- Intro
-
- Intro.1
- Intro.2
- Variables
-
- 基本知识点
- Variables.1
- Variables.2
- Variables.3
- Variables.4
- Variables.5
- Variables.5
- Functions
-
- Functions.1
- Functions.2
- Functions.3
- Functions.4
- Functions.5
- if
-
- if.1
- If.2
- Quiz1
- Primitive_types
-
- Primitive_types.1
- Primitive_types.2
- Primitive_types.3
- Primitive_types.4
- Primitive_types.5
- Primitive_types.4
- Vecs
-
- Vecs1
- Vecs2
- Move_semantics移动语义
-
- 基本知识
- Move_semantics1
- Move_semantics2
- Move_semantics3
- Move_semantics4
- Move_semantics5
- Move_semantics6
- Structs
-
- Structs1
- Structs2
- Structs3
- Enums
-
- Enums1
- Enums2
- Enums3
- strings
-
- 切片相关知识
- strings1
- strings2
- strings3
- strings4
- Modules
-
- Modules1
- Modules2
- Modules3
- Hashmaps
-
- Hashmaps1
- Hashmaps2
- Hashmaps13
- Intro
-
- Intro.1
- Move_semantics6
- Intro
-
- Intro.1
- Move_semantics6
- Intro
-
- Intro.1
- Move_semantics6
- 参考博客
😊点此到文末惊喜↩︎
使用前提
- 配置好rust和vscode环境
- 进入rustlings的根目录
- 在命令行输入
rustlings watch
命令,进行自动检测编译模式 - 按照rustlings环境下命令进行顺序编写,编写完成后删除
// I AM NOT DONE
- 每次运行rustlings会进行重新编译,并分析进度和给出错误提示
Intro
Intro.1
- 答案:啥也不用改,直接删除// I AM NOT DONE即可
- 知识点
- 打印宏
// 打印函数 println!("Hello world");
Intro.2
-
答案
println!("Hello {}!", "World");
-
知识点
println!
是一个宏- 占位符
{}
,会被顺序替换成相应类型的参数
println!("{} World {} {}", "Hello", true, 42); //输出结果: //Hello World true 42let origin = Point { x: 10, y: 20 }; println!("origin = {}", origin) //输出结果: //origin = (10, 20)
-
更多语法可见: [rust-007]rust的println!函数的各种用法
Variables
基本知识点
- 变量声明
- 不可变绑定:
let
将值和变量名进行绑定,声明同时必须初始化
// 不可变绑定 let x = 5;// 绑定同时必须初始化 x = 10; // error: 绑定后不能改变 // 强类型的自动推导 let x = 1.000;// 编译器会推断为f32 let x : f64 = 1.000;// 类型注解
- 可变绑定:使用
mut
表示可变绑定
// 普通的mut可变 let mut x = 5; x = 10;
- 重新绑定(重影):变量名称可以被重新使用的机制
// 变量遮蔽的继承可变 let x = 5; // warning:未使用的符号 let x = 10;// 可以通过let对同一符号名进行重新绑定
- 不可变绑定:
- 注意:
- Rust 是
强类型语言
,能类型推导和进行类型安全检查 - 声明变量最好加
类型注解
,没有注解Rust会进行类型推断,可能不符合预期 - Rust处于
内存安全
考虑,不允许使用或捕获任何未初始化的变量 位置表达式 = 值表达式
,Rust没有左右值的概念,左边表达式返回内存地址,右边表达式返回值
- Rust 是
- 常量
- 常量必须加类型,因为局部的类型推断可能出现问题
- 在编译器进行了类型检查的文本替换,占用代码段或者说不占用内存空间,运行时常量值会被内联到使用的位置
const NUMBER:i32 = 3;
- 默认不可变的好处
- 代码逻辑的干净清爽。人们总是默认使用简短的不可变语法,所以使得代码逻辑更加简单
- 多线程编程中的参数传递。假如语法层面保证了一个值的不可变,就不需要锁保护
Variables.1
- 答案:声明一个变量,需要加 let,如果不声明 mut 则默认为不可变变量。
let x = 5; println!("x has the value {}", x);
Variables.2
let x: i32 = 10;// 注意初始化类型if x == 10 {println!("x is ten!");} else {println!("x is not ten!");}
Variables.3
let x: i32 = 100;// Rust的变量使用前必须绑定值println!("Number {}", x);
Variables.4
let mut x = 3;// 可变绑定println!("Number {}", x);x = 5; // don't change this lineprintln!("Number {}", x);
Variables.5
- const变量的绑定必须要有初始类型和值
const NUMBER:i32 = 3;
Variables.5
let number = "T-H-R-E-E"; // don't change this lineprintln!("Spell a Number : {}", number);let number = 3; // 重影方式,进行重新绑定println!("Number plus two is : {}", number + 2);
Functions
Functions.1
这里实际上是对于函数 call_me,有使用但是没有声明,因此我们只需要声明一下即可。
fn call_me() {}
Functions.2
函数形参必须指定类型
fn call_me(num: i32) {for i in 0..num {println!("Ring! Call number {}", i + 1);}
}
Functions.3
函数形参和实参的类型和数量必须匹配
call_me(10);
Functions.4
函数返回值使用->
进行标识
fn sale_price(price: i32) -> i32{if is_even(price) {price - 10} else {price - 3}
}
Functions.5
函数的返回值后不需要加分号
fn square(num: i32) -> i32 {num * num
}
if
if.1
Rust 的 If statement 部分并不需要加括号
pub fn bigger(a: i32, b: i32) -> i32 {// Complete this function to return the bigger number!// Do not use:// - another function call// - additional variablesif a > b {a} else {b}
}
If.2
这个需要根据下面的测试程序决定没一个输入应该输出什么字符串。
pub fn foo_if_fizz(fizzish: &str) -> &str {if fizzish == "fizz" {"foo"} else if fizzish == "fuzz" {"bar"} else {"baz"}
}
Quiz1
这个小测试实际上需要我们读懂上面的英文含义,并实现calculate_price_of_apples函数。
按照所叙述的要求,当购买数量超过 40 时,每个苹果 1 元,否则 2 元。
fn calculate_price_of_apples(cnt: i32) -> i32 {if cnt <= 40 {cnt << 1} else {cnt}
}
Primitive_types
Primitive_types.1
可以发现下面的变量 is_evening 有使用没有定义,再考虑到这里代码的语义,可以得到需要填写的代码:
let is_evening = false; // Finish the rest of this line like the example! Or make it be false!
Primitive_types.2
只需要对于 your_character 这个变量进行定义即可
let your_character = '3';
Primitive_types.3
定义一个字符串数组,数组里面每一个元素均为 “qaq”,数组总长度为 666.
let a = ["qaq"; 666];
Primitive_types.4
切片的变量实际上并不能获得地址的所有权的,仅仅是一个引用
let nice_slice = &a[1..4];// 前闭后开
Primitive_types.5
元组的使用
let (name, age) = cat;
Primitive_types.4
获得元组的某一个元素,就直接使用[元组名.index]这样的格式即可。
let numbers = (1, 2, 3);// Replace below ??? with the tuple indexing syntax.let second = numbers.1;// 元素的下标引用assert_eq!(2, second,"This is not the 2nd number in the tuple!")
Vecs
Vecs1
使用宏 vec! 来定义一个 vector
fn array_and_vec() -> ([i32; 4], Vec<i32>) {let a = [10, 20, 30, 40]; // a plain arraylet v = vec![10, 20, 30, 40];// TODO: declare your vector here with the macro for vectors(a, v)
}
Vecs2
- 遍历改变 vector 内部元素的方式
- 迭代器遍历元素
fn vec_loop(mut v: Vec<i32>) -> Vec<i32> {for i in v.iter_mut() {// TODO: Fill this up so that each element in the Vec `v` is// multiplied by 2.*i <<= 1;}// At this point, `v` should be equal to [4, 8, 12, 16, 20].v }
- map 映射:使用匿名函数进行变量的捕获和运算
fn vec_map(v: &Vec<i32>) -> Vec<i32> {v.iter().map(|num| {// TODO: Do the same thing as above - but instead of mutating the// Vec, you can just return the new number!num << 1}).collect() }
Move_semantics移动语义
基本知识
- 所有权规则
- Rust 中的每个值都有一个变量,称为其所有者。
- 每个值只能有一个所有者,但可以有多个借用者
- 值所有者的生命周期只在可用范围内,在生命范围结束时,编译器自动调用函数释放资源
- 变量生命范围
{// 在声明以前,变量 s 无效let s = "runoob";// 这里是变量 s 的可用范围 } // 变量范围已经结束,变量 s 无效
- 值传递类型
- 移动语义Move:
- 引用类型:栈中的指针变量指向堆内存,赋值时使用移动语义,赋值后原指针失效。
let s1 = String::from("hello"); let s2 = s1; // 赋值后s1失效,指向的堆内存所有权移动给s2
- 拷贝语义Clone:
- 基本数据类型存在栈中,直接赋值时拷贝语义
let x = 5; let y = x; // 拷贝语义:赋值后均有效// 堆内存的深拷贝 let s1 = String::from("hello"); let s2 = s1.clone();
- 移动语义Move:
- 函数参数所有权的传递
- 在
栈中的基本数据类型
的赋值是拷贝语义
,在堆中的引用数据类型
的赋值是移动语义
函数返回值的临时变量
可以通过移动语义
返回到函数调用处
fn main() {let s = String::from("hello");// s 被声明有效takes_ownership(s);// s 的值被当作参数传入函数// 所以可以当作 s 已经被移动,从这里开始已经无效let x = 5;// x 被声明有效makes_copy(x);// x 的值被当作参数传入函数// 但 x 是基本类型,依然有效// 在这里依然可以使用 x 却不能使用 slet s2 = String::from("hello");let s3 = takes_and_gives_back(s2);// s2 被当作参数移动, s3 获得返回值所有权 } // 函数结束, x 无效, 然后是 s. 但 s 已被移动, 所以不用被释放fn takes_ownership(some_string: String) {// 一个 String 参数 some_string 传入,有效println!("{}", some_string); } // 函数结束, 参数 some_string 在这里释放fn makes_copy(some_integer: i32) {// 一个 i32 参数 some_integer 传入,有效println!("{}", some_integer); } // 函数结束, 参数 some_integer 是基本类型, 无需释放 fn take_and_giveback(a_string:String)->String{a_string // a_string 被当作返回值移出函数 }
- 在
- 引用
- 引用是一个指向被引用对象的指针变量,是一种间接访问的方式
- 引用不会获得值的所有权,引用只能租借(Borrow)值的所有权。
- 如果被引用对象的所有权发生转移,需要重新租借
- 不能多重可变引用,为了防止写时碰撞
- Rust不允许返回局部变量的引用
// 基本引用 let s1 = String::from("hello"); let s2 = &s1; // 所有权转移引起的租借失效 let s1 = String::from("hello"); let mut s2 = &s1; let s3 = s1;// s1内存资源所有权转移到s3中,s2的租借失效 s2 = &s3; // 重新从 s3 租借所有权 // 可变租借 let mut s1 = String::from("run");// 被租借对象本身就是可变的 // s1 是可变的 let s2 = &mut s1;// 赋予租借者可变的权力 // s2 是可变的引用 let s3 = &mut s1;// error,不允许多重可变引用
Move_semantics1
vec1 在 11 行进行了修改,而我们在进行变量声明的时候并没有使用 mut 关键字,因此出现错误
let mut vec1 = fill_vec(vec0);
Move_semantics2
vec0 在进入函数之后,所有权就被传入进去并在函数结束后释放。因此在 fill_vec 之后就再也不能调用 vec0 了。这里解决的方式是通过 clone 的方式进行深拷贝,避免直接使用变量本身。
let vec0 = Vec::new();
let vec = vec0.clone();
let mut vec1 = fill_vec(vec);
Move_semantics3
问题的核心错误在于函数 fill_vec 的 vec 变量是不可变变量
fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {vec.push(22);vec.push(44);vec.push(66);vec
}
Move_semantics4
问题:函数调用形参未写,并且局部变量名称相同
fn fill_vec(vec0: Vec<i32>) -> Vec<i32> {let mut vec = vec0;
Move_semantics5
Rust1.31以后,借用的作用域的结束位置从花括号{}
变成最后一次使用的位置
fn main() {let mut x = 100;let y = &mut x;*y += 100;// 最后一次使用y,使用完成后会自动释放let z = &mut x;*z += 1000;assert_eq!(x, 1200);
}
Move_semantics6
注释提示了,第一个函数不应该有所有权,第二个函数应该获得所有权。
所以第一个函数使用借用,第二个函数使用移动语义进行直接传递
fn main() {let data = "Rust is great!".to_string();get_char(&data);string_uppercase(data);
}// Should not take ownership
fn get_char(data: &String) -> char {// 形参借用也要加&data.chars().last().unwrap()
}// Should take ownership
fn string_uppercase(mut data: String) {data = data.to_uppercase();println!("{}", data);
}
Structs
Structs1
- 每一个// TODO: 都需要改动
// 结构的声明方式
struct ColorClassicStruct {// TODO: Something goes herered: i32,green: i32,blue: i32,
}
// 元组的声明方式
struct ColorTupleStruct(u8, u8, u8);
// 结构体的初始化及绑定
// TODO: Instantiate a classic c struct!
// let green =let green = ColorClassicStruct {red: 0,green: 255,blue: 0,};
// 元组的初始化及绑带
let green = ColorTupleStruct(0, 255, 0);
// 实例化一个单元类
let unit_like_struct = UnitLikeStruct;
Structs2
按照断言补足即可
let your_order = Order {name: String::from("Hacker in Rust"),year: 2019,made_by_phone: false,made_by_mobile: false,made_by_email: true,item_number: 123,count: 1,};
Structs3
通过断言必须为真,分析代码逻辑,从而改写函数。
fn is_international(&self) -> bool {// Something goes here...self.sender_country != self.recipient_country
}fn get_fees(&self, cents_per_gram: i32) -> i32 {// Something goes here...self.weight_in_grams * cents_per_gram
}
Enums
Enums1
枚举类写法
enum Message {// TODO: define a few types of messages as used belowQuit,Echo,Move,ChangeColor,
}
Enums2
在枚举类内可以定义不同类型的元素,甚至还可以定义枚举类。
enum Message {// TODO: define the different variants used belowMove {x: i32, y: i32},Echo(String),ChangeColor(i32, i32, i32),Quit
}
Enums3
match函数和枚举的使用,match类似switch但是不需要break
// 枚举类
enum Message {// TODO: implement the message variant types based on their usage belowChangeColor((u8, u8, u8)),Echo(String),Move(Point),Quit,
}
// match函数
match message{Message::ChangeColor(t) => self.change_color(t),Message::Echo(msg) => self.echo(msg),Message::Move(point) => self.move_position(point),Message::Quit => self.quit(),
};
// 括号?
state.process(Message::ChangeColor((255, 0, 255)));
strings
切片相关知识
- String对象
- 内存结构:存储在栈上,大小固定为三个字。
- 一个指向堆中字符串的指针
- String的容量
- String的长度
- 可以根据需要调整容量,通过push_str()追加字符串
- 保证内部只保存标准的UTF-8文本
- 内存结构:存储在栈上,大小固定为三个字。
- &str
- 定义:针对字符串中的特定部分的引用
- 内存结构:栈上为
切片后字符串在堆的起始地址+长度
,堆上为被引用的字符串 - 应用场景:常用于作为只读形参
- 原则:被部分引用的变量不能通过引用进行原值的修改
切片
和双引号的字符串
都是&str类型
let s = String::from("broadcast"); let part1 = &s[0..5];// 0,1,2,3,4 // 参数只读的打印函数 fn greet(name: &str) {// 类似与const &形参println!("Hello, {}!", name); } // 切片引用不能修改原值 let mut s = String::from("runoob"); let slice = &s[0..3]; s.push_str("yes!"); // 错误
- String和&str的转换
- String 和 str 都支持切片,切片的结果是 &str 类型的数据。
let s1 = String::from("hello"); let s2 = &s1[..];// String 转换成 &str let s3 = s2.tostring();
- 切片(Slice)是对数据值的部分引用。切片变量实际为
片开始指针 + 大小
- 范围运算符
..
,其中x…y 表示 [x, y) 的数学含义..y // 等价于 0..y x.. // 等价于位置 x 到数据结束 .. // 等价于位置 0 到结束 x..y // x到y前闭后开
- 其他线性数据结构也支持切片操作,例如数组
let arr = [1, 3, 5, 7, 9]; let part = &arr[0..3]; for i in part.iter() {println!("{}", i); } ```### <font face="黑体" color=purple>strings.1</font>
strings1
简单的&str转换成String对象
fn current_favorite_color() -> String {"blue".to_string()
}
strings2
参数类型的更改
fn is_a_color_word(attempt: String) -> bool {attempt == "green" || attempt == "blue" || attempt == "red"
}
strings3
注释已经标出
fn trim_me(input: &str) -> String {// TODO: Remove whitespace from both ends of a string!input.trim().to_string() // trim()除了单词间的空格全部消除
}fn compose_me(input: &str) -> String {// TODO: Add " world!" to the string! There's multiple ways to do this!format!("{} world!", input) // 审题
}fn replace_me(input: &str) -> String {// TODO: Replace "cars" in the string with "balloons"!input.replace("cars", "balloons") // 替换单词
}
strings4
根据第一个小标题下的切片知识可以做出来
fn main() {string_slice("blue");string("red".to_string());string(String::from("hi"));string_slice("rust is fun!".to_owned());string_slice("nice weather".into());string_slice(format!("Interpolation {}", "Station"));string_slice(&String::from("abc")[0..1]);string_slice(" hello there ".trim());string("Happy Monday!".to_string().replace("Mon", "Tues"));string_slice("mY sHiFt KeY iS sTiCkY".to_lowercase());
}
Modules
Modules1
在外面使用,因此需要定义为pub
pub fn make_sausage() {get_secret_recipe();println!("sausage!");}
Modules2
给模块起别名,注意末尾的分号,并且外界需要使用就加pub
pub use self::fruits::PEAR as fruit;
pub use self::veggies::CUCUMBER as veggie;
Modules3
使用 use 关键字来引入标准库
use std::time::{SystemTime, UNIX_EPOCH};
Hashmaps
Hashmaps1
哈希表的定义
let mut basket = HashMap::new();
哈希表的插入
basket.insert(String::from("apple"), 1);
basket.insert(String::from("mango"), 2);
Hashmaps2
通过 entry 获取得到针对某个字符的记录,or_insert在哈希表中检查条目中是否已经存在参数值,没有则插入
basket.entry(fruit).or_insert(1);
Hashmaps13
与上题相同,但需要逻辑判断
let score = scores.entry(team_1_name.clone()).or_insert(Team {name: team_1_name,goals_scored: 0,goals_conceded: 0,});
(*score).goals_scored += team_1_score;
(*score).goals_conceded += team_2_score;
let score = scores.entry(team_2_name.clone()).or_insert(Team {name: team_2_name,goals_scored: 0,goals_conceded: 0,
});
(*score).goals_scored += team_2_score;
(*score).goals_conceded += team_1_score;
Intro
Intro.1
Move_semantics6
注释提示了,第一个函数不应该有所有权,第二个函数应该获得所有权。
所以第一个函数使用借用,第二个函数使用移动语义进行直接传递
fn main() {let data = "Rust is great!".to_string();get_char(&data);string_uppercase(data);
}// Should not take ownership
fn get_char(data: &String) -> char {// 形参借用也要加&data.chars().last().unwrap()
}// Should take ownership
fn string_uppercase(mut data: String) {data = data.to_uppercase();println!("{}", data);
}
Intro
Intro.1
Move_semantics6
注释提示了,第一个函数不应该有所有权,第二个函数应该获得所有权。
所以第一个函数使用借用,第二个函数使用移动语义进行直接传递
fn main() {let data = "Rust is great!".to_string();get_char(&data);string_uppercase(data);
}// Should not take ownership
fn get_char(data: &String) -> char {// 形参借用也要加&data.chars().last().unwrap()
}// Should take ownership
fn string_uppercase(mut data: String) {data = data.to_uppercase();println!("{}", data);
}
Intro
Intro.1
Move_semantics6
注释提示了,第一个函数不应该有所有权,第二个函数应该获得所有权。
所以第一个函数使用借用,第二个函数使用移动语义进行直接传递
fn main() {let data = "Rust is great!".to_string();get_char(&data);string_uppercase(data);
}// Should not take ownership
fn get_char(data: &String) -> char {// 形参借用也要加&data.chars().last().unwrap()
}// Should take ownership
fn string_uppercase(mut data: String) {data = data.to_uppercase();println!("{}", data);
}
少年,我观你骨骼清奇,颖悟绝伦,必成人中龙凤。 不如点赞·收藏·关注一波
🚩点此跳转到首行↩︎
参考博客
- Rust语言圣经(Rust Course)
- rust 的引用与借用
- Rust 学习笔记
- rustlilngs 答案
- 待定引用
- 待定引用
- 待定引用
- 待定引用