MichaelFu

好记性不如烂笔头

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
阅读全文 »

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

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

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

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

阅读全文 »

下面是在 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) 闭包 `
阅读全文 »

Go 管理多个版本不用使用额外的版本管理器,直接使用 go install 即可。

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
ubuntu@vm-docker:~$ go install golang.org/dl/go1.16.15@latest
go: downloading golang.org/dl v0.0.0-20220315170520-faa7218da89a
ubuntu@vm-docker:~$
ubuntu@vm-docker:~$ $(go env GOPATH)/bin/go1.16.15
go1.16.15: not downloaded. Run 'go1.16.15 download' to install to /home/ubuntu/sdk/go1.16.15
ubuntu@vm-docker:~$ $(go env GOPATH)/bin/go1.16.15 download
Downloaded 0.0% ( 16384 / 99698554 bytes) ...
Downloaded 4.2% ( 4227040 / 99698554 bytes) ...
Downloaded 15.0% (14991248 / 99698554 bytes) ...
Downloaded 25.8% (25771872 / 99698554 bytes) ...
Downloaded 36.7% (36552496 / 99698554 bytes) ...
Downloaded 47.5% (47332480 / 99698554 bytes) ...
Downloaded 57.0% (56835664 / 99698554 bytes) ...
Downloaded 67.8% (67599872 / 99698554 bytes) ...
Downloaded 78.5% (78282160 / 99698554 bytes) ...
Downloaded 89.1% (88866144 / 99698554 bytes) ...
Downloaded 99.9% (99646160 / 99698554 bytes) ...
Downloaded 100.0% (99698554 / 99698554 bytes)
Unpacking /home/ubuntu/sdk/go1.16.15/go1.16.15.linux-arm64.tar.gz ...
Success. You may now run 'go1.16.15'
ubuntu@vm-docker:~$ $(go env GOPATH)/bin/go1.16.15 version
go version go1.16.15 linux/arm64
ubuntu@vm-docker:~$
ubuntu@vm-docker:~$ $(go env GOPATH)/bin/go1.16.15 env GOROOT
/home/ubuntu/sdk/go1.16.15

为啥会有这篇文章呢,因为我通过 brew 安装一个编译器太慢了,虽然 github 相关的网站没有被 GFW 屏蔽,但是现在速度依然感人,100MB 的东西我得下一天。

我在执行命令 brew install aarch64-unknown-linux-gnu 时经常中断,气得我肝疼,挂代理也不行:

好在有一台香港的服务器,根据图片中的下载地址,我先从服务器上下载,然后 scp 到本地。通过 brew --cache 命令找到 brew 下载文件时的缓存目录:

1
2
3
~/WORKDIR/gamelife1314.github.io on  source! ⌚ 18:02:53
$ brew --cache
/Users/fudenglong/Library/Caches/Homebrew

这个目录下的文件都是链接到了 "$(brew --cache)/downloads" 中,可以在这个 downloads 找到我们未下载未完成的文件,用我们下载好的文件将它替换掉,替换的时候删除后缀 .incomplete

然后重新执行命令 brew install aarch64-unknown-linux-gnu,它会从断点处重传,看到已经下载完成了,就不会再下载了。

到这里其实就安装完成了,后面还有一些自动的依赖更新,我们可以通过设置环境变量 HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 禁用这个行为。

交叉编译就是跨平台编译,例如在 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 中,工具链的安装,升级版本切换都是由 rustup 来完成的。rust 的工具链分布在三个不同的 channelstablebetanightly

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

1
2
export RUSTUP_DIST_SERVER="https://rsproxy.cn"
export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/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/

阅读全文 »

在部署k8s的时候,因为某些众所周知的原因,k8s.gcr.io 的镜像会拉取失败,本文示例一种或方式能正常拉取镜像,前提是你能科学上网,示例环境:

ubuntu@vm-docker:~/workdir$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 21.10
Release:	21.10
Codename:	impish

docker 服务创建一个内嵌的 systemd 目录:

mkdir -p /etc/systemd/system/docker.service.d

创建配置文件 /etc/systemd/system/docker.service.d/http-proxy.conf,并且写入以下内容;配置规则请看 https://docs.docker.com/network/proxy/#use-environment-variables

ubuntu@vm-docker:~/workdir$ cat /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://192.168.3.100:1087"
Environment="HTTPS_PROXY=http://192.168.3.100:1087"
Environment="NO_PROXY=localhost,127.0.0.1,https://******.mirror.aliyuncs.com"

更新配置并且重启docker:

systemctl daemon-reload && systemctl restart docker

验证配置加载成功:

ubuntu@vm-docker:~/workdir$ sudo systemctl show --property=Environment docker
Environment=HTTP_PROXY=http://192.168.3.100:1087 HTTPS_PROXY=http://192.168.3.100:1087 NO_PROXY=localhost,127.0.0.1,https://******.mirror.aliyuncs.com
ubuntu@vm-docker:~/workdir$

测试镜像拉取:

参考文章

  1. 下载k8s.gcr.io仓库的镜像的两个方式

依赖注入是一种通用技术,通过显式地为组件提供它们工作所需的所有依赖关系,生成灵活且松耦合的代码。在Go语言中,我们经常采用下面这样的方式为构造器传递依赖:

1
2
// NewUserStore returns a UserStore that uses cfg and db as dependencies.
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}

这种技术在小规模上效果很好,但较大的应用程序可能有一个复杂的依赖关系图,导致一大块初始化代码依赖于顺序。通常很难干净地分解这段代码,尤其是某些依赖项被多次使用。如果涉及到服务替换可能会更痛苦,因为它涉及通过添加一组全新的依赖项,我们需要修改依赖关系图。如果大家干过这种事情,发现这种代码的修改很繁琐。

依赖注入工具旨在简化初始化代码的管理,我们只需要将自己的服务及其依赖关系描述为代码或配置,然后依赖注入工具会处理生成的依赖关系图,确定排序并且为每个服务自动传递所需要的依赖。通过更改函数签名,添加或删除初始化程序就可以更改应用程序的依赖项,然后依赖注入完成为整个依赖关系图生成初始化代码的繁琐工作。

在Go语言中,这样依赖工具有不少,例如:diginject 以及 wire。这次我们着重介绍 wire,相对其他两个有如下优势:

  1. wire 使用代码生成而不是运行时反射。因为当依赖图变得复杂时,运行时依赖注入可能很难跟踪和调试。使用代码生成意味着在运行时执行的初始化代码是常规的、惯用的 Go 代码,易于理解和调试;

  2. wire 使用Go类型名称识别依赖项,不用像其他的服务定位器,需要为每个依赖项定义一个名称;

  3. wire 更容易避免依赖膨胀。 Wire 生成的代码只会导入需要的依赖项,因此二进制文件不会有未使用的导入。然而运行时依赖注入器直到运行时才能识别未使用的依赖项;

  4. Wire 的依赖图是静态可知的,便于工具可视化;

阅读全文 »

ABI(Application Binary Interface),即应用程序二进制接口,定义了函数调用时参数和返回值如何传递。就像C语言 x86-64 系统中,返回值保存在寄存器 %rax 中,前6个参数分别通过寄存器 %rdi%rsi%rdx%rcx%r8 以及 %r9 传递。

但是 Go 语言使用了一套跨架构通用 ABI 设计,它定义了数据在内存上的布局和函数之间的调用规约,这个调用规约是不稳定的,是会随着 Go 的版本进行变换的,称之为 ABIInternal。如果我们想开发汇编代码,应该使用稳定的 ABI0。所有原代码中定义的 Go 函数都遵循 ABIInternal,两种调用规约下的函数可以通过透明的 wrapper 相互调用。

之所以有两套调用规约,并且一个是稳定的(ABI0,承诺向后兼容),一个是不稳定的(ABIInternal,不承诺向后兼容)是因为一开始Go的调用规约约定所有的参数和返回值都通过栈传递,并且很多Go内部的包中有很多基于这个机制编写的汇编代码,例如 math/big,如果现在想升级调用规约,那么这么多汇编代码都得重写,显然不是很现实。所以,比较好的办法是引入一种新的私有约定,不承诺向后兼容,但可以在多个调用规约之间透明互调。私有的调用规约用于Go代码最终汇编的生成,稳定的调用规约用于汇编代码开发,由编译器完成两者之间的自动互调用。更多的内容可以查看 Proposal: Create an undefined internal calling convention

Go1.17 Release Notes Compiler 就对原有的调用规约做了更新,从基于栈的参数传递更新成基于寄存器,基准测试发现,性能有 5% 的提升,二进制大小减少 2%,但是 Go1.17 只在 Amd64 平台上实现了。

Go1.18 Release Notes Compiler 开始支持 GOARCH=arm64OARCH=ppc64, ppc64le。在 64ARM64PowerPC 系统上,基准测试显示性能提升 10% 或更多。

也就是说,在Go的调用规约中,我们需要遵循以下这些点:

  • 如果想写汇编代码,那么可以基于 ABI0,通过栈传递参数,汇编中使用 FP 等伪寄存器传递和访问参数以及返回值;
  • ABI0 是当前的调用约定,它在堆栈上传递参数和结果,在调用时破坏所有寄存器,并且有一些平台相关的固定寄存器;
  • ABIInternal 不稳定,可能会随版本变化。最初的时候它是与 ABI0 相同的,但 ABIInternal 为扩展提供了更多的可能性;
阅读全文 »
0%