我有以下简化代码,其中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| | }
| |_^
答案 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>
:如果x
是Cow<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
等于String
。 to_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
则返回&str
,Cow::Owned(value)
为value
时返回String
。
这意味着&'a str
和String
实施Into<Cow<'a, str>>
,因此可以使用Cow
方法将其转换为into()
。这正是我的示例中发生的事情 - 我使用into()
将String
或&str
转换为Cow<str>
。如果没有这种显式转换,您将收到有关不匹配类型的错误。