rust learning
day 1 (2021/05/27)
学了常量,变量,数据类型,控制流,所有权
-
char
的宽度是4字节,一个 unicode 的宽度 - 控制流条件都不要括号
- 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
- 所有权
- 定义非常清晰严格,在作用域结束时生命周期结束,赋值操作会发生所有权的转移,旧值变得不可用(内建类型除外)
- 同一作用域内,不允许出现两个可变引用,可以减少产生竞态条件的情况
- 不能在拥有不可变引用的同时拥有可变引用
- 切片 slice,在使用和内部实现上和 cpp 的 string_view 或者 golang 的切片都有点类似,对于函数的入参而言使用切片和原始数据类型无差异
- 结构体 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可以对你的参数进行解析,然后中间的过程你自己决定,对于参数而言,不一定要相等,我约等于也可以。
结合 match
的 enum
才算是真正发挥了设计的效果。
一个语法糖 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举个