Rust 学习笔记

Rust 学习笔记

前情摘要

使用 Rust 完成的 Linux 代码的编写将要合入 Linux 主分支的事情激励我去学习 Rust 这门语言。个人感觉 Rust 会是未来 Kernel 乃至 System 的趋势,通过使用 Cargo 进行版本管理可以有效缓解 C 语言的版本问题,同时依赖模块类似于 Go 以及 Python 的第三方库下载可以有效解决 C 的死板的头文件以及依赖库问题,这些都是 Rust 的优势所在。更重要的是,通过引入生命周期的概念使得在运行时并不需要 GC 的这种我愿意称之为“语言归约”的设计,在运行性能上必然会优于 Java 和 Python,在内存泄漏问题上优于 C++,这也是我决心学习 Rust 的原因。

Rust 的学习方式比较多,我选择通过 Rustlings 这种类似于游戏的方式来学习 Rust 的各种知识。

我将我的解答开源在 GitHub 上,欢迎大家学习与围观。

Rustlings

Intro

Intro.1

这个问题就是展示一下 Rustlings 的图标,同时展示最简单的输出宏 println!,就和 C 语言的 printf 一样,是我们学习的基础。

Intro.2

这个问题希望我们输出 “Hello World”,一个点就是在 Rust 中 println! 格式串中的 {} 将会被替换成任意参数,结果如下所示:

    println!("Hello {}!", "World");

Variables

Variables.1

声明一个变量,需要加 let,如果不声明 mut 则默认为不可变变量。

    let x = 5;
    println!("x has the value {}", x);

Variables.2

这里变量 x 需要进行初始化,注意初始化的时候需要变量类型和变量初值。

    let x: i32 = 10;
    if x == 10 {
        println!("x is ten!");
    } else {
        println!("x is not ten!");
    }

注意 Rust 的变量初始化与 C 语言并不同,格式类似于变量:类型=初值

Variables.3

在这里变量 x 并没有初始化,Rust 编译器并不允许这种未赋值就使用的行为。

    let x: i32 = 100;
    println!("Number {}", x);

Variables.4

Rust 里面如果希望改变一个变量的值,需要定义为 mut 类型。

    let mut x = 3;
    println!("Number {}", x);
    x = 5; // don't change this line
    println!("Number {}", x);

Variables.5

在 Rust 中存在影子变量这中说法,这在 C 语言中是不允许的(会触发编译错误)。
实际上,在使用影子变量之后,编译器将仅仅只能看到后面的变量。

    let number = "T-H-R-E-E"; // don't change this line
    println!("Spell a Number : {}", number);
    let number = 3; // don't rename this variable
    println!("Number plus two is : {}", number + 2);

注意使用影子变量的方法是需要用 let 关键字。

Variables.6

实际上这里是 C 语言的写法,需要使用 Rust 自己的带类型赋值方法。

const NUMBER:i32 = 3;

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

Rust 在返回值设置上,并不像 C 语言那种使用 Return Statement 来标识返回值。而是通过最后一行是否使用分号决定返回的内容。同时在函数实现时需要使用 -> type 来标注函数的返回值。
此处需要补充的就是函数的返回值类型:

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 语法和 C 语法不一样的一点,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 variables
    if 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

这里我们需要定义数组,方式如下所示:

    let a = ["qaq"; 666];

这句话的含义为定义一个字符串数组,数组里面每一个元素均为 “qaq”,数组总长度为 666.

Primitive_types.4

这里实际上是 Rust 切片的应用。说到切片,就不得不提到所有权这个概念,实际上 Rust 对于地址的所有权管理是十分严格的。切片的变量实际上并不能获得地址的所有权的,仅仅是一个引用。
根据下面的测试程序,我们可以得到下面的代码:

    let nice_slice = &a[1..4];

切片的两个下标,是左闭右开的。

Primitive_types.5

这块是元组的使用,如下所示:

    let (name, age) = cat;

Primitive_types.6

我们要获得元组的某一个元素,就直接使用[元组名.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 array
    let 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
}

这里使用 iter_mut 的原因是因为要改变数组内的元素。
第二种采用 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()
}

其实背后是用函数式编程的思想,将 num 改变为 num << 1.
总体来说我对于函数式编程没有那么熟悉,因此也更倾向于第一种的方式。

Move_semantics

所有权 (ownership) 是 rust 中非常重要的一个概念,它的本质是内存管理的一些规则。通过这一系列规则,可以保证对于内存的管理不会出现问题(传统的 Java 是通过 GC 方式来管理内存碎片的),而在编译 rust 时如果违反了这些规则,则会直接导致编译错误。

Move_semantics1

这个问题 vec1 在 11 行进行了修改,而我们在进行变量声明的时候并没有使用 mut 关键字,因此出现错误。

    let mut vec1 = fill_vec(vec0);

Move_semantics2

这个问题就出在所有权上,vec0 在进入函数之后,所有权就被传入进去并在函数结束后释放。因此在 fill_vec 之后就再也不能调用 vec0 了。这里解决的方式是通过 clone 的方式进行深拷贝,避免直接使用变量本身。

fn main() {
    let vec0 = Vec::new();
    let vec = vec0.clone();
    let mut vec1 = fill_vec(vec);

    // Do not change the following line!
    println!("{} has length {} content `{:?}`", "vec0", vec0.len(), vec0);

    vec1.push(88);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
}

Move_semantics3

这个问题的核心错误在于函数 fill_vec 的 vec 变量是不可变变量。因此我们直接将函数参数加上 mut 即可解决这个问题。

fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
    vec.push(22);
    vec.push(44);
    vec.push(66);

    vec
}

Move_semantics4

主函数中的fill_vec参数与调用的位置并不匹配,因此需要删除主函数的参数。
同时我们需要在fill_vec函数中将 vec 的声明改为一个新的变量声明。

fn main() {
    let mut vec1 = fill_vec();

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);

    vec1.push(88);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
}

// `fill_vec()` no longer takes `vec: Vec` as argument
fn fill_vec() -> Vec<i32> {
    let mut vec = Vec::new();

    vec.push(22);
    vec.push(44);
    vec.push(66);

    vec
}

Move_semantics5

按道理一个变量不应该有超过两个引用,因此在这里我们需要调整一下顺序。
将第11行和第12行交换,这样我们可以保证变量 y 先使用完先结束,变量 z 后使用完后结束。
这个例子提醒我们,rust 对于可变引用的要求,实际上是不能在同一个位置有超过两个活跃的相同变量引用。

fn main() {
    let mut x = 100;
    let y = &mut x;
    *y += 100;
    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

rust 语言的结构体定义方式类似于 go 语言,采用的是(变量名:类型)的声明方式,同时在定义结构体对象时同样采用这样的方式。

Structs1

结构体的定义如下所示:

struct ColorClassicStruct {
    // TODO: Something goes here
    red: i32,
    green: i32,
    blue: i32
}

总共有三种定义结构体实例的方式。
传统 C 语言方式:

    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

和 C 语言一致的枚举类写法。

enum Message {
    // TODO: define a few types of messages as used below
    Quit,
    Echo,
    Move,
    ChangeColor
}

Enums2

在枚举类内可以定义不同类型的元素,甚至还可以定义枚举类。

enum Message {
    // TODO: define the different variants used below
    Move {x: i32, y: i32},
    Echo(String),
    ChangeColor(i32, i32, i32),
    Quit
}

Enums3

使用 match 对元素类别进行列举。
实际上我觉得 match 是一种更加强大的 switch.

    fn process(&mut self, message: Message) {
        // TODO: create a match expression to process the different message variants
        match message {
            Message::ChangeColor(color) => {
                self.change_color(color);
            }
            Message::Echo(string) => {
                self.echo(string);
            }
            Message::Move(point) => {
                self.move_position(point);
            }
            Message::Quit => {
                self.quit();
            }
        }
    }

Strings

在 rust 中,字符串分为两种类型:strString 类型。前者一般是保存在 ELF 中的常量字符串区域,且使用时经常采用 &str 类型的借用方式,而 String 则是一种可变化、可扩展的类型。两者在 rust 中并不可以混用。
可以类比于 c 中的char*数组和 c++ 中的 string类型。

Strings1

“blue” 是一个&str类型的字符串切片,而函数需要返回的是String类型对象,因此我们需要采用to_string()这样一个构造函数转化一下。

fn current_favorite_color() -> String {
    "blue".to_string()
}

Strings2

需要将借用String类型的内容,因此需要给字符串 word 加上&.

fn main() {
    let word = String::from("green"); // Try not changing this line :)
    if is_a_color_word(&word) {
        println!("That is a color word I know!");
    } else {
        println!("That is not a color word I know.");
    }
}

Strings3

字符串相关函数在下面的官方手册。
字符串前后去掉空格采用trim函数,字符串增加采用format!宏,字符串替换采用replace函数。

fn trim_me(input: &str) -> String {
    // TODO: Remove whitespace from both ends of a string!
    input.trim().to_string()
}

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

这是一个区分strString类别的小问题。
牢记下面的一句话:如果需要对字符串进行变化,必然需要String类型,&str类型仅仅是一个“快照”。

fn main() {
    string_slice("blue");
    string("red".to_string());
    string(String::from("hi"));
    string("rust is fun!".to_owned());
    string("nice weather".into());
    string(format!("Interpolation {}", "Station"));
    string_slice(&String::from("abc")[0..1]);
    string_slice("  hello there ".trim());
    string("Happy Monday!".to_string().replace("Mon", "Tues"));
    string("mY sHiFt KeY iS sTiCkY".to_lowercase());
}

Modules

rust 的模块管理比较复杂,包括多种概念。这里我尝试去弄懂 rust 包管理这个哲学。
package 是最外层的“集装箱”,会包括相当多的 crate. 通过Cargo.toml文件来控制依赖。
crate 是可编译的最小单元,可以理解为小箱子。crate 总共分为两种类型,一种为 binary crate,另一种则为 library crate. 对于每一个 package,可以包含多个 binary crate,但是最多只能包括一个 library crate. Binary crate 文件通常包含main函数,而 library crate 则通常不包含这样的函数入口。
module 这个概念更多是为了代码的组织,当我们定义一个模块或者使用模块后,会按照模块的名字进行寻找方法。

Modules1

函数make_sausage需要在外面使用,因此需要定义为pub.

    pub fn make_sausage() {
        get_secret_recipe();
        println!("sausage!");
    }

Modules2

这里需要使用use关键字来进行重命名,同时由于外部调用因此需要声明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();// TODO: declare your hash map here.

哈系表的插入:

    // TODO: Put more fruits in your basket here.
    basket.insert(String::from("apple"), 1);
    basket.insert(String::from("mango"), 2);

Hashmaps2

哈系表中的不存在情况下增加操作采用下面的方法:

    basket.entry(fruit).or_insert(1);

Hashmaps3

这里采用和上一题一样的思路进行插入,注意字符串的所有权问题!

    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;

Quiz2

这个小 quiz 还是有一些难度的。
首先我们需要给 transformer 函数声明参数类型和返回值类型,这里传入是 Vec<(String, Command)>,返回是 Vec.
接着我们应该用 match 关键字对操作类型进行枚举。
最后主函数时引用my_module::transformer即可完成这个问题。

mod my_module {
    use super::Command;

    // TODO: Complete the function signature!
    pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
        // TODO: Complete the output declaration!
        let mut output: Vec<String> = vec![];
        for (string, command) in input.iter() {
            // TODO: Complete the function body. You can do it!
            match command {
                Command::Uppercase => {
                    output.push(string.to_uppercase());
                }
                Command::Trim => {
                    output.push(string.trim().to_string());
                }
                Command::Append(usize) => {
                    let mut ans = String::new();
                    for i in 0..*usize {
                        ans += &string.clone();
                    }
                    output.push(format!("{}bar", ans));
                }
            }
        }
        output
    }
}
    // TODO: What do we have to import to have `transformer` in scope?
    use my_module::transformer;

Options

泛型是面向对象语言的一大重要语言特性,这里我们将去使用 Rust 的泛型来处理相关任务。
Some 的设计实际上为了让 NULL 和 type 本身一块打包存在。
如下:

Some(10)
Some(20)
NULL

这三个当返回值为 Option 可以共同做为返回值返回!
这种方法可以有效增加代码编写出来的安全性!

Options1

本题按照要求实现返回值即可。

    if time_of_day > 24 {
        None
    } else if time_of_day >= 22 {
        Some(0)
    } else {
        Some(5)
    }

最后在判断时应该判 Some(5) 而并非 5.

     // TODO: Fix this test. How do you get at the value contained in the Option?
     let icecreams = maybe_icecream(12);
     assert_eq!(icecreams, Some(5));

Options2

本问题按照要求实现“褪曾”即可,if-let 和 while-let 都是非常好的模式。

    // TODO: Make this an if let statement whose value is "Some" type
    if let Some(word) = optional_target {
        assert_eq!(word, target);
    }
    // TODO: make this a while let statement - remember that vector.pop also adds another layer of Option
    // You can stack `Option`'s into while let and if let
    while let Some(Some(integer)) = optional_integers.pop() {
        assert_eq!(integer, range);
        range -= 1;
    }

这里需要两层 Some 的原因是因为 Vec 的 pop 函数会套一层 Option.

Options3

这里是需要让 match 语句不拥有所有权,而是采用借用的方式,采用 ref 关键字。

fn main() {
    let y: Option<Point> = Some(Point { x: 100, y: 200 });

    match y {
        Some(ref p) => println!("Co-ordinates are {},{} ", p.x, p.y),
        _ => println!("no match"),
    }
    y; // Fix without deleting this line.
}

你可能感兴趣的:(rust,学习,开发语言)