【问题标题】:Impl trait with generic associated type in return position causes lifetime error在返回位置具有泛型关联类型的 Impl trait 会导致生命周期错误
【发布时间】:2019-10-31 08:24:22
【问题描述】:

我需要在'static 结构中存储一个fn(I) -> O(其中IO 可以是引用)。 O 需要是具有'static 泛型关联类型的特征,该关联类型也存储在结构中。 IO 本身都不会存储在结构中,因此它们的生命周期无关紧要。但是编译器仍然在抱怨I 活得不够长。

trait IntoState {
    type State: 'static;

    fn into_state(self) -> Self::State;
}

impl IntoState for &str {
    type State = String;

    fn into_state(self) -> Self::State {
        self.to_string()
    }
}

struct Container<F, S> {
    func: F,
    state: S,
}

impl<I, O> Container<fn(I) -> O, O::State>
where
    O: IntoState,
{
    fn new(input: I, func: fn(I) -> O) -> Self {
        // I & O lives only in the next line of code. O gets converted into
        // a `'static` (`String`), that is stored in `Container`.
        let state = func(input).into_state();
        Container { func, state }
    }
}

fn map(i: &str) -> impl '_ + IntoState {
    i
}

fn main() {
    let _ = {
        // create a temporary value
        let s = "foo".to_string();

        // the temporary actually only needs to live in `new`. It is
        // never stored in `Container`.
        Container::new(s.as_str(), map)
        // ERR:        ^ borrowed value does not live long enough
    };
    // ERR: `s` dropped here while still borrowed
}

playground

【问题讨论】:

  • 问题似乎在于关联的泛型类型,如果我删除它(插入String 作为返回类型)它开始工作。 playground。但对于我的应用程序逻辑来说,它是通用的。
  • 我是否应该更新标题和描述以更好地反映问题(这里的问题不是存储 fn,而是 IntoState trait/"missing" 泛型类型)?
  • 我会以一种与静态生命周期无关的方式更新它,但它仍然反映编译器的错误消息(因为人们会搜索那些误导性的东西)

标签: rust


【解决方案1】:

据我所知,编译器的错误信息具有误导性,它实际上需要的是明确定义的关联类型:

fn map(i: &str) -> impl '_ + IntoState<State = String> {
    i
}

对该问题的出色回答:Why does the compiler not infer the concrete type of an associated type of an impl trait return value? 提供了足够的信息,说明为什么实际需要这样做。

另见Rust issue #42940 - impl-trait return type is bounded by all input type parameters, even when unnecessary

您可以使用泛型类型参数而不是返回impl,在这种情况下您不必指定关联的类型:

fn map<T: IntoState>(i: T) -> T {
    i
}

【讨论】:

  • 更新中的版本在本例中有效,但有时您实际上想要使用存在类型。您可以使用 impl IntoState&lt;State = impl Any + 'static&gt; 或类似的 hack,但这似乎也是错误的。
  • 我相信我找到了解决这个问题的正确 GitHub 问题:github.com/rust-lang/rust/issues/42940
  • @SvenMarnach 这不是错误。返回T: IntoState(普遍量化)与返回impl IntoState(存在量化)完全不同。在第一种情况下,调用者确定T,因此任何调用者都知道关联的类型。在第二种情况下,调用者不知道它将获得哪个特征的实现,因此需要知道关联的类型才能对其进行任何操作。
  • @PeterHall 我不完全确定我理解你的评论。在不指定关联类型的情况下返回 impl IntoState 是完全可以的。除了 trait 定义中关联类型的边界之外,调用者将无法对关联类型做出任何假设,但原则上允许省略关联类型。然而,在这种特定情况下,返回的类型将由 map()&amp;str 参数的生命周期参数化,因此尽管在特征定义中声明了 'static,但关联类型也将包含该生命周期。
  • 我确实认为这是编译器的一个缺点(我不会讨论它是否应该被称为“错误”)。这个生命周期在存在返回类型的关联类型 中被捕获,这一事实是相当出乎意料的。我可以理解为什么它在编译器中以这种方式实现,但是从编写 Rust 代码的软件开发人员的角度来看,这种行为没有意义。
【解决方案2】:

显然对于这里到底发生了什么仍然有些困惑,所以我将尝试将我的 cmets 简化为一个简短的答案。

这里的问题是函数map()的原型:

fn map(i: &str) -> impl '_ + IntoState

这指定map() 的返回类型是实现IntoStatesome 类型,具有未指定的关联类型State。返回类型有一个生命周期参数,参数的生命周期为i;让我们称之为生命周期'a,以及完整的返回类型T&lt;'a&gt;。此返回类型的关联类型State 现在是&lt;T&lt;'a&gt; as IntoState&gt;::State,由'a 参数化。尽管特征定义中有'static 声明,编译器目前无法从关联类型中消除此生命周期参数。通过将关联类型显式指定为String,编译器将简单地使用显式指定的类型String 而不是&lt;T&lt;'a&gt; as IntoState&gt;::State,因此生命周期参数消失了,我们不再报错。

this Github issue 中讨论了此编译器缺点。

【讨论】:

    猜你喜欢
    • 2019-03-20
    • 1970-01-01
    • 1970-01-01
    • 2022-11-22
    • 2019-08-15
    • 2019-12-27
    • 2021-02-01
    • 2018-02-18
    • 2017-12-29
    相关资源
    最近更新 更多