> 文章列表 > 【Rust基础】Rustlings答案及解析

【Rust基础】Rustlings答案及解析

【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
    • 参考博客

😊点此到文末惊喜↩︎


使用前提

  1. 配置好rust和vscode环境
  2. 进入rustlings的根目录
  3. 在命令行输入rustlings watch命令,进行自动检测编译模式
  4. 按照rustlings环境下命令进行顺序编写,编写完成后删除// I AM NOT DONE
  5. 每次运行rustlings会进行重新编译,并分析进度和给出错误提示

Intro

Intro.1

  1. 答案:啥也不用改,直接删除// I AM NOT DONE即可
  2. 知识点
    • 打印宏
    // 打印函数
    println!("Hello world");
    

Intro.2

  1. 答案

    println!("Hello {}!", "World");
    
  2. 知识点

    • println!是一个宏
    • 占位符{},会被顺序替换成相应类型的参数
    println!("{} World {} {}", "Hello", true, 42);
    //输出结果:
    //Hello World true 42let origin = Point { x: 10, y: 20 };
    println!("origin = {}", origin)
    //输出结果:
    //origin = (10, 20)
    
  3. 更多语法可见: [rust-007]rust的println!函数的各种用法


Variables

基本知识点

  1. 变量声明
    • 不可变绑定: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对同一符号名进行重新绑定
    
  2. 注意:
    • Rust 是强类型语言,能类型推导和进行类型安全检查
    • 声明变量最好加类型注解,没有注解Rust会进行类型推断,可能不符合预期
    • Rust处于内存安全考虑,不允许使用或捕获任何未初始化的变量
    • 位置表达式 = 值表达式,Rust没有左右值的概念,左边表达式返回内存地址,右边表达式返回值
  3. 常量
    • 常量必须加类型,因为局部的类型推断可能出现问题
    • 在编译器进行了类型检查的文本替换,占用代码段或者说不占用内存空间,运行时常量值会被内联到使用的位置
    const NUMBER:i32 = 3;
    
  4. 默认不可变的好处
    • 代码逻辑的干净清爽。人们总是默认使用简短的不可变语法,所以使得代码逻辑更加简单
    • 多线程编程中的参数传递。假如语法层面保证了一个值的不可变,就不需要锁保护

Variables.1

  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

  1. 遍历改变 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移动语义

基本知识

  1. 所有权规则
    • Rust 中的每个值都有一个变量,称为其所有者。
    • 每个值只能有一个所有者,但可以有多个借用者
    • 值所有者的生命周期只在可用范围内,在生命范围结束时,编译器自动调用函数释放资源
  2. 变量生命范围
    {// 在声明以前,变量 s 无效let s = "runoob";// 这里是变量 s 的可用范围
    }
    // 变量范围已经结束,变量 s 无效
    
  3. 值传递类型
    • 移动语义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();
    
  4. 函数参数所有权的传递
    • 栈中的基本数据类型的赋值是拷贝语义,在堆中的引用数据类型的赋值是移动语义
    • 函数返回值的临时变量可以通过移动语义返回到函数调用处
    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 被当作返回值移出函数
    }
    
  5. 引用
    • 引用是一个指向被引用对象的指针变量,是一种间接访问的方式
    • 引用不会获得值的所有权,引用只能租借(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,不允许多重可变引用
    

【Rust基础】Rustlings答案及解析


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

  1. 每一个// 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

切片相关知识

  1. String对象
    • 内存结构:存储在栈上,大小固定为三个字。
      • 一个指向堆中字符串的指针
      • String的容量
      • String的长度
    • 可以根据需要调整容量,通过push_str()追加字符串
    • 保证内部只保存标准的UTF-8文本
  2. &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!"); // 错误
    
  3. String和&str的转换
    • String 和 str 都支持切片,切片的结果是 &str 类型的数据。
    
    let s1 = String::from("hello");
    let s2 = &s1[..];// String 转换成 &str
    let s3 = s2.tostring();
    
  4. 切片(Slice)是对数据值的部分引用。切片变量实际为片开始指针 + 大小
    【Rust基础】Rustlings答案及解析
  5. 范围运算符..,其中x…y 表示 [x, y) 的数学含义
    ..y 	// 等价于 0..y
    x.. 	// 等价于位置 x 到数据结束
    .. 		// 等价于位置 0 到结束
    x..y	// x到y前闭后开
    
  6. 其他线性数据结构也支持切片操作,例如数组
    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基础】Rustlings答案及解析


🚩点此跳转到首行↩︎

参考博客

  1. Rust语言圣经(Rust Course)
  2. rust 的引用与借用
  3. Rust 学习笔记
  4. rustlilngs 答案
  5. 待定引用
  6. 待定引用
  7. 待定引用
  8. 待定引用