返回在堆栈上分配的内容

wro*_*ame 6 stack lifetime rust

我有以下简化代码,其中struct 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);
}
Run Code Online (Sandbox Code Playgroud)

上面的代码产生

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| | }
  | |_^
Run Code Online (Sandbox Code Playgroud)

Vla*_*eev 13

这个问题大多肯定在之前得到了回答,但我并没有把它作为副本关闭,因为这里的代码有些不同,我认为这很重要.

注意你如何定义你的功能:

fn combine(orig: &'a str) -> A<'a>
Run Code Online (Sandbox Code Playgroud)

它表示它将返回一个类型A的值,其内部与提供的字符串完全一样长.但是,该函数的主体违反了此声明:

let attr = &*(orig.to_string() + "suffix");
A {
    some_attr: attr
}
Run Code Online (Sandbox Code Playgroud)

在这里,您构建一个新的 ,从中String获取orig,并尝试将其返回到内部A.但是,为其创建的隐式变量orig.to_string() + "suffix"的生命周期严格小于输入参数的生命周期.因此,您的计划被拒绝.

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

为了克服这个问题,您可以存储String内部A:

pub struct A {
    some_attr: String
}
Run Code Online (Sandbox Code Playgroud)

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

pub struct A<'a> {
    some_attr: Cow<'a, str>
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

使用Cow它也可以在A没有分配的情况下直接从切片中创建值:

fn new(orig: &str) -> A {
    A { some_attr: orig.into() }
}
Run Code Online (Sandbox Code Playgroud)

这里的生命周期参数A将被绑定(通过生命周期省略)到输入字符串切片的生命周期.在这种情况下,使用借用的变体Cow,并且不进行分配.

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

如果你在飞行中创建它,你怎么能回归A生命'static?不确定"拥有的变体Cow"意味着什么,以及为什么这样做'static.

这是以下定义Cow:

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

它看起来很复杂但实际上很简单.Cow可以包含对某种类型的引用B或者可以B通过该ToOwned特征派生的拥有值的实例.由于str器具ToOwned,其中Owned关联的类型等于String(写为ToOwned<Owned = String>,当此枚举是专业的str,它看起来像这样:

pub enum Cow<'a, str> {
    Borrowed(&'a str),
    Owned(String)
}
Run Code Online (Sandbox Code Playgroud)

因此,Cow<str>可以表示字符串切片或拥有的字符串 - 虽然Cow确实提供了写入时克隆功能的方法,但它通常用于保存可以借用或拥有的值,以避免额外的分配.因为Cow<'a, B>工具Deref<Target = B>,你可以&BCow<'a, B>简单的reborrowing:如果xCow<str>的话&*x就是&str,不管什么是包含内x-当然,你可以得到一个片出来的这两个变种Cow.

您可以看到该Cow::Owned变体仅包含其中的任何引用String.因此,当Cow使用Ownedvariant 创建值时,可以选择所需的任何生命周期(请记住,生命周期参数与泛型类型参数非常相似;特别是,调用者可以选择它们) - 对它没有任何限制.因此选择'static尽可能最长的生命是有意义的.

是否orig.to_owned从任何调用此函数的人中删除所有权?这听起来很不方便.

to_owned()方法属于ToOwned特质:

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

这个特点是通过实现strOwned相等String.to_owned()method返回调用它的任何值的拥有变量.在这种特殊情况下,它会创建一个String出来的&str,有效地复制串片的内容到一个新的分配.因此,to_owned()不,并不意味着所有权转移,它更像是暗示一个"聪明"的克隆.

至于我可以告诉String实现Into<Vec<u8>>但不是str,那么我们如何into()在第二个例子中调用?

Into特征非常通用,并且在标准库中实现了许多类型.Into通常通过From特征来实现:if T: From<U>,then U: Into<T>.From标准库中有两个重要的实现:

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

impl<'a> From<String> for Cow<'a, str>
Run Code Online (Sandbox Code Playgroud)

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

这意味着&'a strString实现Into<Cow<'a, str>>,因此可以将它们转换为Cowwith into()方法.这是在我的例子究竟发生了什么-我使用into()转换String&strCow<str>.如果没有这种显式转换,您将收到有关不匹配类型的错误.