您现在的位置是:首页 >技术交流 >RUST 每日一省:生命周期&作用域网站首页技术交流

RUST 每日一省:生命周期&作用域

许汶强 2023-06-18 16:00:02
简介RUST 每日一省:生命周期&作用域

生命周期

        一个变量的生命周期就是它从创建到销毁的整个过程。

作用域
        我们声明的每个变量都有作用域。作用域其实是变量和值存在的环境。作用域是由一对花括号表示的。例如,使用块表达式会创建一个作用域,即任何以花括号开头和结尾的表达式。此外,作用域支持互相嵌套,并且可以在子作用域中访问父作用域的元素,但反过来不行。

{
    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;

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。