< 返回版块

Aaron009 发表于 2021-10-18 11:31

playground link

error[E0308]: mismatched types
 --> src/main.rs:7:14
  |
4 |     let closures_1 = || {};
  |                      ----- the expected closure
...
7 |     let v2 = vec![closures_1, closures_2,]; // ❌
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected slice, found array of 2 elements
  |
  = note: expected struct `Box<[[closure@src/main.rs:4:22: 4:27]], _>`
             found struct `Box<[fn(); 2], std::alloc::Global>`
  = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info)

为什么?

评论区

写评论
苦瓜小仔 2021-10-18 23:50

赞成 eweca-d 的观点。以下是我的理解和补充。 根据 reference 的类型分类,至少有以下一些类型与函数/闭包有关:

  1. Function item types:比如 fn foo<T>() { },每个函数都是不同的类型
  2. Closure types:A closure type is approximately equivalent to a struct which contains the captured variables. 所以每个闭包也是不同的类型。
  3. Function pointer types:比如 type Binop = fn(i32, i32) -> i32; —— written using the fn keyword, refer to a function whose identity is not necessarily known at compile-time —— 相同签名的函数/非捕获闭包可以强制转化为相同的类型。注意这里是 小 f。
  4. Trait objects:比如 dyn Fn(usize) -> usize,动态分发。注意这里是 大 F。
  5. Impl trait:比如 impl Fn(usize) -> usize 或者 F: Fn(usize) -> usize,静态分发
  6. Type parameters:显式指定类型,利用 coercion ,你可以改变值的类型
  7. Inferred type _:编译器推断类型

而且前三者有 如下 coercion 关系

Function pointer types can be created via a coercion from both function items and non-capturing closures.

相同签名的函数

fn main() {
    let _ = [add, add2];
    let _ = vec![add, add]; 
    // let _ = vec![add, add2]; // ❌:类型不同
    let _: Vec<Binop> = vec![add, add2]; 
}

fn add(x: i32, y: i32) -> i32 {
    x + y
}

fn add2(x: i32, y: i32) -> i32 {
    x + y + 2
}

type Binop = fn(i32, i32) -> i32;

报错的那行有这样的提示:

5 |     let _ = vec![add, add2]; 
  |             ^^^^^^^^^^^^^^^ expected slice, found array of 2 elements
  |
  = note: expected struct `Box<[fn(i32, i32) -> i32 {add}], _>`
             found struct `Box<[fn(i32, i32) -> i32; 2], std::alloc::Global>`

即:

  1. expected Function item types 的切片:因为 reference Function item types 提到:There is no syntax that directly refers to a function item type, but the compiler will display the type as something like fn(u32) -> i32 {fn_name} in error messages
  2. found Function pointer types 的数组

切片和数组这两种不同的类型在这里不是重点,根据 playground 的 expand macro 功能,可以清楚地看到, vec! 宏被展开成:

let _ = <[_]>::into_vec(box [add, add]);
let _ = <[_]>::into_vec(box [add, add2]); // ❌
let _: Vec<Binop> = <[_]>::into_vec(box [add, add2]);

这说明了三点问题:

  1. 函数是单独的类型(Function item type),具有 Copy trait,而且同一名称的函数,是同一类型。
  2. 当两个不同名函数的函数签名相同时,这两个函数不是相同的类型(Function item types),但是可以统一(隐式强转或者显式指定类型)成 Function pointer type,而编译器 可能 在某些情况下推断的类型 (Inferred type)不一定是你想要的,因为模棱两可的情况下,Rust 会默认帮助你做你安全(保守)的决断。比如在 array 下推断为 Function pointer type,在 Vec 下推断为 Function item types。
  3. 如果 Rust 会默认帮助你做你安全(保守)的决断,那么它为什么不拒绝编译 let _ = [add, add2]; 呢。这很难讲。安全本来就不是绝对的,从绝对的角度看,一切隐式都不应该存在:比如隐式/强制转换、强制解引用等。一方面,如果没有这些方便的“隐式”做法,写代码也太累了;另一方面,隐式带来未知,增加设计障碍和学习障碍。性能、安全、人体工学相互牵制,没有语言可以做到完美。

相同签名的闭包

闭包使用起来很容易,但是它很特殊,从而带来复杂性(性能、安全、人体工学 金三角 again...):

  1. 闭包是单独的类型 (Closure types):不同名的闭包,其类型不同;同名的闭包,其类型相同。
  2. 闭包也可以统一(隐式强转或者显式指定类型)成 Function pointer type。可见闭包几乎与函数一致。
  3. 但是闭包有很多种分类(捕获和非捕获、可变和不可变),这导致很多隐式的性质。最典型的,闭包可以有一种或者多种 Fn 型 traits;四种 marker traits 在不同闭包下也不一致,比如以可变(独占)方式捕获的闭包不具有 Copy trait 。见 reference

例子正如楼主和 eweca-d 贴出的。而且涉及到上面 7 种罗列的大部分类型。


p.s. 我写的 Vec::from([closures_1, closures_2]); 通过编译,是因为 impl<T, const N: usize> From<[T; N]> for Vec<T, Global> 性质,只要 [closures_1, closures_2] 能编译,Vec::from 就能编译。

eweca-d 2021-10-18 16:11

因为实际上类型是不一样的,这个提示可能没说清楚,如果使用如下代码:

fn main() {
    let closures_1 = || {};
    let closures_2 = || {};
    let mut v2 = Vec::new();
    v2.push(closures_1);
    v2.push(closures_2);
}

报错信息:

 |
4 |     let closures_1 = || {};
  |                      ----- the expected closure
5 |     let closures_2 = || {};
  |                      ----- the found closure
...
8 |     v2.push(closures_2);
  |             ^^^^^^^^^^ expected closure, found a different closure
  |
  = note: expected closure `[closure@src/main.rs:4:22: 4:27]`
             found closure `[closure@src/main.rs:5:22: 5:27]`
  = note: no two closures, even if identical, have the same type
  = help: consider boxing your closure and/or using it as a trait object

这个报错提示就比较明显了,因为所有闭包,哪怕是完全一致的,都是不同的type(类型)。

可以看看相关的帖子:

https://stackoverflow.com/questions/29371914/what-is-the-inferred-type-of-a-vector-of-closures https://stackoverflow.com/questions/39083375/expected-closure-found-a-different-closure

简单来说,编译器会为每一个闭包生成一个名字不同的结构体,其中包含有捕获的变量和函数指针。所以类型都是不同的。 至于指定类型为Vec<fn()>什么可以用我没想通,可能fn()提示会让vec里加入的是函数指针吧。至于为什么不能自动推断出这个,第一个帖子认为,这相当于把一个concrete type(固定类型)转换为类似object trait,涉及到动态派发,不是zero overhead,所以需要你手动指定才行。

Bai-Jinlin 2021-10-18 12:41

let v2:Vec<fn()> = vec![closures_1, closures_2,];这样指定类型就行,不过最好还是Box一下let v2:Vec<Box<dyn Fn()>> = vec![Box::new(closures_1), Box::new(closures_2)];,不box的话你闭包只要捕获变量就不能用fn()了

苦瓜小仔 2021-10-18 12:36

看报错是 vec 宏的问题,但是的确可以存在 Vec 中:

#![allow(unused)]
fn main() {
    let closures_1 = || {};
    let closures_2 = || {};
    let v = [closures_1, closures_2,]; // ✅
    let v2 = Vec::from([closures_1, closures_2]); // ✅
}
1 共 4 条评论, 1 页