Rust 编程之道 - Rust 变量与绑定

Rust 从函数式语言中借鉴了 let 关键字建立变量。 Let 建立的变量通常称为绑定(Binding),它代表了标识符(Identifier)和值(value)之间创建的一种关联关系。数组

Rust 位置表达式和值表达式

Rust 中的表达式通常能够分为位置表达式(Place Expression)和值表达式(Value Expression)。安全

位置表达式

位置表达式即表示内存位置的表达式。分为markdown

  • 本地变量
  • 静态变量
  • 解引用(*expr)
  • 数组索引(expr[expr])
  • 字段引用(expr.field)
  • 位置表达式组合

经过位置表达式能够对某个数据单元的内存进行读写。主要是进行写操做,也就是位置表达式能够被赋值的缘由。函数

值表达式

除了位置表达式之外的表达式就是值表达式。 值表达式通常值引用了某个存储单元地址中的数据。至关于数据值,只能进行读操做。spa

从语义角度讲, 位置表达式表明了数据持久性数据,值表达式表明了临时数据。位置表达式通常有持久状态,值表达式通常为字面量或为表达式求值过郑重建立的临时值。指针

表达式的求值过程在不一样的上下文中会有不一样的结果。求值上下文也分为位置上下文(Place Context)和值上下文(Value Context)code

位置上下文

  • 赋值或者复合赋值语句左侧的操做数 a = b + c 其中 a 就是位置上下文
  • 一元表达式的独立操做数。
  • 包含隐式借用(引用)的操做数。
  • match 判别式或 let 绑定右侧在使用 ref 模式匹配的时候也是位置上下文。

除此之外都是值上下文。值表达式不能出如今位置上下文中。示例以下:orm

pub fn temp() -> i32 {
	return 1;
} 

fn main () {
	let x = &temp(); // temp 函数调用是一个无效的位置表达式
	temp() = *x; // error 报错
}
复制代码

函数 temp 的调用放到了赋值语句左边的位置上下文中,此时编译器报错,由于 temp 函数调用的是一个无效的位置表达式,它是值表达式。索引

不可绑定与可变绑定

使用 let 关键字声明的位置表达式默认不可变,为不可变绑定,简单来说,就 是 let 声明的变量不可变,相似于 JavaScript 中的 const 关键字。ip

fn main () {
	let a = 1; // a 不可变
  // a = 2; immutable and error
	let mut b = 2; // 增长可变声明
	b = 3;
}
复制代码

经过 mut 关键字,能够声明可变的位置表达式,便可变绑定,可变绑定能够正常修改和赋值。

从语义上来说,let 默认声明的不可变绑定只能对相应的存储单元进行读取,而 let mut 声明的可变绑定则是能够对相应的存储单元进行写入的。

全部权与引用

当位置表达式出如今值上下文中时,该表达式会把内存地址转移给另一个位置表达式,这实际上是全部权转移。

fn main () {
	let place1 = "hello";
	let place2 = "hello".to_string();
  let other = place1;
  println!("{:?}", other);
	let other = place2;
  println("{:?}", other) // error 
}
复制代码

上面代码中使用 let 声明了两个绑定, place1 和 place2。而后将 place1 赋值给新的变量 other。 由于 place1 是一个位置表达式,出如今了赋值操做符的右侧,即一个值上下文内,因此 place1 会将内存地址转移给 other。同理 将 place2 复制给新声明的 other ,place2 的内存地址一样会转移给 other。

第二次声明 other 将 place2 地址赋值给 other 时,会出现报错,意为该处使用了已经移动的值,之因此会出现这种区别,实际上是和底层内存安全管理有关系。这两种行为虽然有差异,都是 Rust 为了安全刻意为之。

在语义上,每一个变量绑定实际上都拥有该存储单元的全部权,这种转移内存地址的行为就是全部权(OwnerShip) 的转移,在 Rust 中移动(Move)语义,那种不转移的状况其实是一种复制(Copy)语义。Rust 无 GC,因此彻底靠全部权来进行内存管理。

在开发中,通常不须要转移全部权。Rust 提供了引用操做符(&),能够直接获取表达式的存储单元地址,即内存为止。能够经过该内存位置对内存进行读取。

fn main () {
	let a = [1,2,3];
  let b = &a;     // 获取内存地址,至关于指针
  println!("{:p}", b);

	let mut c = vec![1,2,3];
  let d = &mut c;
  d.push(4);
  println!("{:?}", d);
}
复制代码

上面代码中,b 以 & 的方式获取 a 的内存地址,这种方式不会引发全部权转移,由于使用引用操做符已经将赋值表达式右侧变成了位置上下文,它知识共享内存位置,经过 println! 宏指定{:p} 格式,能够打印 b 的指针地址,也就是内存地址。

经过 let mut 声明动态常数数组c ,经过 &mut 可获取 c 的可变引用,赋值给 d, 对于 d 的 push 操做,插入新的元素 4。要获取可变引用,必须先声明可变绑定。

不管是&a 仍是 &mut c 都至关于对于 a 和 c 全部权的借用,由于 a 和 c 还依旧保留他们的全部权,因此引用也称之为借用。

相关文章
相关标签/搜索