[ Rust笔记 十一] 内存安全-所有权和移动语义

所有权

“所有权”代表着以下意义:

  1. 每个值在Rust中都有一个变量来管理它,这个变量就是这个值、这块内存的所有者;

  2. 每个值在一个时间点上只有一个管理者;

  3. 当变量所在的作用域结束的时候,变量以及它代表的值将会被销毁。

移动语义

一个变量可以把它拥有的值转移给另外一个变量,称为“所有权转移”​。赋值语句、函数调用、函数返回等,都有可能导致所有权转移。

1
2
3
4
5
6
7
8
9
10
11
fn create() -> String {
let s = String::from("hello");
return s; // 所有权转移,从函数内部移动到外部
}
fn consume(s: String) { // 所有权转移,从函数外部移动到内部
println! ("{}", s);
}
fn main() {
let s = create();
consume(s);
}

所有权转移的步骤分解如下。

  1. main函数调用create函数。

  2. 在调用create函数的时候创建了字符串,在栈上和堆上都分配有内存。局部变量s是这些内存的所有者。

  3. create函数返回的时候,需要将局部变量s移动到函数外面,这个过程就是简单地按字节复制memcpy。

  4. 同理,在调用consume函数的时候,需要将main函数中的局部变量转移到consume函数,这个过程也是简单地按字节复制memcpy。

  5. 当consume函数结束的时候,它并没有把内部的局部变量再转移出来,这种情况下,consume内部局部变量的生命周期就该结束了。这个局部变量s生命周期结束的时候,会自动释放它所拥有的内存,因此字符串也就被释放了。

Rust中所有权转移的重要特点是,它是所有类型的默认语义。这是许多读者一开始不习惯的地方。这里再重复一遍,请大家牢牢记住,Rust中的变量绑定操作,默认是move语义,执行了新的变量绑定后,原来的变量就不能再被使用!一定要记住!

Read More

[ Rust笔记 九] 基础-宏

示例

两种方式实现自定义宏:

  1. 通过标准库提供的macro_rules!宏实现

  2. 通过提供编译器扩展来实现

编译器扩展只能在不稳定版本中使用。它的API正在重新设计中,还没有正式定稿,这就是所谓的macro 2.0。

实现这样一个宏定义

1
let counts = hashmap! ['A' => 0, 'C' => 0, 'G' => 0, 'T' => 0];

实现了hashmap! {‘A’ => ‘1’}

1
2
3
4
5
6
macro_rules! hashmap {
($key: expr => $val: expr) => {
// 暂时空着
()
}
}

我们希望这个宏扩展开后的类型是HashMap,而且进行了合理的初始化,那么我们可以使用“语句块”的方式来实现:

1
2
3
4
5
6
7
8
9
macro_rules! hashmap {
($key: expr => $val: expr) => {
{
let mut map = ::std::collections::HashMap::new();
map.insert($key, $val);
map
}
}
}

现在我们希望在宏里面,可以支持重复多个这样的语法元素。我们可以使用+模式和*模式来完成。类似正则表达式的概念,+代表一个或者多个重复,*代表零个或者多个重复。因此,我们需要把需要重复的部分用括号括起来,并加上逗号分隔符:

1
2
3
4
5
6
7
macro_rules! hashmap {
($( $key: expr => $val: expr ), *) => {{
let mut map = ::std::collections::HashMap::new();
map.insert($key, $val);
map
}}
}

最后,我们在语法扩展的部分也使用*符号,将输入部分扩展为多条insert语句。最终的结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
macro_rules! hashmap {
($( $key: expr => $val: expr ), *) => {{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $val); )*
map
}}
}
fn main() {
let counts = hashmap! ['A' => 0, 'C' => 0, 'G' => 0, 'T' => 0];
println! ("{:? }", counts);
}

宏1.1

对于一些简单的宏,这种“示例型”​(by example)的方式足够使用了。但是更复杂的逻辑则需要通过更复杂的方式来实现,这就是所谓的“过程宏”​(procedural macro)​。它是直接用Rust语言写出来的,相当于一个编译器插件。但是编译器插件的最大问题是,它依赖于编译器的内部实现方式。一旦编译器内部有所变化,那么对应的宏就有可能出现编译错误,需要修改。因此,Rust中的“宏”一直难以稳定。

[ Rust笔记 七] 基础-模式解构(Pattern Destructure)

简介

1
2
let tuple = (1_i32, false, 3f32);
let (head, center, tail) = tuple;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct T1 (i32, char);
struct T2 {
item1: T1,
item2: bool,
}

fn main()
{
let x = T2 {
item1: T1(0, 'A'),
item2: false,
};
let T2 {
item1: T1(value1, value2),
item2: value3,
} = x;
println!("{} {} {}", value1, value2, value3);
}

match

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum Direction {
East, West, South, North
}
fn print(x: Direction)
{
match x {
Direction::East => {
println!("East");
}
Direction::West => {
println!("West");
}
Direction::South => {
println!("South");
}
Direction::North => {
println!("North");
}
}
}
fn main() {
let x = Direction::East;
print(x);
}

Read More

[ Rust笔记 六] 基础-数组和字符串

数组

简介

数组本身所容纳的元素个数也必须是编译期确定的,执行阶段不可变。数组类型的表示方式为[T; n]​。其中T代表元素类型;n代表元素个数。

1
2
3
4
5
6
fn main() {
// 定长数组
let xs: [i32; 5] = [1, 2, 3, 4, 5];
// 所有的元素,如果初始化为同样的数据,可以使用如下语法
let ys: [i32; 500] = [0; 500];
}

在Rust中,对于两个数组类型,只有元素类型和元素个数都完全相同,这两个数组才是同类型的。

1
2
3
4
5
6
fn main() {
let mut xs: [i32; 5] = [1, 2, 3, 4, 5];
let ys: [i32; 5] = [6, 7, 8, 9, 10];
xs = ys; //同类型,正常赋值
println!("new array {:? }", xs);
}

把数组xs作为参数传给一个函数,这个数组并不会退化成一个指针。而是会将这个数组完整复制进这个函数。函数体内对数组的改动不会影响到外面的数组。

内置方法

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let v1 = [1, 2, 3];
let v2 = [1, 2, 4];
println!("{:? }", v1 < v2 );//比较大小
}

fn main() {
let v = [0_i32; 10];
for i in &v {//遍历切片
println!("{:? }", i);
}
}

数组本身没有实现IntoIterator trait,但是数组切片是实现了的。所以我们可以直接在for in循环中使用数组切片,而不能直接使用数组本身。

多维数组

1
2
3
4
5
6
fn main() {
let v : [[i32; 2]; 3] = [[0, 0], [0, 0], [0, 0]];
for i in &v {
println!("{:? }", i);
}
}

数组切片

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
fn mut_array(a : &mut [i32]) {
a[2] = 5;
}
println!("size of &[i32; 3] : {:? }", std::mem::size_of::<&[i32; 3]>());
println!("size of &[i32] : {:? }", std::mem::size_of::<&[i32]>());
let mut v : [i32; 3] = [1,2,3];
{
let s : &mut [i32; 3] = &mut v;
mut_array(s);
}
println!("{:? }", v);
}

DST和胖指针

Slice与普通的指针是不同的,它有一个非常形象的名字:胖指针(fat pointer)​。与这个概念相对应的概念是“动态大小类型”​(Dynamic Sized Type, DST)​。所谓的DST指的是编译阶段无法确定占用空间大小的类型。为了安全性,指向DST的指针一般是胖指针。

Read More

[ Rust笔记 五] 基础-trait

成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait Shape {
fn area(&self) -> f64;//关联函数,参数被称为接收者
}

trait T {
fn method1(self: Self);
fn method2(self: &Self);
fn method3(self: &mut Self);
}
// 上下两种写法是完全一样的
trait T {
fn method1(self);
fn method2(&self);
fn method3(&mut self);
}

具有receiver参数的函数,我们称为“方法”​(method)​,可以通过变量实例使用小数点来调用。
没有receiver参数的函数,我们称为“静态函数”​(static function)​,可以通过类型加双冒号::的方式来调用。

Rust中Self(大写S)和self(小写s)都是关键字,大写S的是类型名,小写s的是变量名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Circle {
radius: f64,
}
impl Shape for Circle {
// Self 类型就是 Circle
// self 的类型是 &Self,即 &Circle
fn area(&self) -> f64 {
// 访问成员变量,需要用 self.radius
std::f64::consts::PI * self.radius * self.radius
}
}
fn main() {
let c = Circle { radius : 2f64};
// 第一个参数名字是 self,可以使用小数点语法调用
println!("The area is {}", c.area());
}

另一种写法“内在方法”

1
2
3
impl Circle {
fn get_radius(&self) -> f64 { self.radius }
}

trait中可以包含方法的默认实现。如果这个方法在trait中已经有了方法体,那么在针对具体类型实现的时候,就可以选择不用重写。

Read More

[ Rust笔记 四] 基础-函数

简介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   fn add1(t : (i32, i32)) -> i32 { //模式解构
t.0 + t.1
}

fn add2((x, y) : (i32, i32)) -> i32 {//和上述写法一样
x + y
}


fn main() {
// 先让 func 指向 add1
let mut func = add1;
// 再重新赋值,让 func 指向 add2
func = add2;
}

会报错,类型不一致。每个函数都有自己得类型。

1
2
3
4
// 写法一,用 as 类型转换
let mut func = add1 as fn((i32, i32))->i32;
// 写法二,用显式类型标记
let mut func : fn((i32, i32))->i32 = add1;

Rust的函数体内也允许定义其他item,包括静态变量、常量、函数、trait、类型、模块等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn test_inner() {
static INNER_STATIC: i64 = 42;
// 函数内部定义的函数
fn internal_incr(x: i64) -> i64 {
x + 1
}
struct InnerTemp(i64);
impl InnerTemp {
fn incr(&mut self) {
self.0 = internal_incr(self.0);
}
}
// 函数体,执行语句
let mut t = InnerTemp(INNER_STATIC);
t.incr();
println!("{}", t.0);
}

发散函数

1
2
3
4
5
6
7
8
9
10
11
12
fn diverges() -> ! {
panic! ("This function never returns! ");
}

let x : i32 = diverges();
let y : String = diverges();

let p = if x {
panic!("error");
} else {
100
};

可以被转换成任意类型

Read More

[ Rust笔记 三] 基础-复合数据类型

tuple

1
2
3
4
5
6
7
8
9
10
11
   let a = (1i32, false);        // 元组中包含两个元素,第一个是i32类型,第二个是bool类型
let b = ("a", (1i32, 2i32)); // 元组中包含两个元素,第二个元素本身也是元组,它又包含了两个元素
let a = (0, ); // a是一个元组,它有一个元素
let b = (0); // b是一个括号表达式,它是i32类型

let p = (1i32, 2i32);
let (a, b) = p;

let x = p.0;
let y = p.1;
println!("{} {} {} {}", a, b, x, y);

元组内部也可以一个元素都没有。这个类型单独有一个名字,叫unit(单元类型)​:

1
let empty : () = ();

unit类型是Rust中最简单的类型之一,也是占用空间最小的类型之一。空元组和空结构体struct Foo;一样,都是占用0内存空间。

1
2
3
4
5
fn main() {
println!("size of i8 {}" , std::mem::size_of::<i8>());
println!("size of char {}" , std::mem::size_of::<char>());
println!("size of '()' {}" , std::mem::size_of::<()>());
}

struct

1
2
3
4
struct Point {
x: i32,
y: i32,
}

初始化,类json语法初始化

1
2
3
4
fn main() {
let p = Point { x: 0, y: 0};
println!("Point is at {} {}", p.x, p.y);
}

使用局部变量初始化

1
2
3
4
5
6
7
8
fn main() {
// 刚好局部变量名字和结构体成员名字一致
let x = 10;
let y = 20;
// 下面是简略写法,等同于 Point { x: x, y: y },同名字的相对应
let p = Point { x, y };
println!("Point is at {} {}", p.x, p.y);
}

使用语法糖赋值

1
2
3
4
5
6
7
8
9
10
11
12
struct Point3d {
x: i32,
y: i32,
z: i32,
}
fn default() -> Point3d {
Point3d { x: 0, y: 0, z: 0 }
}
// 可以使用default()函数初始化其他的元素
// ..expr 这样的语法,只能放在初始化表达式中,所有成员的最后最多只能有一个
let origin = Point3d { x: 5, ..default()};
let point = Point3d { z: 1, x: 2, ..origin };

Read More

[ Rust笔记 二] 基础-基本类型

bool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  fn main() {
let x = true;
let y: bool = !x; // 取反运算
let z = x && y; // 逻辑与,带短路功能
println!("{}", z);
let z = x || y; // 逻辑或,带短路功能
println!("{}", z);
let z = x & y; // 按位与,不带短路功能
println!("{}", z);
let z = x | y; // 按位或,不带短路功能
println!("{}", z);
let z = x ^ y; // 按位异或,不带短路功能
println!("{}", z);
}

char

1
2
3
4
5
6
7
8
9
10
11
12
13
   unicode
都是4个字节
let love = '❤'; // 可以直接嵌入任何unicode字符
转义符
let c1 = '\n'; // 换行符
let c2 = '\x7f'; // 8 bit字符变量
let c3 = '\u{7FFF}'; // unicode字符

都是1个字节
let x :u8 = 1;
let y :u8 = b'A';
let s :&[u8;5] = b"hello";
let r :&[u8;14] = br#"hello \n world"#;

整数类型

i8 u8, i16 u16 … i128, u128, isize, usize

1
2
3
4
5
6
7
8
9
let var1 : i32 = 32;      // 十进制表示
let var2 : i32 = 0xFF; // 以0x开头代表十六进制表示
let var3 : i32 = 0o55; // 以0o开头代表八进制表示
let var4 : i32 = 0b1001; // 以0b开头代表二进制表示
let var6 = 123usize; // i6变量是usize类型
let var7 = 0x_ff_u8; // i7变量是u8类型
let var8 = 32; // 不写类型,默认为 i32 类型

let var5 = 0x_1234_ABCD; // 使用下划线分割数字,不影响语义,但是极大地提升了阅读体验。

(pointer size)isize和usize占用字节数和32位或者64位系统有关系

浮点类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   let f1 = 123.0f64;         // type f64
let f2 = 0.1f64; // type f64
let f3 = 0.1f32; // type f32
let f4 = 12E+99_f64; // type f64 科学计数法
let f5 : f64 = 2.; // type f64


enum FpCategory {
Nan,
Infinite,
Zero,
Subnormal,
Normal,
}

Read More

[ Rust笔记 一] 基础-变量与类型

变量

1
2
3
4
5
6
let variable : i32 = 100;

fn main() {
let x = 5;
x = 10;
}

这段代码会报错,变量是只读的。mut关键字申明的变量才是可写的。

1
2
let mut x = 5; // mut x: i32
x = 10;

mut x是一个“模式”​,我们还可以用这种方式同时声明多个变量

1
2
let (mut a, mut b) = (1, 2);
let Point { x: ref a, y: ref b} = p;

Rust中,每个变量必须被合理初始化之后才能被使用。
编译器会帮我们做一个执行路径的静态分析,确保变量在使用前一定被初始化:

1
2
3
4
5
6
7
8
9
fn test(condition: bool) {
let x: i32; // 声明 x,不必使用 mut 修饰
if condition {
x = 1; // 初始化 x,不需要 x 是 mut 的,因为这是初始化,不是修改
println!("{}", x);
}
// 如果条件不满足,x 没有被初始化
// 但是没关系,只要这里不使用 x 就没事
}

Read More

Nacos Naming Server源码分析-NamingGrpcClientProxy启动阶段

NamingGrpcClientProxy是Nacos SDK中,Grpc调用的主要实现类。话不多说,看一下类图:

NamingGrpcClientProxy

该类的启动过程

NamingGrpcClientProxy#NamingGrpcClientProxy
->start()
->RpcClient#start()

构造函数逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public NamingGrpcClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListFactory serverListFactory,
NacosClientProperties properties, ServiceInfoHolder serviceInfoHolder) throws NacosException {
super(securityProxy); //客户端鉴权
this.namespaceId = namespaceId;
this.uuid = UUID.randomUUID().toString();
this.requestTimeout = Long.parseLong(properties.getProperty(CommonParams.NAMING_REQUEST_TIMEOUT, "-1"));
Map<String, String> labels = new HashMap<>();
labels.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK);
labels.put(RemoteConstants.LABEL_MODULE, RemoteConstants.LABEL_MODULE_NAMING);
labels.put(Constants.APPNAME, AppNameUtils.getAppName());
this.rpcClient = RpcClientFactory.createClient(uuid, ConnectionType.GRPC, labels,
RpcClientTlsConfig.properties(properties.asProperties())); //创建grpc客户端 GrpcSdkClient,最终初始化RpcClient。并将客户端状态设置为INITIALIZED
this.redoService = new NamingGrpcRedoService(this, properties); //创建redoService
NAMING_LOGGER.info("Create naming rpc client for uuid->{}", uuid);
start(serverListFactory, serviceInfoHolder); //继续往下看
}

Read More