为什么 Rust 允许通过空指针调用函数? [英] Why does Rust allow calling functions via null pointers?

查看:46
本文介绍了为什么 Rust 允许通过空指针调用函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Rust 中尝试了函数指针魔术,最终得到了一个代码片段,我完全没有解释它为什么编译,甚至更多,为什么它运行.

I was experimenting with function pointer magic in Rust and ended up with a code snippet which I have absolutely no explanation for why it compiles and even more, why it runs.

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));
}

我无法解释为什么通过将 caller(...) 中的空指针转换为 caller(...) 类型的实例来调用 foo代码>F.我原以为只能通过相应的函数指针调用函数,但鉴于指针本身为空,显然情况并非如此.话虽如此,我似乎显然误解了 Rust 类型系统的一个重要部分.

I cannot explain why foo is being invoked by casting a null pointer in caller(...) to an instance of type F. I would have thought that functions may only be called through corresponding function pointers, but that clearly can't be the case given that the pointer itself is null. With that being said, it seems that I clearly misunderstand an important piece of Rust's type system.

Playground 示例

推荐答案

这个程序实际上根本没有构造函数指针——它总是直接调用 foo 和那两个闭包.

This program never actually constructs a function pointer at all- it always invokes foo and those two closures directly.

每个 Rust 函数,无论是闭包还是 fn 项,都有唯一的匿名类型.此类型根据需要实现 Fn/FnMut/FnOnce 特征.fn 项的匿名类型是零大小的,就像没有捕获的闭包类型一样.

Every Rust function, whether it's a closure or a fn item, has a unique, anonymous type. This type implements the Fn/FnMut/FnOnce traits, as appropriate. The anonymous type of a fn item is zero-sized, just like the type of a closure with no captures.

因此,表达式 create(foo)create 的参数 F 实例化为 foo 的类型- 这不是函数指针类型 fn(),而是一个匿名的、长度为零的类型,仅用于 foo.在错误消息中,rustc 调用此类型 fn() {foo},如您所见 此错误消息.

Thus, the expression create(foo) instantiates create's parameter F with foo's type- this is not the function pointer type fn(), but an anonymous, zero-sized type just for foo. In error messages, rustc calls this type fn() {foo}, as you can see this error message.

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

Inside create::<fn() {foo}> (using the name from the error message), the expression caller::<F>() forwards this type to caller without giving it a value of that type.

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

Finally, in caller::<fn() {foo}> the expression closure() desugars to FnMut::call_mut(closure). Because closure has type &mut F where F is just the zero-sized type fn() {foo}, the 0 value of closure itself is simply never used1, and the program calls foo directly.

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

The same logic applies to the closure || println!("Okay..."), which like foo has an anonymous zero-sized type, this time called something like [closure@src/main.rs:2:14: 2:36].

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

The second closure is not so lucky- its type is not zero-sized, because it must contain a reference to the variable val. This time, FnMut::call_mut(closure) actually needs to dereference closure to do its job. So it crashes2.

1 像这样构造一个空引用在技术上是未定义的行为,因此编译器不对该程序的整体行为做出任何承诺.但是,用其他一些地址"替换 0F 的对齐避免了像 fn() {foo} 这样的零大小类型的问题,并给出了 同样的行为!)

1 Constructing a null reference like this is technically undefined behavior, so the compiler makes no promises about this program's overall behavior. However, replacing 0 with some other "address" with the alignment of F avoids that problem for zero-sized types like fn() {foo}, and gives the same behavior!)

2 再一次,构造一个空(或悬空)引用是实际承担责任的操作 - 之后,任何事情都会发生.段错误只是一种可能性——未来版本的 rustc,或者在稍微不同的程序上运行时的相同版本,可能会完全做其他事情!

2 Again, constructing a null (or dangling) reference is the operation that actually takes the blame here- after that, anything goes. A segfault is just one possibility- a future version of rustc, or the same version when run on a slightly different program, might do something else entirely!

这篇关于为什么 Rust 允许通过空指针调用函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆