为什么将具有Box语法的函数指针数组装箱仅适用于临时的let绑定?

时间:2019-02-11 14:17:21

标签: rust

我有两个虚拟功能:

fn foo() -> u32 { 3 }
fn bar() -> u32 { 7 }

我想创建一个盒装的函数指针切片:Box<[fn() -> u32]>。我想使用box语法(我知道两个元素不是必需的,但是我的实际用例是不同的。)

我尝试了几件事(Playground):

// Version A
let b = box [foo, bar] as Box<[_]>;

// Version B
let tmp = [foo, bar];
let b = box tmp as Box<[_]>;

// Version C
let b = Box::new([foo, bar]) as Box<[_]>;

版本B和C可以正常工作(尽管C对我来说不起作用,因为它使用Box::new),但是版本A错误:

error[E0605]: non-primitive cast: `std::boxed::Box<[fn() -> u32; 2]>` as `std::boxed::Box<[fn() -> u32 {foo}]>`
 --> src/main.rs:8:13
  |
8 |     let b = box [foo, bar] as Box<[_]>;
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

显然,由于某种原因,在版本A中,编译器无法将功能项强制转换为功能指针。 那是为什么?为何还要与附加的临时let绑定一起使用?


此问题的灵感来自this other question。我想知道为什么vec![foo, bar]出错,但是[foo, bar]正常工作。我看着the definition of vec![],发现这部分令我感到困惑。

2 个答案:

答案 0 :(得分:4)

在我看来,这似乎是类型推断算法的特质,除了当前的推断算法的行为恰似它之外,可能没有更深层次的原因。对于类型推断何时起作用以及何时不起作用没有正式的规范。如果遇到类型推断引擎无法处理的情况,则需要添加类型注释或以编译器可以正确推断类型的方式重写代码,而这正是您在此处要做的。

Rust中的每个函数都有其自己的单独的function item type,无法通过语法直接命名,但是会被显示为例如错误消息中的fn() -> u32 {foo}。如果它们出现在match的不同分支,if的不同分支或数组的不同元素中,则存在一种特殊的强制转换,将具有相同签名的函数项类型转换为相应的函数指针类型。 。这种强制与其他强制不同,因为它不仅发生在显式类型的上下文(“强制站点”)中,而且这种特殊的处理方式很可能导致这种特殊性。

特殊强制是由绑定触发的

let tmp = [foo, bar];

因此tmp的类型完全确定为[fn() -> u32; 2]。但是,似乎在编写时在类型推断算法中还没有足够早地触发特殊强制

let b = box [foo, bar] as Box<[_]>;

编译器首先假定数组的项目类型为其第一个元素的类型,显然当试图确定_在此处表示什么时,编译器仍未更新此概念–根据错误消息_在这里被推断为fn() -> u32 {foo}。有趣的是,编译器在打印错误消息时已经正确推断出box [foo, bar]的完整类型,因此该行为的确很奇怪。仅当详细查看编译器源代码时才能给出完整的解释。

Rust的类型求解器引擎通常无法处理其理论上应该能够解决的情况。 Niko Matsakis的chalk engine旨在在将来某个时候为所有这些情况提供一个通用的解决方案,但我不知道该项目的状态和时间表是什么。

答案 1 :(得分:3)

[T; N][T]unsizing coercion

  

CoerceUnsized<Pointer<U>> for Pointer<T> where T: Unsize<U>是   为所有指针类型实现(包括智能指针,例如Box   和Rc)。 Unsize仅自动实现,并启用   以下转换:

     
      
  • [T; n] => [T]
  •   

这些强制仅发生在某些强制位置

  

强制发生在强制位置。任何明确的位置   输入将导致强制转换为其类型。如果需要推断,   强制将不会执行。穷尽性的强制场所   对于表达式e键入U的情况是:

     
      
  • 让语句,静态变量和常量:let x: U = e
  •   
  • 函数参数:takes_a_U(e)
  •   
  • 将返回的任何表达式:fn foo() -> U { e }
  •   
  • 结构文字:Foo { some_u: e }
  •   
  • 数组文字:let x: [U; 10] = [e, ..]
  •   
  • 元组文字:let x: (U, ..) = (e, ..)
  •   
  • 块中的最后一个表达式:let x: U = { ..; e }
  •   

您的情况B是一个let语句,您的情况C是一个函数参数。您的案件A没有承保。


纯粹出于直觉,我要指出box是一个不稳定的魔术关键字,因此它可能只是实现了一半。也许应该施加强制性,但是没有人需要它,因此它从未得到支持。