我终于决定尝试Rust(1.7& 1.8)。来自C ++,我必须说Rust看起来很棒。我试图在C ++中重现一个众所周知的行为,包括在一组对象上使用动态多态。
我遇到了一个非常烦人的问题,我能够解决,但我想知道你们对这个问题的看法,我希望它可以帮助那些试图做同样事情的人。
让我们考虑以下实现初步想法的代码:
struct Foo
{
foo: u32,
}
trait Bar
{
fn bar(& self) -> u32;
}
impl Bar for Foo
{
fn bar(& self) -> u32
{
return self.foo;
}
}
fn foobar(l: &std::collections::LinkedList<& Bar>)
{
for i in l
{
println!("{}", i.bar());
}
}
fn main()
{
let foo0 = Foo{foo: 8u32};
let foo1 = Foo{foo: 8u32};
let mut l = std::collections::LinkedList::new();
l . push_back(&foo0 as &Bar);
l . push_back(&foo1 as &Bar);
foobar(&l);
}
这里所有东西都在编译,一切都很完美。
我想传递对特征bar
的函数Bar
的另一个引用,因此我必须为Bar
特征添加生命周期。为了简单起见,我将添加生命周期(因为它将在Rust 1.8下很好地编译)。通过整个代码添加生命周期后,代码最终看起来像这样:
struct Foo
{
foo: u32,
}
trait Bar<'a>
{
fn bar(& 'a self) -> u32;
}
impl<'a> Bar<'a> for Foo
{
fn bar(& 'a self) -> u32
{
return self.foo;
}
}
fn foobar<'a>(l: &std::collections::LinkedList<& 'a Bar>)
{
for i in l
{
println!("{}", i.bar());
}
}
fn main()
{
let foo0 = Foo{foo: 8u32};
let foo1 = Foo{foo: 8u32};
let mut l = std::collections::LinkedList::new();
l . push_back(&foo0 as &Bar);
l . push_back(&foo1 as &Bar);
foobar(&l);
}
如果您编译此代码,则错误消息如下:
../test.rs:21:12: 21:13 error: cannot infer an appropriate lifetime due to conflicting requirements [E0495]
../test.rs:21 for i in l
^
../test.rs:19:1: 25:2 help: consider using an explicit lifetime parameter as shown: fn foobar<'a>(l: &std::collections::LinkedList<&'a Bar>)
../test.rs:19 fn foobar<'a>(l: &std::collections::LinkedList<& 'a Bar>)
../test.rs:20 {
../test.rs:21 for i in l
../test.rs:22 {
../test.rs:23 println!("{}", i.bar());
../test.rs:24 }
...
error: aborting due to previous error
这里问题很明显,编译器知道push_back
中给出的变量有不同的生命周期,因此它不符合我的意思并且不同意我所写的内容。如果变量foo0
和foo1
在同一let
语句中声明,则可以解决该问题。
我发现很难弄清楚代码中出了什么问题。 我的问题是:
有没有办法告诉编译器集合可能需要不同的生命周期?
通过在另一个引用变量(此处未显示)而不是'a
上设置self
生命周期,代码将进行编译。这是否意味着如果我们没有指定任何生命周期,那么编译器会将'_
与我之前提出的问题中的特定生命周期相对应?
是否有隐含的规则&#34;禁止&#34;我们在自己身上度过一生?
这段代码是惯用的Rust吗?
答案 0 :(得分:4)
如果您将foobar
的定义更改为:
fn foobar<'a>(l: &std::collections::LinkedList<&'a Bar<'a>>) {
for i in l {
println!("{}", i.bar());
}
}
然而,你对生命周期参数所做的事情对我来说并没有多大意义。
当我们想要一个返回引用的方法时,我们通常会定义一个在生命周期内是通用的特征(例如你的Bar
的第二个版本),该引用的生命周期是实现者成员的生命周期。例如,假设我们有以下结构:
struct Foo<'a> {
foo: &'a str,
}
此结构包含引用,我们必须明确命名该生命周期。我们希望允许任何生命周期,因此我们定义了一个生命周期参数'a
。
我们可以为这个结构添加一个固有的方法:
impl<'a> Foo<'a> {
fn foo(&self) -> &'a str {
self.foo
}
}
注意&self
没有明确的生命周期。相反,我们在'a
方法的返回类型上使用impl
中的foo
参数,因为我们希望返回值的生命周期与struct中的生命周期相同,而不是self
的生命周期(通常会更短)。
如果我们想要同样定义特质方法怎么办?
trait Bar<'a> {
fn bar(&self) -> &'a str;
}
impl<'a> Bar<'a> for Foo<'a> {
fn bar(&self) -> &'a str {
self.foo
}
}
特征Bar
定义了一个生命周期参数,impl
将其链接到Foo
的生命周期参数。
您也可以在个别方法上添加生命周期参数,而不是在特征上添加生命周期参数。
例如,考虑这个特性:
trait Slice {
fn slice<'a>(&self, s: &'a str) -> &'a str;
}
我们希望结果与s
参数具有相同的生命周期。为此,我们需要在方法上定义一个生命周期参数,并将其应用于两个引用。
为了完整性,这里是该特征的实现:
struct SliceBounds {
start: usize,
end: usize,
}
impl Slice for SliceBounds {
fn slice<'a>(&self, s: &'a str) -> &'a str {
&s[self.start..self.end]
}
}