< 返回版块

hzqd 发表于 2023-01-07 17:38

Rust 代码如下:

trait Test: Sized {
    fn test(self, f: impl FnOnce(Self)) {
        f(self)
    }
}
impl<T> Test for T {}

fn foo<T>(f: impl FnOnce(T)) {
    |x| x.test(f);
}

编译错误:

error[E0282]: type annotations needed
 --> src/lib.rs:9:6
  |
9 |     |x| x.test(f);
  |      ^  - type must be known at this point
  |
help: consider giving this closure parameter an explicit type
  |
9 |     |x: _| x.test(f);
  |       +++

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

x 传入到了 f 里,被当成闭包 f 的参数,所以类型自然应该是 T 呀!

这个闭包参数的类型需要声明,是从原理上就无法推导,还是 Rust 编译器可以改进的?

评论区

写评论
苦瓜小仔 2023-01-08 15:57

给不知道的人一个提醒,楼主已经在官方论坛提了同样的问题

The closure parameter type needs to be declared, can the compiler improve this?

而且我觉得那里的回答很棒了。

基本上就是 Rust 类型推断方向的事情,它当然不完美(而且规则并未被明确 —— 所以可以改进),但至少是保守的(导致的结果无非就是多加类型标注来明确你需要的类型)。

wangbyby 2023-01-08 14:25

https://rustc-dev-guide.rust-lang.org/closure.html 可以看下rustc的文档

--
👇
hzqd: 我加了您说的这个trait,四种情况均无法编译,报冲突了。

但如果我只给结构体关联了同名的函数:

struct T2;
impl T2 {
    fn test<T>(self, f: impl FnOnce(T)) {}
}

四种情况均能编译通过,这是为什么呢?

--
👇
wangbyby: - 对于情况四,你可以加个

trait Test2: Sized {
    fn test(self, f: impl FnOnce(Self)) {
        f(self)
    }
}
impl<T> Test2 for T {}

看看结果.

ThalliMega 2023-01-08 13:45

情况3中,从f(x)可以推出x的类型为T,但如果反过来先x.test(f),那么x的类型在此时是无法推出的,即类型推导与语句顺序有关。

情况4,给结构体关联了同名的函数是指什么?

trait Test: Sized {
    fn test(self, f: impl FnOnce(Self)) {
        f(self)
    }
}
impl<T> Test for T {}
trait Test2: Sized {
    fn test(self, f: impl FnOnce(Self)) {
        f(self)
    }
}
impl<T> Test2 for T {}
struct T2;
impl T2 {
    fn test<T>(self, f: impl FnOnce(T)) {}
}
fn foo<T>(f: impl FnOnce(T)) {
    |x| <_>::test(x, f);
}

以上代码是无法通过编译的

作者 hzqd 2023-01-08 12:30

我加了您说的这个trait,四种情况均无法编译,报冲突了。

但如果我只给结构体关联了同名的函数:

struct T2;
impl T2 {
    fn test<T>(self, f: impl FnOnce(T)) {}
}

四种情况均能编译通过,这是为什么呢?

--
👇
wangbyby: - 对于情况四,你可以加个

trait Test2: Sized {
    fn test(self, f: impl FnOnce(Self)) {
        f(self)
    }
}
impl<T> Test2 for T {}

看看结果.

wangbyby 2023-01-08 11:54
  • 情况一加入了类型
  • 情况二是返回了闭包, rustc会帮助你类型推断
  • 情况三我不是很了解,我猜测可能和类型推断算法有关
  • 情况四是使用的调用trait的语法 对于情况四,你可以加个
trait Test2: Sized {
    fn test(self, f: impl FnOnce(Self)) {
        f(self)
    }
}
impl<T> Test2 for T {}

看看结果.

也可以直接去github rust提个issue, 说不定以及有RFC了

--
👇
hzqd: 请问您如何解释以下四种编译通过的情况?

情况一:

fn foo<T>(f: impl FnOnce(T)) {
    |x: T| x.test(f);
}

情况二:

fn foo<T>(f: impl FnOnce(T)) -> impl FnOnce(T) {
    |x| x.test(f)
}

情况三:

fn foo<T>(f: impl FnOnce(T)) {
    |x| if false { f(x) } else { x.test(f) };
}

情况四:

fn foo<T>(f: impl FnOnce(T)) {
    |x| <_>::test(x, f);
}

它们是如何避免“其他traitstruct存在同名方法”而直接通过了编译呢?

--
👇
Pikachu: x.test可以指向的是任意的函数。假设std或者core里有个同名的函数test,编译器不可能知道你用的是哪个函数,也就没法根据函数签名做出类型约束。

作者 hzqd 2023-01-08 11:33

另外,对于情况三来讲,如下等价代码不能通过编译,这又是为什么?

fn foo<T>(f: impl FnOnce(T)) {
    |x| if true { x.test(f) } else { f(x) };
}

因为Rust具备全局类型推导的特性,它完全可以根据else代码块的内容反推x的类型。

但这段代码依然获得编译错误,其故何如?

作者 hzqd 2023-01-08 11:01

请问您如何解释以下四种编译通过的情况?

情况一:

fn foo<T>(f: impl FnOnce(T)) {
    |x: T| x.test(f);
}

情况二:

fn foo<T>(f: impl FnOnce(T)) -> impl FnOnce(T) {
    |x| x.test(f)
}

情况三:

fn foo<T>(f: impl FnOnce(T)) {
    |x| if false { f(x) } else { x.test(f) };
}

情况四:

fn foo<T>(f: impl FnOnce(T)) {
    |x| <_>::test(x, f);
}

它们是如何避免“其他traitstruct存在同名方法”而直接通过了编译呢?

--
👇
Pikachu: x.test可以指向的是任意的函数。假设std或者core里有个同名的函数test,编译器不可能知道你用的是哪个函数,也就没法根据函数签名做出类型约束。

Pikachu 2023-01-08 01:03

x.test可以指向的是任意的函数。假设std或者core里有个同名的函数test,编译器不可能知道你用的是哪个函数,也就没法根据函数签名做出类型约束。

如果你想明确指定这里用的是Test::test函数,你得这么写

trait Test: Sized {
    fn test(self, f: impl FnOnce(Self)) {
        f(self)
    }
}
impl<T> Test for T {}

fn foo<T>(f: impl FnOnce(T)) {
    |x| Test::test(x, f);
}

当然这里会有warning,因为定义了一个closure但没有使用。但是最起码编译能通过,说明类型没错。

--
👇
hzqd: 但x是闭包f的参数呀,闭包f的参数类型就是T,为何说x可以为任意类型?

如果你是指:T是泛型,而不是具体类型。那么,给x显式声明泛型T依然可以通过编译,又要如何解释?

作者 hzqd 2023-01-07 21:11

x是闭包f的参数呀,闭包f的参数类型就是T,为何说x可以为任意类型?

如果你是指:T是泛型,而不是具体类型。那么,给x显式声明泛型T依然可以通过编译,又要如何解释?

ThalliMega 2023-01-07 20:18

在原错误写法中,x可以为任意类型,不一定为T类型,x的类型从原理上就无法推导

--
👇
hzqd: 你可以解释一下为什么加上返回类型就可以通过编译吗?

作者 hzqd 2023-01-07 20:14

你可以解释一下为什么加上返回类型就可以通过编译吗?

ThalliMega 2023-01-07 20:05
trait Test: Sized {
    fn test(self, f: impl FnOnce(Self)) {
        f(self)
    }
}
impl<T> Test for T {}

fn foo<T>(f: impl FnOnce(T)) -> impl FnOnce(T) {
    |x| x.test(f)
}
frank-king 2023-01-07 18:42

首先,Pipe这个trait很奇怪,泛型参数为何不在pipe函数上而要放在trait上?

pub trait Pipe: Sized {
    fn pipe<R>(self, f: fn(Self) -> R) -> R {
        f(self)
    }
}
impl<T> Pipe for T {}

这样可能更好一点。

其次,main函数只放一个闭包表达式是什么意思?不太明白。

fn main() {
    let _closure = |x| x.pipe(test);
}

如果只是想示意这个闭包用户,将其绑定到一个变量上,语义可能会更明确一些。

最后回到你的问题,即|x| x.pipe(test)的类型问题。正如上面所写,

    let _clousre = |x| x.pipe(test);

那么你期望_clousure是什么类型?如果要推导类型,只能推出来如下信息:

  1. test的类型是fn (fn (?T)).
  2. 代入Pipe::pipe的类型中,由testpipe的第二个参数,可知x的类型为fn (?T)x.pipe(test)的类型(即pipe中的泛型参数R)为()|x| x.pipe(test)的类型为f(?impl Pipe)

可见,此时仍然不知道x的类型。所以必须显式地标注x的类型。

1 共 13 条评论, 1 页