【Rust练习】23.生命周期

发布于:2024-11-27 ⋅ 阅读:(69) ⋅ 点赞:(0)

练习题来自https://practice-zh.course.rs/lifetime/basic.html

1

/* 为 `i` 和 `borrow2` 标注合适的生命周期范围 */


// `i` 拥有最长的生命周期,因为它的作用域完整的包含了 `borrow1` 和 `borrow2` 。
// 而 `borrow1` 和 `borrow2` 的生命周期并无关联,因为它们的作用域没有重叠
fn main() {
    let i = 3;                                             
    {                                                    
        let borrow1 = &i; // `borrow1` 生命周期开始. ──┐
        //                                                │
        println!("borrow1: {}", borrow1); //              │
    } // `borrow1` 生命周期结束. ──────────────────────────────────┘
    {                                                    
        let borrow2 = &i; 
                                                        
        println!("borrow2: {}", borrow2);               
    }                                                   
}   

我就不标注了,i的生命周期从main函数开始到结束。

2

/* 像上面的示例一样,为 `r` 和 `x` 标准生命周期,然后从生命周期的角度. */

fn main() {  
    {
        let r;                // ---------+-- 'a
                              //          |
        {                     //          |
            let x = 5;        // -+-- 'b  |
            r = &x;           //  |       |
        }                     // -+       |
                              //          |
        println!("r: {}", r); //          |
    }                         // ---------+
}

和上面的代码一样,这段代码是无法通过编译的,r的生命周期大于其引用的x。这意味着一旦x被释放,r将成为一个悬垂引用。

3

/* 添加合适的生命周期标注,让下面的代码工作 */
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {}

答案:

fn longest<'a, 'b:'a>(x:&'a str, y:&'b str) -> &'a str {
  if x.len() > y.len() {
      x
  } else {
      y
  }
}

原则1:每一个引用参数都会获得独特的生命周期
然后没有了,此时编译器无法推导出返回值引用的生命周期,这就需要我们手动标注。因为我们将返回值的生命周期标注为a,就必须让返回值的生命周期小于入参的(否则入参被释放了,就出现悬垂引用的问题了)

另外,类似的生命周期问题在C++中也是存在的:

int &larger(int &a, int &b)
{
    return a > b ? a : b;
}

int main()
{
    int a = 1;
    int& res = a;
    {
        int b = 2;
        res = larger(a, b);
    }
    cout << res << endl;
    return 0;
}

注意,C++是强类型语言,因此res的类型必须提前声明(即使用auto,那也得是auto&)。另外,C++中的引用声明时就必须初始化。

当b为更大值时,这里就会出现悬垂引用的问题。但在我编译这段代码并反复运行时,我没有看到异常输出。我的C++水平还没法解释为什么。

4

使用三种方法修复下面的错误

fn invalid_output<'a>() -> &'a String { 
  &String::from("foo") 
}

fn main() {
}

第一种当然是最原始的方法,转移所有权:

fn invalid_output() ->  String { 
  String::from("foo") 
}

新增一个入参也是不错的选择,甚至可以省略生命周期标注符号:

fn invalid_output(s: &String) -> &String {
    s
}

第三种我倒是没想到,看了答案才意识到,我也可以直接返回硬编码的&str

fn invalid_output() -> &'static str { 
    "foo"
}

5

// `print_refs` 有两个引用参数,它们的生命周期 `'a` 和 `'b` 至少得跟函数活得一样久
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("x is {} and y is {}", x, y);
}

/* 让下面的代码工作 */
fn failed_borrow<'a>() {
    let _x = 12;

    // ERROR: `_x` 活得不够久does not live long enough
    let y: &'a i32 = &_x;

    // 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小
    // 你不能将一个小的生命周期强转成大的
}

fn main() {
    let (four, nine) = (4, 9);
    

    print_refs(&four, &nine);
    // 这里,four 和 nice 的生命周期必须要比函数 print_refs 长
    
    failed_borrow();
    // `failed_borrow`  没有传入任何引用去限制生命周期 `'a`,因此,此时的 `'a` 生命周期是没有任何限制的,它默认是 `'static`
}

Rust处理这类问题的一个基本原则是:调用者的生命周期一定要比被调用的短,否则就会出现空的调用。

fn failed_borrow<'a>() {
  let _x = 12;

  // ERROR: `_x` 活得不够久does not live long enough
  let y: & i32 = &_x;

  // 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小
  // 你不能将一个小的生命周期强转成大的
}

6

/* 增加合适的生命周期标准,让代码工作 */

// `i32` 的引用必须比 `Borrowed` 活得更久
#[derive(Debug)]
struct Borrowed(&i32);

// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
#[derive(Debug)]
struct NamedBorrowed {
    x: &i32,
    y: &i32,
}

#[derive(Debug)]
enum Either {
    Num(i32),
    Ref(&i32),
}

fn main() {
    let x = 18;
    let y = 15;

    let single = Borrowed(&x);
    let double = NamedBorrowed { x: &x, y: &y };
    let reference = Either::Ref(&x);
    let number    = Either::Num(y);

    println!("x is borrowed in {:?}", single);
    println!("x and y are borrowed in {:?}", double);
    println!("x is borrowed in {:?}", reference);
    println!("y is *not* borrowed in {:?}", number);
}

我得去查查Rust有没有什么编码规范,规定了生命周期的命名。

#[derive(Debug)]
struct Borrowed<'a>(&'a i32);

// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
#[derive(Debug)]
struct NamedBorrowed<'b> {
    x: &'b i32,
    y: &'b i32,
}

#[derive(Debug)]
enum Either <'c>{
    Num(i32),
    Ref(&'c i32),
}

7

/* 让代码工作 */

#[derive(Debug)]
struct NoCopyType {}

#[derive(Debug)]
struct Example<'a, 'b> {
    a: &'a u32,
    b: &'b NoCopyType
}

fn main()
{ 
  let var_a = 35;
  let example: Example;
  
  {
    let var_b = NoCopyType {};
    
    /* 修复错误 */
    example = Example { a: &var_a, b: &var_b };
  }
  
  println!("(Success!) {:?}", example);
}

b的生命周期太短了,打印exampleb已经被释放了。

fn main()
{ 
  let var_a = 35;
  let example: Example;
  let var_b = NoCopyType {};
  {
    
    
    /* 修复错误 */
    example = Example { a: &var_a, b: &var_b };
  }
  
  println!("(Success!) {:?}", example);
}

8


#[derive(Debug)]
struct NoCopyType {}

#[derive(Debug)]
#[allow(dead_code)]
struct Example<'a, 'b> {
    a: &'a u32,
    b: &'b NoCopyType
}

/* 修复函数的签名 */
fn fix_me(foo: &Example) -> &NoCopyType
{ foo.b }

fn main()
{
    let no_copy = NoCopyType {};
    let example = Example { a: &1, b: &no_copy };
    fix_me(&example);
    println!("Success!")
}

加个生命周期标识符就行了,至于是否真能活到那时候,谁知道。

fn fix_me<'a>(foo: &'a Example) -> &'a NoCopyType
{ foo.b }

9

/* 添加合适的生命周期让下面代码工作 */
struct ImportantExcerpt {
    part: &str,
}

impl ImportantExcerpt {
    fn level(&'a self) -> i32 {
        3
    }
}

fn main() {}

答案

struct ImportantExcerpt<'a> {
  part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
  fn level(&'a self) -> i32 {
      3
  }
}

10

/* 移除所有可以消除的生命周期标注 */

fn nput<'a>(x: &'a i32) {
    println!("`annotated_input`: {}", x);
}

fn pass<'a>(x: &'a i32) -> &'a i32 { x }

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    x
}

struct Owner(i32);

impl Owner {
    fn add_one<'a>(&'a mut self) { self.0 += 1; }
    fn print<'a>(&'a self) {
        println!("`print`: {}", self.0);
    }
}

struct Person<'a> {
    age: u8,
    name: &'a str,
}

enum Either<'a> {
    Num(i32),
    Ref(&'a i32),
}

fn main() {}

由于入参只有一个引用,引用只有一个生命周期,第一个、第二个消除:

fn nput(x: &i32) {
  println!("`annotated_input`: {}", x);
}

fn pass(x: &i32) -> &i32 { x }

参数含有self时(即方法),self的生命周期会赋给全部输出,这里也消除:

struct Owner(i32);

impl Owner {
  fn add_one(&mut self) { self.0 += 1; }
  fn print(&self) {
      println!("`print`: {}", self.0);
  }
}

网站公告

今日签到

点亮在社区的每一天
去签到