类型变量中的闭包特征边界与高阶函数参数中的闭包特征边界

Closure Trait Bounds in Type Variables versus Closure Trait Bounds in Arguments on Higher Order Functions

为什么这两个有效:

fn apply_once1<F: FnOnce(T1) -> T2, T1, T2> (f: F, x: T1) -> T2 {
    f(x)
}

fn apply_once2<F, T1, T2> (f: F, x: T1) -> T2 
    where F: FnOnce(T1) -> T2 
{
    f(x)
}

但是这个不能编译:

fn apply_once3<T1, T2> (f: FnOnce(T1) -> T2, x: T1) -> T2 {
    f(x)
}

它抱怨:

error: the trait `core::marker::Sized` is not implemented for the type `core::ops::FnOnce(T1) -> T2 + 'static` [E0277]
    once3<T1, T2> (f: FnOnce(T1) -> T2, x: T1) -> T2 {
                   ^
help: see the detailed explanation for E0277
note: `core::ops::FnOnce(T1) -> T2 + 'static` does not have a constant size known at compile-time
note: all local variables must have a statically known size

我知道 FnOnce 可能没有静态已知的大小,所以通常我会用 & 来解决这个问题,将变量换成一个引用,这样大小就已知了。但是我不明白为什么apply_once1apply_once2可以侥幸逃脱?

四处搜索,我找不到任何关于将特征绑定到参数上与将特征绑定到类型变量之间的区别的内容。

Googling around, I can't find anything that talks about the difference between putting a trait bound on the argument versus putting it on the type variables.

这实际上不是您在第三个中所做的。让我们做一些更简单的事情:

fn do_something<C: Clone>(x: C); fn do_something<C>(x: C) where C: Clone;

fn do_something(x: Clone)

前两个实际上是同一件事,where 只是语法糖,使具有非平凡特征边界的函数更易于阅读,但两者都在说 "I am writing a function that will later be specialized for a type implementing the Clone trait".

最后一个在说

"I want x, which is literally the Clone trait."

也许这令人困惑,让我详细说明一下,任何带有尖括号的东西都可以被认为是函数的示意图,它表示对于满足特定要求的给定类型,编译器可以用下面的代码生成一个函数。这会阻止我们写:

fn do_something(x: f64); fn do_something(x: Vec<usize>);

等等。注意没有尖括号。如果没有尖括号,则说明您不是在编写原理图,而是在告诉编译器您想要的 exact 类型。现在,Clone 是一种类型……但它是一种特征。特征不像结构。事实上,您要求的是 Trait Object,因为它们没有调整大小,所以只能通过引用传递。

您实际上并不是在请求 实现 FnOnce 的对象,您实际上是在请求 FnOnce 的 "platonic form" 的有效内容,这不是您想要的可以传递,因为它更像是一个抽象概念而不是实际事物。您可以传入 &FnOnce,也就是 "a pointer to any random, arbitrary thing that happens to be an FnOnce",但这有一些权衡(我建议阅读关于特征对象的 link,或者找到关于它们的其他 SO 答案,其中涵盖了它们比这里更详细。

所以简短的版本:函数签名中的任何内容都是具体类型,尖括号(and/or where 子句)中的任何内容都是对这些具体类型之一的约束。

当您使用 apply_once1 时,类型变量 F 被实例化为一个已知大小的具体函数类型。

apply_once3中,FnOnce(T1) -> T2是trait对象的类型,它没有已知的大小,因为apply_once3的单个实例可以用不同的方式调用函数。

this section of the Rust book