返回在堆栈上分配的内容

时间:2015-09-16 08:51:01

标签: stack rust lifetime

我有以下简化代码,其中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);
}

以上代码生成

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| | }
  | |_^

1 个答案:

答案 0 :(得分:13)

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

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

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

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

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

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

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

要解决此问题,您可以在String内存储A

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()将字符串切片转换为String,因为这些方法不需要运行格式代码,因此效率更高。

  

当您即时创建时,如何返回A生命周期'static?不确定&#34;拥有Cow&#34;的变体?意味着什么使'static成为可能。

以下是Cow的定义:

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

看起来很复杂,但实际上很简单。 Cow的实例可以包含对某些类型B的引用,也可以包含可以通过B特征从ToOwned派生的拥有值。由于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>,您可以通过简单的重新提供从&B获得Cow<'a, B>:如果xCow<str>,那么&*x&str,无论x中包含什么内容 - 当然,您可以从Cow的两种变体中获得切片。

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

  

orig.to_owned是否会删除任何人调用此功能的所有权?这听起来很不方便。

to_owned()方法属于ToOwned特征:

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

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

  

据我所知,String实现Into<Vec<u8>>而不是str,那么我们如何在第二个例子中调用into()

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>

这些实现非常简单 - 如果Cow::Borrowed(value)value则返回&strCow::Owned(value)value时返回String

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