【问题标题】:Using impl Trait in a recursive function在递归函数中使用 impl Trait
【发布时间】:2019-05-30 16:06:42
【问题描述】:

我一直在尝试impl Trait,在构建递归函数时遇到了这个错误:

error[E0308]: if and else have incompatible types
  --> src/main.rs:16:5
   |
16 | /     if logic {
17 | |         one(false)
18 | |     } else {
19 | |         two()
20 | |     }
   | |_____^ expected opaque type, found a different opaque type
   |
   = note: expected type `impl Meow` (opaque type)
              found type `impl Meow` (opaque type)

这是重现的代码 (Rust playground link):

trait Meow {
    fn meow();
}

struct Cat(u64);

impl Meow for Cat {
    fn meow() {}
}

fn one(gate: bool) -> impl Meow {
    if gate {
        one(false)
    } else {
        two()
    }
}

fn two() -> impl Meow {
    Cat(42)
}

fn main() {
    let _ = one(true);
}

我无法找到有关此特定问题的文档,而且我觉得奇怪的是编译器返回的错误大致上说“这两个相同的东西是不同的”。

请问有什么方法可以支持impl Trait 语法同时进行这种回避吗?

【问题讨论】:

  • 这是一个稍微不同的问题。这里编译器可以理想地推断one返回的impl Meow必须与two返回的impl Meow相同,无论它是什么,在这种情况下,两个if分支将统一。它只是没有,今天。
  • @AndersKaseorg 虽然问题可能不同,但答案仍然与副本中的相同
  • @你好我不同意。一开始我也是这么想的,后来仔细看了这个问题,想出了my answer,这个递归模式特有的,不需要trait对象。
  • 我同意@AndersKaseorg,虽然链接答案中的解决方法也解决了这个问题,但失败的原因有些不同:在链接答案中有两种不同的类型都实现了这个特征;然而,在这个问题中,只有一种类型实现了该特征,更聪明的编译器可以接受它。
  • @MatthieuM.:嗯,是的,如果调用者和被调用者是不同的函数,那就是真的。但是递归函数呢,它在检查递归调用时可以使用自己的实现吗?也许有理论上的理由不这样做,但我没有看到任何实际的缺点。

标签: rust


【解决方案1】:

我认为理想的编译器会接受您的代码,但是当前的语言不允许递归推理来确定在这种情况下类型实际上是相同的。您可以通过使用类型变量抽象 impl Meow 类型来解决这个缺失的功能:

fn one_template<T: Meow>(gate: bool, two: impl FnOnce() -> T) -> T {
    if gate {
        one_template(false, two)
    } else {
        two()
    }
}

fn one(gate: bool) -> impl Meow {
    one_template(gate, two)
}

Rust playground link

【讨论】:

  • @MatthieuM。您的评论与我的回答无关。这里根本不涉及dyn Trait。这完全是关于解决真正相同的类型之间的类型统一问题。 (这 not 与其他类型看起来相同但不同的问题一样!)如果编译器正确执行了统一,它仍然是零开销。在将其标记为重复之前,您是否仔细阅读了此内容?
  • 我同意@Matthieu;类型的不透明度是重点。一个足够聪明的编译器可以为您做无数的事情,但是对不透明类型进行去透明化并不是它意味着要做的事情(与完整的程序类型推断相同)。跨度>
  • @trentcl 我们能否解决一些问题:我完全理解impl Trait 是不透明的,并且dyn Trait 有运行时开销(即使dyn Trait 什么都没有与这个问题有关)。我并不是建议编译器应该在任何地方对impl Meow 进行去透明化处理。对于impl Meow 打算强制执行的抽象障碍,这将是一场彻底的灾难。好的?我们同意所有这些。但是这里,因为我们正在处理递归,所以我们应该那个抽象屏障中。我们应该能够将我们自己的返回类型与我们自己的返回类型统一起来!
  • 现在您可以同意或不同意语言应该以这种方式扩展。美好的。但我们至少可以承认这是一个与impl Trait 不透明度和impl Traitdyn Trait 不同的一般问题不同的问题吗?
【解决方案2】:

免责声明:此答案假定读者了解-&gt; impl Trait 需要返回单一类型;见this question for returning different types


不透明度

Rust 的核心原则之一是类型检查完全由函数、类型等的接口驱动...而忽略实现。

关于-&gt; impl Trait 功能,这表现为语言将每个-&gt; impl Trait 视为不透明类型,仅由其来源的函数标识。

因此,您可以两次调用同一个函数:

use std::fmt::Debug;

fn cat(name: &str) -> impl Debug { format!("Meow {}", name) }

fn meow(g: bool) -> impl Debug {
    if g {
        cat("Mario")
    } else {
        cat("Luigi")
    }
}

fn main() {
    println!("{:?}", meow(true));
}

但是你不能调用不同的函数,即使它们返回相同的类型,如果至少有一个隐藏在-&gt; impl Trait后面:

use std::fmt::Debug;

fn mario() -> impl Debug { "Meow Mario" }

fn luigi() -> &'static str { "Meow Luigi" }

fn meow(g: bool) -> impl Debug {
    if g {
        mario()
    } else {
        luigi()
    }
}

fn main() {
    println!("{:?}", meow(true));
}

产量:

error[E0308]: if and else have incompatible types
  --> src/main.rs:8:9
   |
8  | /         if g {
9  | |             mario()
10 | |         } else {
11 | |             luigi()
12 | |         }
   | |_________^ expected opaque type, found &str
   |
   = note: expected type `impl std::fmt::Debug`
              found type `&str`

还有两个隐藏在-&gt; impl Trait后面:

use std::fmt::Debug;

fn mario() -> impl Debug { "Meow Mario" }

fn luigi() -> impl Debug { "Meow Luigi" }

fn meow(g: bool) -> impl Debug {
    if g {
        mario()
    } else {
        luigi()
    }
}

fn main() {
    println!("{:?}", meow(true));
}

产生与您得到的相同的错误消息:

error[E0308]: if and else have incompatible types
  --> src/main.rs:8:5
   |
8  | /     if g {
9  | |         mario()
10 | |     } else {
11 | |         luigi()
12 | |     }
   | |_____^ expected opaque type, found a different opaque type
   |
   = note: expected type `impl std::fmt::Debug` (opaque type)
              found type `impl std::fmt::Debug` (opaque type)

与递归交互

无。

语言在这里没有特殊情况递归,因此没有意识到,在问题中提出的情况下,只涉及一种类型。相反,它注意到fn one(...) -&gt; impl Meowfn two(...) -&gt; impl Meow 并得出结论,它们是不同的不透明类型,因此编译时统一是不可能的。

提交 RFC 来调整这方面可能是合理的,要么从递归的角度争论,要么从模块级可见性的角度争论;这超出了这个答案的范围。


解决方法

唯一的可能是确保类型是唯一的,这需要命名它。捕获名称中的类型后,您可以在任何需要匹配的地方始终如一地应用它。

我会将您推荐给 @Anders' answer,以了解他的巧妙解决方法。

【讨论】:

    猜你喜欢
    • 2019-05-28
    • 2023-01-09
    • 2021-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-11
    • 2017-01-21
    相关资源
    最近更新 更多