我在 Rust 中尝试了函数指针魔术,最终得到了一个代码片段,我完全没有解释它为什么编译,甚至更多,为什么它运行。
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));
}
Run Code Online (Sandbox Code Playgroud)
我无法解释为什么 foo通过将空指针caller(...)转换为 type 的实例来调用F。我原以为只能通过相应的函数指针调用函数,但鉴于指针本身为空,显然情况并非如此。话虽如此,似乎我显然误解了 Rust 类型系统的一个重要部分。
rpj*_*nst 50
这个程序从来没有真正构造过一个函数指针——它总是直接调用foo这两个闭包。
每个 Rust 函数,无论是闭包还是fn项,都有唯一的匿名类型。这种类型的实现Fn/ FnMut/FnOnce特征,适当的。项目的匿名类型fn是零大小的,就像没有捕获的闭包类型一样。
因此,表达式用的类型create(foo)实例化create的参数——这不是函数指针类型,而是一个匿名的、大小为零的类型,仅用于。在错误消息中,rustc 调用此类型,正如您可以看到此错误消息。Ffoofn()foofn() {foo}
在内部create::<fn() {foo}>(使用错误消息中的名称),表达式caller::<F>()将此类型转发到,caller而不为其赋予该类型的值。
最后,在caller::<fn() {foo}>表达式closure()desugars to 中FnMut::call_mut(closure)。因为closurehas type &mut FwhereF只是零大小的 type fn() {foo},本身的0值closure根本就没有被使用过1,程序foo直接调用。
同样的逻辑适用于闭包|| println!("Okay..."),它 likefoo有一个匿名的零大小类型,这次称为类似[closure@src/main.rs:2:14: 2:36].
第二个闭包就没有那么幸运了——它的类型不是零大小的,因为它必须包含对变量的引用val。这一次,FnMut::call_mut(closure)实际上需要取消引用closure来完成它的工作。所以它崩溃了2。
1像这样构造一个空引用在技术上是未定义的行为,因此编译器不对该程序的整体行为作出承诺。但是,用0对齐替换一些其他“地址”F可以避免零大小类型(如 )的问题fn() {foo},并提供相同的行为!)
2同样,构造一个空(或悬空)引用是实际承担责任的操作 - 之后,任何事情都会发生。段错误只是一种可能性 - rustc 的未来版本,或者在稍微不同的程序上运行时的相同版本,可能会完全做其他事情!
的类型fn foo() {...}不是函数指针fn(),它实际上是特定于foo. 只要您携带该类型(此处为F),编译器就知道如何调用它而无需任何额外的指针(此类类型的值不携带数据)。不捕获任何内容的闭包以相同的方式工作。当最后一个闭包尝试查找时,它只会变得很冒险,val因为您放置了一个0(大概)指向的指针val应该在的位置。
您可以通过 观察到size_of,在前两次调用中, 的大小closure为零,但在最后一次调用中,在闭包中捕获了某些内容,大小为 8(至少在操场上)。如果大小为 0,则程序不必从NULL指针加载任何内容。
NULL指向引用的指针的有效转换仍然是未定义的行为,但因为类型恶作剧而不是因为内存访问恶作剧:引用NULL本身就是非法的,因为类型的内存布局Option<&T>依赖于值的假设一个参考是从不NULL。这是一个如何出错的示例:
unsafe fn null<T>(_: T) -> &'static mut T {
&mut *(0 as *mut T)
}
fn foo() {
println!("Hello, world!");
}
fn main() {
unsafe {
let x = null(foo);
x(); // prints "Hello, world!"
let y = Some(x);
println!("{:?}", y.is_some()); // prints "false", y is None!
}
}
Run Code Online (Sandbox Code Playgroud)