Return 在堆栈上分配的东西

Return something that's allocated on the stack

我有以下简化代码,其中结构 A 包含某个属性。我想从该属性的现有版本创建 A 的新实例,但如何使属性的新值的生命周期持续到函数调用之后?

pub struct A<'a> {
    some_attr: &'a str,
}

impl<'a> A<'a> {
    fn combine(orig: &'a str) -> A<'a> {
        let attr = &*(orig.to_string() + "suffix");
        A { some_attr: attr }
    }
}

fn main() {
    println!("{}", A::combine("blah").some_attr);
}

以上代码产生

error[E0597]: borrowed value does not live long enough
 --> src/main.rs:7:22
  |
7 |         let attr = &*(orig.to_string() + "suffix");
  |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not live long enough
8 |         A { some_attr: attr }
9 |     }
  |     - temporary value only lives until here
  |
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 5:1...
 --> src/main.rs:5:1
  |
5 | / impl<'a> A<'a> {
6 | |     fn combine(orig: &'a str) -> A<'a> {
7 | |         let attr = &*(orig.to_string() + "suffix");
8 | |         A { some_attr: attr }
9 | |     }
10| | }
  | |_^

这个问题之前肯定有人回答过,但我不会将其作为重复项关闭,因为这里的代码有些不同,我认为这很重要。

注意您是如何定义函数的:

fn combine(orig: &'a str) -> A<'a>

它说它将 return 类型 A 的值,其内部与提供的字符串一样长。然而,函数体违反了这个声明:

let attr = &*(orig.to_string() + "suffix");
A {
    some_attr: attr
}

这里构造一个从[=22=得到的newString,取一片然后尝试return里面A。但是,为 orig.to_string() + "suffix" 创建的隐式变量的生命周期严格小于输入参数的生命周期。因此,你的程序被拒绝了。

另一种更实用的方法是考虑由 to_string() 和连接创建的字符串必须存在于某个地方。但是,您只是 return 借来的一部分。因此,当函数退出时,字符串被销毁,returned 切片变得无效。这正是 Rust 防止的情况。

为了克服这个问题,您可以在 A:

中存储一个 String
pub struct A {
    some_attr: String
}

或者您可以使用 std::borrow::Cow 来存储切片或拥有的字符串:

pub struct A<'a> {
    some_attr: Cow<'a, str>
}

在最后一种情况下,您的函数可能如下所示:

fn combine(orig: &str) -> A<'static> {
    let attr = orig.to_owned() + "suffix";
    A {
        some_attr: attr.into()
    }
}

请注意,因为您在函数内部构造字符串,所以它表示为 Cow 的拥有变体,因此您可以使用 'static 生命周期参数作为结果值。将它绑定到 orig 也是可能的,但没有理由这样做。

使用 Cow 也可以直接从切片中创建 A 的值而无需分配:

fn new(orig: &str) -> A {
    A { some_attr: orig.into() }
}

此处 A 的生命周期参数将(通过生命周期省略)与输入字符串切片的生命周期相关联。在这种情况下,使用了 Cow 的借用变体,并且没有进行任何分配。

另请注意,最好使用to_owned()into()将字符串切片转换为Strings,因为这些方法不需要将代码格式化为运行和所以他们更有效率。

how can you return an A of lifetime 'static when you're creating it on the fly? Not sure what "owned variant of Cow" means and why that makes 'static possible.

这里是Cow的定义:

pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized {
    Borrowed(&'a B),
    Owned(B::Owned),
}

看似复杂,其实很简单。 Cow 的实例可能包含对某种类型 B 的引用,或者包含可以通过 ToOwned 特征从 B 派生的拥有值。因为 str 实现 ToOwned,其中 Owned 关联类型等于 String(写为 ToOwned<Owned = String>,当此枚举专门用于 str 时,它看起来像这样:

pub enum Cow<'a, str> {
    Borrowed(&'a str),
    Owned(String)
}

因此,Cow<str> 可能表示字符串切片或拥有的字符串 - 虽然 Cow 确实提供了写时克隆功能的方法,但它也经常用于保存可以借用或拥有以避免额外分配的值。因为 Cow<'a, B> 实现了 Deref<Target = B>,你可以通过简单的重新借用从 Cow<'a, B> 得到 &B:如果 xCow<str>,那么 &*x&str,无论 x 中包含什么 - 自然地,您可以从 Cow.

的两种变体中分出一部分

您可以看到 Cow::Owned 变体内部不包含任何引用,仅包含 String。因此,当使用 Owned 变体创建 Cow 的值时,you 可以选择任何你想要的生命周期(记住,生命周期参数很像泛型类型参数;特别是,是来电者可以选择它们) - 没有任何限制。所以选择 'static 作为最大可能的生命周期是有意义的。

Does orig.to_owned remove ownership from whoever's calling this function? That sounds like it would be inconvenient.

to_owned()方法属于ToOwned特征:

pub trait ToOwned {
    type Owned: Borrow<Self>;
    fn to_owned(&self) -> Self::Owned;
}

此特征由 str 实现,Owned 等于 Stringto_owned() 方法 return 是调用它的任何值的自有变体。在这种特殊情况下,它从 &str 中创建了一个 String,有效地将字符串切片的内容复制到新的分配中。因此,不,to_owned() 并不意味着所有权转移,它更像是意味着 "smart" 克隆。

As far as I can tell String implements Into<Vec<u8>> but not str, so how can we call into() in the 2nd example?

Into 特性非常通用,它在标准库中为许多类型实现。 Into 通常通过 From 特性实现:如果 T: From<U>,则 U: Into<T>From在标准库中有两个重要的实现:

impl<'a> From<&'a str> for Cow<'a, str>

impl<'a> From<String> for Cow<'a, str>

这些实现非常简单 - 它们只是 return Cow::Borrowed(value) 如果 value&strCow::Owned(value) 如果 valueString.

这意味着&'a strString实现了Into<Cow<'a, str>>,因此它们可以通过into()方法转换为Cow。这正是我的示例中发生的情况 - 我正在使用 into()String&str 转换为 Cow<str>。如果没有这种显式转换,您将收到有关类型不匹配的错误。