Rust 学习指南 - Rust 所有权及生命周期

1,244 阅读6分钟
原文链接: www.codemore.top

在编写rust程序之前,首先必须弄清楚rust的内存管理,rust是如何保证内存安全的。

所有权
fn main() {
    let a = [1, 2, 3];
    let b = a;
    println!("{:?} {:?}", a, b); // [1, 2, 3] [1, 2, 3]
}
fn main() {
    let a = vec![1, 2, 3];
    let b = a;
    println!("{:?} {:?}", a, b); 
}

第一段代码可以正常编译运行,第二段代码在编译的时候会报错,

 --> test.rc:4:27
  |
3 |     let b = a;
  |         - value moved here
4 |     println!("{:?} {:?}", a, b); // Error; use of moved value: `a`
  |                           ^ value used here after move
  |
  = note: move occurs because `a` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.

错误提示表示,无法访问a,因为a的值已经被移动到b了,所以a无法访问,因为a未实现Copy trait所以,在使用let b = a 的时候是转移了所有权而不是copy。 每个变量绑定的时候都会绑定一个所有权,一个值只有一个变量对其有所有权,这个变量对这个值的整个生命周期负责,当其作用域结束时自动drop。当讲一个变量绑定到另一个变量或者是将其传递到函数中时(非引用)分两种情况: 1. Copy 类型,如果类型实现了copy,会对原类型进行复制,然后绑定到其他变量或者传递到其他函数。 2. Move 类型,如果未实现copy,则会将其变量的所有权转移到新的变量或者传递给其函数变量。

借用

大部分情况下,在编写程序的时候都需要将变量赋值给其他变量或者将变量传递给函数,在rust中我们可以通过引用来实现,实现的方式就是借用(borrow),即借用这个数据的使用。 借用分为两种: 1. 不可变借用(&T),一个变量可以有多个不可变的共享借用,数据无法被修改 2. 可变借用(& mut T), 一个变量仅仅可以有一个可变借用,数据可以被可变借用修改。

注意:当被数据被可变借用借用时,如果可变借用在作用域内,其原所有的变量无法被访问,只有当可变借用被drop的时候才可以被使用,可变借用和不可变借用不可共存。

fn main() {
  let mut a = vec![1, 2, 3];
  let b = &mut a;  //  &mut borrow of a starts here
  println!("{:?}", a); // trying to access a as a shared borrow, so giving error

}                  //  &mut borrow of a ends here
fn main() {
  let mut a = vec![1, 2, 3];
  {
    let b = &mut a;  //  &mut borrow of a starts here
    // any other code
  }                  //  &mut borrow of a ends here
  println!("{:?}", a); // allow to borrow a as a shared borrow
}
生命周期

在rust中,变量在同一时刻只有一个所有者。当变量不在其作用域内后,会被自动移除。当需要重用相同资源的时候,可以通过借用,暂时获取其资源值,当处理引用的时候,我们必须指定一个引用符号,告诉编译器这个引用会存活多长时间,但是因为引用符号会使得代码非常繁琐,所有大部分情况下rust编译器会自动的帮助我们推到出引用变量的生命周期,只有在编译器无法自动推到出生命周期的时候才需要我们指定引用类型的生命周期。 生命周期的检测时在编译期间进行的,编译器通过检查一个应用类型第一次和最用一次使用的时间,自动的运行时管理引用变量的生命周期,当然也正是因为这个原因所以rust在的编译非常的慢。 一般来说生命周期标志符以撇好开始,后面跟一个小写字母,例如 'a 就表示一个生命周期,一般来说通过 'a,'b,'c 来表示生命周期和其存活时间。

何时使用生命周期标识
函数声明

函数声明时,如果输入,输出有引用类型,需要手动确定其声明周期。

fn function<'a> () -> &'a str{} //返回一个引用str,其声明周期是 a

fn function<'a>(x :&'a str) {} // 输入的引用是 a
// 输入输出有相同的声明周期,返回的生命周期至少和输入相同
fn function<'a> (x :&'a str) -> &'a str{} 
// 多个输入值,返回值的生命周期至少和y相同
fn function<'a> (x: i32, y: &'a str) -> &'a str{}
// 返回值的生命周期至少和x,y相同
fn function<'a>(x: &'a str, y: &'a str) -> &'a str {}
// 多个生命周期,返回值至少和x的生命周期相同
fn function<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {}
struct和enum
// x 的生命周期至少和struct一样
struct<'a> {
	x: &'a str 
}
// Variant 的生命周期至少和enum一样
enum Enum<'a> { 
    Variant(&'a Type)
}
impl 和 trait
struct Struct<'a> { 
    x: &'a str 
}
    impl<'a> Struct<'a> { 
        fn function<'a>(&self) -> &'a str {
            self.x 
        }
    }
struct Struct<'a> { 
    x: &'a str,

    y: &'a str
}
    impl<'a> Struct<'a> { 
        fn new(x: &'a str, y: &'a str) -> Struct<'a> {
          Struct {
              x : x,
              y : y
          }
        }
    }
impl<'a> Trait<'a> for Type
impl<'a> Trait for Type<'a>
和泛型一起使用时
fn function<F> (f: F) where for<'a> F: FnOnce(&'a Type)
struct Struct<F> where for<'a> F:FnOnce(&'a Type) {x: F};
enum Enum<F> where for<'a> F:FnOnce(&'a Type) {Variant(F)}
impl<F> Struct where for <'a> F:FnOnece(&'a Type) {fn x(&self) -> &F{&self.x}}
'static 生命周期

'static 生命周期标识符是生命周期标识符的保留标识。这个标识符表示变量的生命周期在整个程序的生命周期内都可用。

static N: i32 = 5; //A constant with 'static lifetime
let a = "Hello, world."; //a: &'static str
fn index() -> &'static str { //No need to mention <'static> ; fn index ̶<̶'̶s̶t̶a̶t̶i̶c̶>̶ 
	"Hello, world!"
}
更多使用生命周期的例子
fn greeting<'a>() -> &'a str {
  "Hi!"
}

fn fullname<'a>(fname: &'a str, lname: &'a str) -> String {
  format!("{} {}", fname, lname)
}
struct Person<'a> { 
    fname: &'a str,
    lname: &'a str
}
  impl<'a> Person<'a> {
      fn new(fname: &'a str, lname: &'a str) -> Person<'a> { //no need to specify <'a> after new; impl already has it
          Person {
              fname : fname,
              lname : lname
          }
      }
      fn fullname(&self) -> String {
          format!("{} {}", self.fname , self.lname)
      }
  }
fn main() {
    let player = Person::new("Serena", "Williams");
    let player_fullname = player.fullname();
    println!("Player: {}", player_fullname);
}