我在Rust中try 函数指针魔术,最后得到了一个代码片段,我完全无法解释它为什么要编译,甚至无法解释它为什么要运行.

fn foo() {
    println!("This is really weird...");
}

fn caller<F>() where F: FnMut() {
    let closure_ptr = 0 as *mut F;
    let closure = unsafe { &mut *closure_ptr };
    closure();
}

fn create<F>(_: F) where F: FnMut() {
    caller::<F>();
}

fn main() {
    create(foo);
    
    create(|| println!("Okay..."));
    
    let val = 42;
    create(|| println!("This will seg fault: {}", val));
}

我无法解释why foo是通过在caller(...)中向F类型的实例投射空指针来调用的.我本以为函数只能通过相应的函数指针来调用,但考虑到指针本身是空的,显然不能这样.话虽如此,我似乎显然误解了Rust类型系统的一个重要部分.

Example on Playground

推荐答案

这个程序实际上从来没有构造过函数指针——它总是调用foo和这两个闭包directly.

每个Rust函数,无论是闭包还是fn项,都有一个唯一的匿名类型.此类型实现Fn/FnMut/FnOnce特性(视情况而定).fn项的匿名类型是零大小的,就像没有捕获的闭包类型一样.

因此,表达式create(foo)foo的类型实例化create的参数F——这不是函数指针类型fn(),而是一个匿名的零大小类型,仅适用于foo.在错误消息中,rustc调用这个类型fn() {foo},正如您可以看到的this error message.

create::<fn() {foo}>内部(使用错误消息中的名称),表达式caller::<F>()将该类型转发给caller,而不给它该类型的值.

最后,在caller::<fn() {foo}>中,表达式closure()减至FnMut::call_mut(closure).因为closure有类型&mut F,其中F只是大小为零的类型fn() {foo},所以closure本身的0值永远不会被使用1,程序直接调用foo.

同样的逻辑也适用于闭包|| println!("Okay..."),它和foo一样有一个匿名的零大小类型,这次称为类似[closure@src/main.rs:2:14: 2:36]的类型.

第二个闭包就没那么幸运了——它的类型是not零大小,因为它必须包含对变量val的引用.这一次,FnMut::call_mut(closure)实际上需要解引用closure来完成它的工作.所以它崩溃了2.


1像这样构造空引用在技术上是未定义的行为,因此编译器不对该程序的整体行为做出promise .然而,将0替换为其他一些"地址"并对齐F可以避免像fn() {foo}这样大小为零的类型的问题,并给出the same behavior!)

2再次强调,构造空(或悬挂)引用实际上是在这里承担责任的操作——在那之后,任何事情都会发生.segfault只是一种可能性——rustc的future 版本,或者在稍有不同的程序上运行时的同一版本,可能会做完全不同的事情!

Rust相关问答推荐

如何确保我的类型实现自动特征?

即使参数和结果具有相同类型,fn的TypId也会不同

PyReadonlyArray2到Vec T<>

什么样的 struct 可以避免使用RefCell?

展开枚举变量并返回所属值或引用

Box::new()会从一个堆栈复制到另一个堆吗?

防止cargo test 中的竞争条件

用 rust 蚀中的future 展望 struct 的future

在使用AWS SDK for Rust时,如何使用硬编码访问密钥ID和密钥凭据?

为什么铁 rust S的默认排序功能比我对小数组的 Select 排序稍微慢一些?

Rust 的多态现象.AsRef与Derf

正在将带有盒的异步特征迁移到新的异步_fn_in_特征功能

如何迭代存储在 struct 中的字符串向量而不移动它们?

使用 Option 来分配?

为什么这个闭包没有实现Fn?

简单 TCP 服务器的连接由对等重置错误,mio 负载较小

如何递归传递闭包作为参数?

如何在 Rust 中将 UTF-8 十六进制值转换为 char?

`use std::error::Error` 声明中断编译

Rust 中的运行时插件