生命周期
一个变量的生命周期就是它从创建到销毁的整个过程。
作用域
我们声明的每个变量都有作用域。作用域其实是变量和值存在的环境。作用域是由一对花括号表示的。例如,使用块表达式会创建一个作用域,即任何以花括号开头和结尾的表达式。此外,作用域支持互相嵌套,并且可以在子作用域中访问父作用域的元素,但反过来不行。
{
let a = "hello"; ----------+-- a的作用域
let b = 1; -----+--b |
{ | |
let c = true; ---+--c | |
| | |
---+ | |
} | |
---------+ |
--------------+
}
当作用域结束时,作用域内定义的变量都会运行相关代码以释放资源。对于在堆栈上分配的数据,可以轻松地判定变量是否存续。对在堆上分配的值, drop 方法会被放在作用域结束标记}之前调用。但这里是隐式的,可以避免程序员忘记释放值。 drop 方法来自 Drop 特征,它是为 Rust 中大部分堆分配类型实现的,可以轻松地自动释放资源。
生命周期&作用域
Rust的生命周期是基于作用域的,我们可以认为变量的生命周期就是其作用域; 编译器能自动识别作用域内这些变量的生命周期,方便进行管理。其实生命周期纯粹是一个编译期构造,它可以帮助编译器确定某个引用有效的作用域,并确保它遵循借用规则。它可以跟踪诸如引用的来源,以及它们是否比借用值生命周期更长这类事情。Rust 中的生命周期能够确保引用的存续时间不超过它指向的值。生命周期并不是作为开发人员要用到的,而是编译器使用和推断引用的有效性时会用到的。
{
let a = "hello"; ----------+-- a生命周期
let b = 1; -----+--b |
{ | |
let c = true; ---+--c | |
| | |
---+ | |
} | |
---------+ |
--------------+
}
所有权的转移
由于变量会在作用域结束时,会自动释放。如果我们需要在其作用域外继续使用它,要么转移所有权, 要么按位复制,就可以继续使用, 这取决于该变量是复制语义还是移动语义的。
如果有其他变量进入了作用域,也是会发生所有权的变化;要么转移所有权, 要么按位复制, 这取决于该变量是复制语义还是移动语义的。
{
let a = "hello".to_string();
let b = 1;
let c_out;
let d_out;
{
let c_in = 2;
let d_in = "world".to_string();
a;
b;
c_out = c_in;
d_out = d_in;
}
//println!("a:{}",a);
println!("b:{}",b);
println!("c_out:{}",c_out);
//println!("c_in:{}",c_in);
//println!("d_in:{}",d_in);
println!("d_out:{}",d_out);
}
b:1
c_out:2
d_out:world
如果我们要把打印a的注释去掉,就会产生如下错误,a进入子作用域之后,发生所有权转移,在自作用结束的时候,释放了a,所以我们继续使用就会报错了。
去掉c_in的打印,则会产生如下错误,c_in也是如此。
创建新的作用域
- 可以使用块表达式(花括号)创建作用域。
{
let a = "hello".to_string();
let b = 1;
let c_out;
let d_out;
{
let c_in = 2;
let d_in = "world".to_string();
a;
b;
c_out = c_in;
d_out = d_in;
}
//println!("a:{}",a);
println!("b:{}",b);
println!("c_out:{}",c_out);
//println!("c_in:{}",c_in);
//println!("d_in:{}",d_in);
println!("d_out:{}",d_out);
}
- match匹配也会产生一个作用域。
- for、 loop以及while循环语句均可以创建新的作用域。
- if let块和while let块也会创建新的作用域。
这三者都是类似,我们以match为例,演示一下。如果t换成是Some(6),就没有问题,因为t是Option<i32>,具有复制语义了。
fn main(){
let t = Some("test".to_string());
match t{
Some(v) => (),
None => (),
}
//println!("t:{}",t);
}
去掉注释之后,报错如下,说明t已经在Some(v)=> ()发生了所有权的转移,然后在作用域结束时被释放掉了;
- 函数体本身是独立的作用域。
由于String是移动语义,当它作为参数传入f函数后,发生了所有权转移,如果在main函数中再次调用就会发生错误。
fn f(t:String){
println!("{:?}",t);
}
fn main(){
let t = Some("test".to_string());
f(t);
//println!("{:?}",t);
}
- 闭包
闭包会创建新的作用域, 对于环境变量来说有以下三种捕获方式:
· 对于复制语义类型, 以不可变引用(&T) 来捕获。
· 对于移动语义类型, 执行移动语义(move) 转移所有权来捕获。
· 对于可变绑定, 如果在闭包中包含对其进行修改的操作, 则以可变引用(&mut) 来捕获。
fn main(){
let t = Some("test".to_string());
let c = |i: i32|{
//println!("{:?},{:?}",t,i);
t;
};
c(0);
//println!("{:?}",t);
}
这里还有一个比较有意思的地方,就是println!("{:?},{:?}",t,i);如果在闭包里只有这一句;没有t; 则不会发生所有权的转移;即println不发生所有权的转移,他表面调用的时候t,实际的参数&t;