我能否将本地变量和对它们的引用转移给返回的迭代器来实现所有权转移?

3

我正在使用一个带有返回盒式迭代器的方法的特质。由于迭代器将使用selffoo的参数,所有这些都受到相同生命周期的约束:

pub trait Foo {
    fn foo<'a>(&'a self, txt: &'a str) -> Box<Iterator<Item = String> + 'a>;
}

我想围绕这个方法构建一个函数:
fn foo_int<'a, F: Foo>(f: &'a F, val: i32) -> impl Iterator<Item = String> + 'a {
    let txt = format!("{}", val);
    f.foo(&txt)
}

但是这段代码无法编译,因为:
error[E0515]: cannot return value referencing local variable `txt`
 --> src/lib.rs:7:5
  |
7 |     f.foo(&txt)
  |     ^^^^^^----^
  |     |     |
  |     |     `txt` is borrowed here
  |     returns a value referencing data owned by the current function

我理解为什么会发生这种情况,这很合理,但似乎应该有一种方法来避免它。毕竟,这就是闭包(使用“move”关键字)所做的事情:它们拥有它们需要“带走”的值的所有权。
是否有一种聪明的方法可以使用闭包或其他东西重写“foo_int”函数?

1
你的 foo 方法对 str 做了什么?如果它需要一个与 f 一样长寿命的字符串,那么接受 String 而不是 &str 是否更有意义? - loganfsmyth
为了论证,假设我在这里无法控制特质Foo。(完全透明:在我的实际情况中,我确实有控制权,因此我可能会考虑您的解决方案...但我预计将来会遇到其他人的特质类似问题) - Pierre-Antoine
如果特质要求txt的生命周期与self一样长,那么在foo_int内部创建一个新字符串并使其满足该生命周期是不可能的。根据定义,f在调用foo_int时已经存在,因此在foo_int中无法做任何事情来创建具有该生命周期的内容。 - loganfsmyth
在这种情况下,唯一满足约束条件的 &str 就是一个 &'static str - loganfsmyth
1
毕竟,这就是闭包所做的事情——闭包有完全相同的问题。 - Shepmaster
显示剩余4条评论
1个回答

1

实际上,在制作我的最小示例时,我想出了一种(有点)解决问题的方法。它在playground中可用。

这个想法是将foo的参数和返回的迭代器包装在一个专用类型中,该类型通过委托内部迭代器来实现Iterator特性。

pub struct Bar<'a>(String, Box<Iterator<Item = String> + 'a>);

impl<'a> Bar<'a> {
    fn new<F: Foo>(foo: &'a F, txt: String) -> Bar<'a> {
        let itr = foo.foo(unsafe { &*(&txt[..] as *const str) });
        Bar(txt, itr)
    }
}

impl<'a> Iterator for Bar<'a> {
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        self.1.next()
    }
}

fn foo_int<'a, F: Foo>(f: &'a F, val: i32) -> impl Iterator<Item = String> + 'a {
    Bar::new(f, format!("{}", val))
}

然而,我对这个解决方案并不完全满意,原因如下。

  • 它相当特定于此特定方法;对于具有其他参数的另一种方法,我需要不同的包装器类型。

  • 它使用了不安全的代码。我认为这个可以(因为String的内部&str通过移动是稳定的),但对于其他类型可能会更难(如果不是不可能)实现。

  • 它强制我重新实现Iterator。虽然这个最小化版本有效,但它是次优的,因为底层迭代器可能有一些方法的优化实现,在这里我“丢失”了默认实现。手动实现Iterator的所有方法(将它们传递给self.1)是繁琐和脆弱的(如果进一步版本添加新方法,则必须添加它们)。

所以,任何更好的解决方案仍然受欢迎...

编辑:@Shepmaster的论点使我信服;这实际上是存储值和对其的引用的旧问题。因此,它是不安全的,而且我的解决方案并不通用,因为通常情况下可能无法正常工作 :-/


所以任何更好的解决方案仍然受欢迎...我会说更好的设计,在这里解决方案很明显是 foo(),应该接受一个字符串,结束。 - Stargateur
@Stargateur 我不同意。在迭代器被直接使用(例如 for 循环)的任何情况下,使用 &str 参数都非常完美。要求用户通过复制数据来使用 String 会导致在这些情况下并没有必要的代价。 - Pierre-Antoine
我没有完全理解你的用例。但这看起来像是一个异常,Rust如何知道引用是否有效?在C中,您可以这样做,但在Rust中不行,因为它是不安全的,所以编译器默认禁止它。此外,在您的示例中,我认为多复制一次的成本并不重要,从O(n)到O(n + 1)以获得安全行为。另外,如果您的函数接受一个String,如果您不克隆给定的字符串,则可以避免额外的复制,因此您不会有任何额外的复制。f.foo(format!("{}", val))将不执行额外的复制。 - Stargateur
@Stargateur 授予,在我的极简示例中,最好的解决方案是更改 foo 的签名。我的实际情况有点过于特定,无法在SO上解释,请相信我需要期望将引用作为参数。此外,我同意我正在将Rust编译器推出其舒适区。正如Shepmaster指出的那样,这里不可避免地需要一个 unsafe 块。 - Pierre-Antoine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接