UMBRELLA

未雨绸缪,举重若轻

环境准备

以下使用 docker 准备学习环境。

  1. 先拉取centos的最新镜像:docker image pull centos
  2. 创建一个数据卷用于存放容器中产生的文件:docker volume create centos
  3. 启动我们的容器:docker run -d -it -v centos:/workdir --name centos centos /bin/bash
  4. 进入我们的容器:docker exec -it centos /bin/bash

Nginx 的主要应用场景

  1. 静态资源服务,即通过本地文件系统提供服务;
  2. 反向代理服务,提供缓存,负载均衡功能;
  3. API服务,通过Openresty直接访问数据库;
阅读全文 »

学习 rust 的第一步当然是安装,在 rust 中,工具链的安装,升级版本切换都是由 rustup 来完成的。rust 的工具链分布在三个不同的 channelstablebetanightly

可以将 rustup 看做 rust 的版本管理器,方便我们在不同的 channel 之间进行切换。 在国内 rust 的相关网站是没有被 GFW 屏蔽的,但是访问速度还是很慢。好在国内有很多镜像源,例如,我这里使用的是中国科学技术大学的镜像,配置的话只需要添加两个环境变量:

  • export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
  • export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup

rustup 的安装我们依然使用官方的方式:

curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh

执行结束之后,应该能看到下面这样的信息,而且会默认安装 nightly(每日构建)版本:

我们可以顺手配置以下 cargo 的镜像地址,参考自 中科大 Rust Crates 镜像使用帮助

~/.cargo/config
1
2
3
4
5
6
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'

[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"

也推荐的字节跳动的 Rustup 镜像和 crates.io 镜像,具体请看 https://rsproxy.cn/

阅读全文 »

交叉编译就是跨平台编译,例如在 window 下编译程序的linux版本,或者在 x86_64 平台下编译 aarch64 版本。跨平台编译在Go语言中非常方便,得益于Go语言汇编器的设计。

MacOS 交叉编译 Linux 程序

本文展示如何在 Apple M1 的平台下编译 Linux aarch64 的应用程序。

  • Apple M1

    1
    2
    3
    4
    5
    6
    7
    ~/WORKDIR/gamelife1314.github.io on  source! ⌚ 13:15:25
    $ uname -pvm
    Darwin Kernel Version 21.4.0: Fri Mar 18 00:46:32 PDT 2022; root:xnu-8020.101.4~15/RELEASE_ARM64_T6000 arm64 arm

    ~/WORKDIR/gamelife1314.github.io on  source! ⌚ 13:15:38
    $ gcc -dumpmachine
    arm64-apple-darwin21.4.0
  • linux aarch64

    1
    2
    3
    4
    5
    ubuntu@vm-docker:~$ gcc -dumpmachine
    aarch64-linux-gnu
    ubuntu@vm-docker:~$ uname -pvm
    #40-Ubuntu SMP Mon Mar 7 08:06:10 UTC 2022 aarch64 aarch64
    ubuntu@vm-docker:~$
阅读全文 »

下面是在 Rust 中会看到的类型的总结,展示了Rust的基本类型,标准库中一些非常常见的类型,以及一些用户定义类型的例子。

Type Description Values
i8, i16, i32, i64, i128 u8, u16, u32, u64, u128 给定宽度的有符号和无符号整数 42,-5i8, 0x400u16, 0o100i16, 20_922_789_888_000u64, b'*'
isize, usize 有符号整数和无符号整数, 与计算机上的地址大小相同(32位或64位) 137, -0b0101_0010isize, 0xffff_fc00usize
f32, f64 IEEE浮点数,单精度和双精度 1.61803, 3.14f32, 6.0221e23f64
bool Boolean truefalse
char Unicode字符,32位宽 '*', '\n', '字', '\x7f', '\u{CA0}'
(char, u8, i32) Tuple:允许混合类型 ('%', 0x7f, -1)
() 空元组 ()
struct S { x: f32, y: f32 } 字段带名称的复合结构 S { x: 120.0, y: 209.0 }
struct T (i32, char); Tuple-like struct T(120, 'X')
struct E; Unit-like struct; has no fields E
enum Attend { OnTime, Late(u32) } 枚举 Attend::Late(5), Attend::OnTime
Box<Attend> Box:拥有指向堆中的值的指针 Box::new(Late(15))
&i32, &mut i32 共享引用和可变引用:非拥有指针,不能比它们的引用活得更久 &s.y, &mut v
String 动态大小的UTF-8字符串 "ラーメン: ramen".to_string()
&str Reference to str: non-owning pointer to UTF-8 text "そば: soba", &s[0..12]
[f64; 4], [u8; 256] 数组,固定长度,元素同类型 [1.0, 0.0, 0.0, 1.0], [b' '; 256]
Vec<f64> 变长Vector,元素同类型 vec![0.367, 2.718, 7.389]
&[u8],&mut [u8] slice的引用:对数组或vector的一部分的引用,包括指针和长度 &v[10..20], &mut a[..]
Option<&str> 可选值,要么是 None,要么是 Some(v) Some("Dr."), None
Result<u64, Error> 可能失败的操作结果,成功就是 Ok(v),失败则是:Err(e) Ok(4096), Err(Error::last_os_error())
&dyn Any, &mut dyn Read Trait对象:引用任何实现了给定方法集的值 value as &dyn Any,&mut file as &mut dyn Read
fn(&str) -> bool 函数指针 str::is_empty
(Closure types have no written form) 闭包 `
阅读全文 »

在编程语言的内存使用中,我们经常遇到什么时候释放内存以及如何确定访问的内存是否已被释放等问题。对于内存管理方式,存在着两大阵营:

  • 一种是以 PythonJavaScriptRubyJavaC#,以及 Go 等为代表的拥有垃圾回收器的语言,垃圾回收器在对象不再被访问时,会释放对象所持有的内存。这种方式对开发者友好,因为我们不用太多关心内存的申请和释放,但是这意味着将对象释放的权利交给了垃圾回收器,对于理解什么时候释放内存会是一个较大的挑战。

  • 另一种是以 CC++ 为代表的语言,它们将内存的申请和回收完全交给了开发者,这造成过很多致命的问题,悬垂指针,访问已释放内存以及多重释放等问题;

Rust旨在既安全又高效,因此这两种方案都不能接受,但如果有更好的方案,估计早就有人做了。Rust 通过限制程序使用指针的方式打破了这种非得妥协的僵局。Rust的做法激进,但这成了它成功的基础,尽管有诸多限制,但使用起来依然足够灵活。

阅读全文 »

Rust 中,指针按是否有所有权属性可以分为两类,例如 Box<T>String,或者 Vec 具有所有权属性的指针(owning pointers),可以说它们拥有指向的内存,当它们被删除时,指向的内存也会被被释放掉。但是,也有一种非所有权指针,叫做引用(references),它们的存在不会影响指向值的生命周期,在 Rust 中创建引用的行为称之为对值的借用。

要注意的是,引用决不能超过其引用的值的生命周期。必须在代码中明确指出,任何引用都不可能超过它所指向的值的寿命。为了强调这一点,Rust 将创建对某个值的引用称为借用:你所借的东西,最终必须归还给它的所有者。

引用值

在《【Rust】所有权》章节中,我们说到函数传值会转移值得所有权,for 循环也会,例如,对下面的代码,我们在将 table 传递给 show 函数之后,table 就处于未初始化状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::collections::HashMap;
type Table = HashMap<String, Vec<String>>;

fn show(table: Table) {
for (artist, works) in table {
println!("works by {}:", artist); for work in works {
println!(" {}", work);
}
}
}


fn main() {
let mut table = Table::new();
table.insert("Gesualdo".to_string(),
vec!["many madrigals".to_string(),
"Tenebrae Responsoria".to_string()]);
table.insert("Caravaggio".to_string(),
vec!["The Musicians".to_string(),
"The Calling of St. Matthew".to_string()]);
table.insert("Cellini".to_string(),
vec!["Perseus with the head of Medusa".to_string(),
"a salt cellar".to_string()]);
show(table);
}

如果在 show 函数之后,我们再想使用 table 变量就会报错,例如:

1
2
3
...
show(table);
assert_eq!(table["Gesualdo"][0], "many madrigals");

Rust 编译器提示变量 table 已经不可用,show 函数的调用已经转移 table 的所有权:

error[E0382]: borrow of moved value: `table`
--> src/main.rs:24:16
|
13 |     let mut table = Table::new();
|         --------- move occurs because `table` has type `HashMap<String, Vec<String>>`, which does not implement the `Copy` trait
...
23 |     show(table);
|          ----- value moved here
24 |     assert_eq!(table["Gesualdo"][0], "many madrigals");
|                ^^^^^ value borrowed here after move
阅读全文 »

Rust 被称作表达式语言,在C中,ifswitch 是语句,它们不会产生值,也不能在表达式中间使用。在Rust中,ifmatch可以产生值。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
let status = if cpu.temperature <= MAX_TEMP {
HttpStatus::Ok
} else {
HttpStatus::ServerError
};

println!(
"Inside the vat, you see {}.",
match vat.contents {
Some(brain) => brain.desc(),
None => "nothing of interest",
}
);

这解释了为什么Rust没有C的三元运算符(expr1: Expr2: expr3),在 C 中,它类似 if 语句,而在 Rust 中,if 完全可以代替。另外大多数控制流在 C 中是语句,而在 Rust 中是表达式(语句都会以 ; 结束,而表达式没有)。

阅读全文 »

Rust 的错误处理方法非常不同寻常,本节介绍了 Rust 中两种不同类型的错误处理:panicResult

Panic

当程序遇到,数组越界,除0,这样很严重的bug时就会panic,在 Result 上调用 .expect() 遇到错误以及断言失败都会发生panic。还有宏 panic!(),用于在代码发现它出错是,想要直接退出。panic!() 接受可选的 println!() 样式参数,用于构建错误消息。

这些都是程序员的错,但我们都会犯错,当这些不该发生的错误发生时,Rust 可以终止进程。来看一个除0的示例:

1
2
3
4
5
6
7
8
fn main() {
pirate_share(100, 0);
}

fn pirate_share(total: u64, crew_size: usize) -> u64 {
let half = total / 2;
half / crew_size as u64
}

运行这段代码,程序会奔溃的并且打印出调用栈,还提示我们可以设置 RUST_BACKTRACE=full 获得更多的信息:

/Users/fudenglong/.cargo/bin/cargo run --color=always --package mandelbrot --bin mandelbrot
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
    Running `target/debug/mandelbrot`
thread 'main' panicked at 'attempt to divide by zero', src/main.rs:7:5
stack backtrace:
0: rust_begin_unwind
            at /rustc/4ca19e09d302a4cbde14f9cb1bc109179dc824cd/library/std/src/panicking.rs:584:5
1: core::panicking::panic_fmt
            at /rustc/4ca19e09d302a4cbde14f9cb1bc109179dc824cd/library/core/src/panicking.rs:142:14
2: core::panicking::panic
            at /rustc/4ca19e09d302a4cbde14f9cb1bc109179dc824cd/library/core/src/panicking.rs:48:5
3: mandelbrot::pirate_share
            at ./src/main.rs:7:5
4: mandelbrot::main
            at ./src/main.rs:2:5
5: core::ops::function::FnOnce::call_once
            at /rustc/4ca19e09d302a4cbde14f9cb1bc109179dc824cd/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Process finished with exit code 101

线程之间的 panic 是相互独立的,也可以调用 std::panic::catch_unwind() 捕获异常,并且让程序执行。默认发生 panic 时会展开调用栈,此外有两种情况 Rust 不会尝试展开调用栈:

  • 如果 .drop() 方法触发了第二次恐慌,而 Rust 在第一次之后仍在尝试清理,这被认为是致命的,Rust 停止展开并中止整个进程;

  • Rust 的恐慌行为是可定制的。如果使用 -C panic=abort 编译,程序中的第一个 panic 会立即中止进程。(使用这个选项,Rust 不需要知道如何展开调用栈,因此这可以减少编译代码的大小。)

阅读全文 »

Crates

Rust 程序是由 crate 组成的,每个 crate 都是一个完整的的单元:单个库或可执行文件的所有源代码,以及任何相关的测试、示例、工具、配置和其他东西。可以使用 cargo build --verbose 查看项目中使用了哪些 crates

通常项目的依赖都是配置在 Cargo.toml 文件中,例如:

1
2
3
4
[dependencies]
num = "0.4"
image = "0.13"
crossbeam = "0.8"

可以通过 cargo buildcargo install 或者 cargo add 下载依赖代码。一旦有了源代码,Cargo 就会编译所有的 crate。它为项目依赖图中的每个 crate 运行一次 rustcRust 编译器)。编译库时,Cargo 使用 --crate-type lib 选项。这告诉 rustc 不要寻找 main() 函数,而是生成一个 .rlib 文件,其中包含可用于创建二进制文件和其他 .rlib 文件的编译代码。例如:

1
rustc --crate-name num --edition=2018 /Users/fudenglong/.cargo/registry/src/mirrors.ustc.edu.cn-61ef6e0cd06fb9b8/num-0.4.0/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C split-debuginfo=unpacked -C debuginfo=2 --cfg 'feature="default"' --cfg 'feature="num-bigint"' --cfg 'feature="std"' -C metadata=b84820de50dc7f78 -C extra-filename=-b84820de50dc7f78 --out-dir /Users/fudenglong/WORKDIR/rust/mandelbrot/target/debug/deps -L dependency=/Users/fudenglong/WORKDIR/rust/mandelbrot/target/debug/deps --extern num_bigint=/Users/fudenglong/WORKDIR/rust/mandelbrot/target/debug/deps/libnum_bigint-bd772250e89d4bb9.rmeta --extern num_complex=/Users/fudenglong/WORKDIR/rust/mandelbrot/target/debug/deps/libnum_complex-d3fd80f953e1ac52.rmeta --extern num_integer=/Users/fudenglong/WORKDIR/rust/mandelbrot/target/debug/deps/libnum_integer-7ff0466209086397.rmeta --extern num_iter=/Users/fudenglong/WORKDIR/rust/mandelbrot/target/debug/deps/libnum_iter-2b149e71dbad2afc.rmeta --extern num_rational=/Users/fudenglong/WORKDIR/rust/mandelbrot/target/debug/deps/libnum_rational-1686ad6eb82c18d4.rmeta --extern num_traits=/Users/fudenglong/WORKDIR/rust/mandelbrot/target/debug/deps/libnum_traits-deaceb32c41a04f1.rmeta --cap-lints allow

对于每个 rustc 命令,Cargo 都会传递 --extern 选项,给出 crate 将使用的每个库的文件名。这样,当 rustc 看到像 use num::bigint::BigInt; 这样的代码行时,它可以确定 num 是另一个 crate 的名称,并且通过 Cargo,可以在磁盘上找到已编译的 crateRust 编译器需要访问这些 .rlib 文件,因为它们包含库的编译代码, Rust 会将该代码静态链接到最终的可执行文件中。 .rlib 还包含类型信息,因此 Rust 可以检查我们在代码中使用的库功能是否确实存在,以及我们是否正确使用它们,它还包含 crate 的公共内联函数、泛型和宏的副本等。

如果编译程序时,Cargo 使用 --crate-type bin,结果将会生成目标平台的二进制可执行文件。

阅读全文 »

Rust 中也有结构体,类似 C/C++ 中的结构体,python 中的 class 以及 javascript 中的对象。Rust 中除了常规的结构体之外,还有 tuple 结构体,单元结构体。

结构体

Rust 中约定包括结构体在内的所有类型都采用驼峰法命名,并且首字母大写,而方法和字段采用蛇形命名,即 _ 连接小写单词。例如:

1
2
3
4
5
/// A rectangle of eight-bit grayscale pixels.
struct GrayscaleMap {
pixels: Vec<u8>,
size: (usize, usize)
}

结构体初始化:

1
2
3
4
5
6
let width = 1024;
let height = 576;
let image = GrayscaleMap {
pixels: vec![0; width * height],
size: (width, height)
};

如果局部变量或者函数参数和字段名称同名,还可以省略字段名称,例如:

1
2
3
4
fn new_map(size: (usize, usize), pixels: Vec<u8>) -> GrayscaleMap {
assert_eq!(pixels.len(), size.0 * size.1);
GrayscaleMap { pixels, size }
}

字段访问采用 . 运算符:

1
2
assert_eq!(image.size, (1024, 576));
assert_eq!(image.pixels.len(), 1024 * 576);

结构体默认只能在当前模块和子模块中使用,如果想要导出结构体需要使用 pub 标识,字段也是同样的道理,如果字段都是私有的,那么只能使用类似 Vec::new 的构造方法来初始化字段:

1
2
3
4
5
/// A rectangle of eight-bit grayscale pixels.
pub struct GrayscaleMap {
pub pixels: Vec<u8>,
pub size: (usize, usize)
}

我们还可以使用相同类型的结构变量去初始化另外一个,使用 .. 运算符,自动填充未显示赋值的字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#[derive(Debug)]
struct Person {
name: String,
age: i32,
sex: char,
}

fn main() {
let p1 = Person {
name: "michael".to_string(),
age: 28,
sex: '男',
};

let p2 = Person {
name: "skye".to_string(),
..p1
};

println!("p1: {:?}, pw: {:?}", p1, p2);
}

p2 除了 name 字段是显示赋值的,其他两个字段都是来源于 p1,这段代码运行之后将输出:

p1: Person { name: "michael", age: 28, sex: '男' }, pw: Person { name: "skye", age: 28, sex: '男' }
阅读全文 »
0%