为什么 `Fn() -> T` 约束了 `T`,但是 `Fn(T) -> T` 却没有约束?

7
下面的代码可以成功编译:
struct StructA<F>(F);
impl<F, T> StructA<F> where F: Fn() -> T {}
T虽然没有出现在StructA的类型参数中,但由于where从句的约束,它仍然受到限制。例如,在std::iter::Map中就使用了这个技巧,因此Map<I,F>只需要两个类型参数,而impl<B,I,F> Iterator for Map<I,F>则需要三个。
然而,以下代码无法编译:
struct StructB<F>(F);
impl<F, T> StructB<F> where F: Fn(T) -> T {}

error[E0207]: the type parameter `B` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:5:9
  |
5 | impl<F, T> StructB<F> where F: Fn(T) -> T {}
  |         ^ unconstrained type parameter

For more information about this error, try `rustc --explain E0207`.
error: could not compile `playground` due to previous error

Playground链接

这很不直观,为什么使用更多的T会使其约束更少?这是故意的还是Rust的限制?


请注意,这也会发生在常规特性中,即Fn的展开版本:

trait FnTrait<Args> {
    type Output;
}

// Works
struct StructA<F>(F);
impl<F, T> StructA<F> where F: FnTrait<(), Output = T> {}

// Fails
struct StructB<F>(F);
impl<F, T> StructB<F> where F: FnTrait<(T,), Output = T> {}

Playground 链接


1
我真的很怀疑这是否不是一个错误或仅仅是实现上的必要性。 - Jakub Dóka
2个回答

6

考虑一下如果我们手动实现Fn会发生什么(当然这需要夜间版本)...

#![feature(fn_traits, unboxed_closures)]

struct MyFunction;
impl<T> FnOnce<(T,)> for MyFunction {
    type Output = T;
    extern "rust-call" fn call_once(self, (v,): (T,)) -> T { v }
}

现在想象一下你的结构体:
struct StructA<F>(F);
impl<F: FnOnce(T) -> T, T> StructA<F>{
    fn foo(self) -> T { (self.0)() }
}

let s: StructA<MyFunction> = ...;
s.foo(); // What is `T`?

尽管参考资料中提到:

如果参数至少在以下情况之一中出现,泛型参数将对实现进行限制:

  • ...
  • 作为一个关联类型出现在包含另一个限制实现的参数的类型的边界

这是不准确的。引用RFC的说法:

类型参数在以下推理规则下是合法的,只要它们符合“约束”条件:
...
如果`>::U == V`出现在impl谓词中,并且`T0`...`Tn`受到约束,并且`T0 as Trait`不是impl特质引用,则`V`受到约束。
也就是说,特质中出现的所有类型参数都应该受到约束,而不仅仅是其中一个。
我已经在参考库中提出了一个问题。
相关链接:https://github.com/rust-lang/rust/issues/25041。

3
您总是需要能够从 impl 的 self 类型(如果是 trait impl,则还可能涉及实现的 trait)中推导出泛型类型参数。在第一种情况下,F: Fn() -> T,可以从 self 类型 StructA<F> 推导出 T。但是,在约束条件 F: Fn(T) -> T 的情况下,这是不可能的。
这两种情况的区别在于闭包 trait Fn 的返回类型是关联类型,而参数类型是泛型参数。换句话说,您只能为任何类型 F 实现一次 Fn() -> T,并且该实现将具有固定的返回类型 T。另一方面,对于相同的 F,可以为多个类型 T 实现 trait Fn(T) -> T,因此不能通用地推断出 T
实际上,在同一类型中实现多个 Fn traits 的情况非常罕见,在仅使用 closures 时甚至是不可能的。然而,由于存在这种可能性,编译器需要考虑到它。

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