the rust book 的简单入门笔记

rust learning

day 1 (2021/05/27)

学了常量,变量,数据类型,控制流,所有权

  1. char 的宽度是4字节,一个 unicode 的宽度
  2. 控制流条件都不要括号
  3. rust 中的元组使用和 c++ 中的非常相似
    // clang++ test.cpp -std=c++11 && ./a.out
    #include <iostream>
    #include <string>
    #include <tuple>
    
    int main() {
    std::tuple<int, std::string> t = std::make_tuple(12, "zxh");
    std::cout << std::get<0>(t) << " " << std::get<1>(t) << std::endl;
    
    std::string s;
    int i;
    std::tie(i, s) = std::make_tuple(1, "test-tie");
    std::cout << i << " " << s << std::endl;
    }
    
    // 输出
    12 zxh
    1 test-tie
    
  4. 所有权
    • 定义非常清晰严格,在作用域结束时生命周期结束,赋值操作会发生所有权的转移,旧值变得不可用(内建类型除外)
    • 同一作用域内,不允许出现两个可变引用,可以减少产生竞态条件的情况
    • 不能在拥有不可变引用的同时拥有可变引用
  5. 切片 slice,在使用和内部实现上和 cpp 的 string_view 或者 golang 的切片都有点类似,对于函数的入参而言使用切片和原始数据类型无差异
  6. 结构体 struct 用法和 cpp 中类似
    • 方法定义等同于 cpp
    • 关联函数的使用类似于 cpp 中的 static 函数使用
    • #[derive(Debug)] 打印整个结构和值

习题

华氏摄氏度温度互相转换

fn c_to_f(c: f32) -> f32 {
    return 1.8 * c + 32.0;
}

fn f_to_c(f: f32) -> f32 {
    return (f - 32.0) / 1.8;
}

打印斐波那契n项值

fn fibonacci(x: u32) -> u32 {
    if x == 0  {
        return 0;
    } else if x == 1 || x == 2 {
        return 1;
    } else {
        return fibonacci(x - 1) + fibonacci(x - 2);
    }
}

TODO

类型系统的转换看起来还有点不相同,像其它的语言的类型转换,在 rust 中会报错

day2 (2021/05/28)

枚举

在 cpp 中的定义

enum Color { Read, Green };

在 rust 中这样也可以工作的很好,里面的所有元素都是相同类型的,在 c 中元素类型默认为 int, cpp 中赋予了更多的类型定义。

更进一步,rust 中允许每个元素 关联不同的数据类型,如官方教程中的

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

但是既然定义为 enum,那表现的语义就应该是类型相同的,即使内部数据类型有差异,就可以这样使用了

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn foo(m: Message) {
    println!("{:?}", m)
}

fn main() {
    foo(Message::Quit);
    foo(Message::Move { x: 1, y: 2 });
    foo(Message::Write(String::from("test-enum")));
    foo(Message::ChangeColor(1, 2, 3))
}

// output:
//  Quit
//  Move { x: 1, y: 2 }
//  Write("test-enum")
//  ChangeColor(1, 2, 3)

将不同的数据类型聚集在一起来表示同一类型的语义,理论上来说是提高了抽象表达能力的;但看起来又和结构体又十分相似尤其在 cpp 中,一个父类派生出多个子类,然后可以用父类指针来表达到不同的子类上,但在 rust 这种实现用于了枚举。

Option

实现和 cpp 中的 type_traits 非常类似,在 cpp 中在编译时通过类型来匹配对应的函数。

enum Option<T> {
    None,
    Some(T),
}

对于一个函数的返回而言,如果需要返回空则直接返回 None;若是需要返回一个 i32,则返回 Some(i32)。进而函数调用后对结果进行 is_none() 的判断,如果非空,则可以使用 x.unwrap() 取出值。

再次表达和 cpp 中的 SFINAE 极其相似,看起来还有一些运行时消耗。

match

match 是一个表达式,每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。

=> 后每个分支的返回值类型必须相同,对于枚举的类型系统而言完成了一个闭环。

前面看到枚举可以由不同类型甚至携带不同的参数组成,开始我对枚举进行判断相同时的构想是这个样子的:

fn foo(m: Message) {
    if m == Message::Quit {}
    if m == Message::Write(String::from("compare")) {}
}

当然,上面的代码是行不通的,但是从这样子来看的话,类型判断还算准确,带参数的要参数才能匹配,

match 出现后,在上面相同的语义上继续放大了威力。enum 带了参数是吧,我match可以对你的参数进行解析,然后中间的过程你自己决定,对于参数而言,不一定要相等,我约等于也可以。

结合 matchenum 才算是真正发挥了设计的效果。

一个语法糖 if let 可以省略 match 表达式,match 中的分支跟在 if let 之后。

day3 (2021/05/30)

rust 包管

rust 的包管理系统都是围绕着 cargo 来进行的。有几个相关的概念.

  • 包(Packages): 源文件和描述包的 Cargo.toml 清单文件的集合。
  • Crates :一个模块的树形结构,是一个库或一个可执行程序,分别称为lib crate 或 binary crate。
  • 模块(Modules)和 use: 允许你控制作用域和路径的私有性。
  • 路径(path):一个命名例如结构体、函数或模块等项的方式

上面的话是官方的解释(The Book 和 The Cargo Book).

在 cpp 中,没有啥包的概念,只有一个命名空间的概念,粗略一看和 rust 中的方式差的远,golang 和 rust 同属现代语言,这里用 golang 做比较会好理解一些。

  • 模块路径都好理解,理解为文件系统的路径就行。一般而言模块的名称就是文件的名称,golang 中模块名为目录名。
  • ccrate 和 package 的关系为,package 可以包含至多一个 lib crate 和多个 binary crate。rust 的 crate 功能就和 golang 中的 package main 相似,可以有多个。

root 的概念

  • crate root, src/main|lib.rs 就是一个与包同名的 crate 根。通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制 crate.
  • package root, 包的 Cargo.toml 文件所在目录
  • workspace root, 工作区的 Cargo.toml 文件坐在目录

集合

vector

提供了宏的操作 vec!,作用类似 cpp 中的初始化列表在 vector 中的使用。

由于语法限制了不能多个可变引用,所以就没有办法变出这种脑淤血的代码,这种代码在 cpp 中叫做迭代器失效,rust 直接禁止这样的写法

    let mut y = vec!["z", "x", "h"];
    for v in &mut y {
        println!("{}", v);
        y.push("sb");
    }

内部实现和各种 vector 实现无异,都是 data,len,capacity 的实现,增长因子为 2.

与 cpp 比起来,多了一个 pop 操作。

字符串

rust 语言内置字符串类型:str,字符串 slice,它通常以被借用的形式出现,&str. 引用存储在其它别处的UTF-8的数据,目前这些数据为字面量字符串和String中的数据。

和 cpp 中的 const * char * 有点类似,但是提供了很多只读的操作,使用起来比较自然,并且是 unicode 而无需为操蛋的 char wchar 浪费精力。

fn read_str(s: &str) {}

read_str("string-literal");  // OK
let s = "&str"; read_str(s); // OK
let ss = String::from("String"); read_str(&ss); // OK, type coerced

对于需要使用到可变字符串的操作而言,需要使用 String, 同样是 unicode 编码,底层实现为 Vec<u8>,不过使用向量不过是方便对数据的存储,省了一遍造*的代码。

虽然是向量作为储存,和 cpp 那种 std::string 表面为字符串,实则为字符数组的的实现不同,String 不支持随机读取,

let name = String::from("123举个
上一篇:2021-05-22


下一篇:C# 调用 Rust 编写的 dll 之二:输入输出简单数值