【问题标题】:Problems with lifetime/borrow on str typestr 类型的生命周期/借用问题
【发布时间】:2020-05-01 07:45:24
【问题描述】:

为什么这段代码会编译?

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

fn main() {
    let x = "eee";
    let &m;
    {
        let y = "tttt";
        m = longest(&x, &y);
    }
    println!("ahahah: {}", m);
}

对我来说,由于生命周期,应该会出现编译错误。 如果我用i64 编写相同的代码,我会得到一个错误。

fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
    if x > y {
        x
    } else {
        y
    }
}

fn main() {
    let x = 3;
    let &m;
    {
        let y = 5;
        m = ooo(&x, &y);
    }
    println!("ahahah: {}", m);
}

错误是:

error[E0597]: `y` does not live long enough
   --> src/main.rs:103:25
    |
103 |       m = ooo(&x, &y);
    |                   ^^ borrowed value does not live long enough
104 |   }
    |   - `y` dropped here while still borrowed
105 |   println!("ahahah: {}", m);
    |                          - borrow later used here

【问题讨论】:

    标签: rust lifetime borrowing


    【解决方案1】:

    为了理解这一点,我们需要知道一些事情。首先是字符串文字的类型。任何字符串文字(如"foo")都具有&amp;'static str 类型。这是对字符串切片的引用,但此外,它是 static 引用。这种引用会在程序的整个长度内持续存在,并且可以根据需要强制到任何其他生命周期。

    这意味着在您的第一段代码中,x 和 y 已经是两个引用并且类型为 &amp;'static str。调用longest(&amp;x, &amp;y) 仍然有效的原因(即使&amp;x&amp;y 具有&amp;&amp;'static str 类型)是由于Deref coercion. longest(&amp;x, &amp;y) 确实被脱糖为longest(&amp;*x, &amp;*y) 以使类型匹配。

    让我们分析第一段代码中的生命周期。

    fn main() {
        // x: &'static str
        let x = "eee";
        // Using let patterns in a forward declaration doesn't really make sense
        // It's used for things like
        // let (x, y) = fn_that_returns_tuple();
        // or
        // let &x = fn_that_returns_reference();
        // Here, it's the same as just `let m;`.
        let &m;
        {
            // y: &'static str
            let y = "tttt";
            // This is the same as `longest(x, y)` due to autoderef
            // m: &'static str
            m = longest(&x, &y);
        }
        // `m: &static str`, so it's still valid here
        println!("ahahah: {}", m);
    }
    

    (playground)

    对于let &amp;m;,您可能意味着类似let m: &amp;str 来强制其类型。我认为这实际上确保m 中的引用的生命周期以该前向声明开始。但是因为m 无论如何都有&amp;'static str 类型,所以没关系。


    现在让我们看一下i64的第二个版本。

    fn main() {
        // x: i64
        // This is a local variable
        // and will be dropped at the end of `main`.
        let x = 3;
        // Again, this doesn't really make sense.
        let &m;
        // If we change it to `let m: &i64`, the error changes,
        // which I'll discuss below.
        {
            // Call the lifetime roughly corresponding to this block `'block`.
            // y: i64
            // This is a local variable,
            // and will be dropped at the end of the block.
            let y = 5;
            // Since `y` is local, the lifetime of the reference here
            // can't be longer than this block.
            // &y: &'block i64
            // m: &'block i64
            m = ooo(&x, &y);
        } // Now the lifetime `'block` is over.
          // So `m` has a lifetime that's over
          // so we get an error here.
        println!("ahahah: {}", m);
    }
    

    (playground)

    如果我们将m 的声明更改为let m: &amp;i64(我认为您的意思是这样),错误就会改变。

    error[E0597]: `y` does not live long enough
      --> src/main.rs:26:21
       |
    26 |         m = ooo(&x, &y);
       |                     ^^ borrowed value does not live long enough
    27 |     } // Now the lifetime `'block` is over.
       |     - `y` dropped here while still borrowed
    ...
    30 |     println!("ahahah: {}", m);
       |                            - borrow later used here
    

    (playground)

    所以现在我们明确希望m 持续与外部块一样长,但我们不能让y 持续那么久,所以错误发生在调用ooo 时。


    由于这两个程序都在处理文字,我们实际上可以编译第二个版本。为此,我们必须利用静态促销。可以在Rust 1.21 announcement(这是引入此的版本)或this question 找到对这意味着什么的一个很好的总结。

    简而言之,如果我们直接引用一个字面值,那么该引用可能会被提升为静态引用。也就是说,它不再引用局部变量。

    fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
        if x > y {
            x
        } else {
            y
        }
    }
    
    fn main() {
        // due to promotion
        // x: &'static i64
        let x = &3;
        let m;
        {
            // due to promotion
            // y: &'static i64
            let y = &5;
            // m: &'static i64
            m = ooo(x, y);
        }
        // So `m`'s lifetime is still active
        println!("ahahah: {}", m);
    }
    

    (playground)

    【讨论】:

      【解决方案2】:

      您的示例并不完全相同。字符串文字"eee" 的类型为&amp;str,而不是str,因此对应的整数代码如下所示:

      fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
          if x > y {
              x
          } else {
              y
          }
      }
      
      fn main() {
          let x = &3;
          let &m;
          {
              let y = &5;
              m = ooo(&x, &y);
          }
          println!("ahahah: {}", m);
      }
      

      它会编译。

      这个(以及&amp;str 示例)有效的原因是因为本地内联文字引用被赋予了'static 生命周期。也就是说,它们被放置在最终二进制文件的静态数据部分中,并且在程序的整个生命周期内都可以在内存中使用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-18
        • 2012-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多