我在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));
}
我无法解释为什么调用foo
的方法是在调用方(…)中投射空指针
到类型为
F
的实例。我本以为函数只能通过相应的函数指针来调用,但鉴于指针本身为空,显然不能这样。话虽如此,我显然误解了Rust类型系统的一个重要部分。
操场上的例子
鉴于生锈是建立在LLVM之上的,并且您所做的是保证UB的,您可能会碰到类似https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-never.html.这是安全生锈能够消除所有UB的许多原因之一。
fn foo(){...}
的类型不是函数指针fn()
,它实际上是特定于foo
的唯一类型。只要您携带该类型(这里是F
),编译器就知道如何调用它,而不需要任何额外的指针(这种类型的值不携带数据)。不捕获任何东西的闭包也是如此。只有当最后一个闭包试图查找val
时,它才会变得危险,因为您将一个0
放在(大概)指向val
的指针应该在的地方。
您可以通过size_of
观察到这一点,在前两次调用中,闭包
的大小为零,但在最后一次调用中,闭包中捕获的东西的大小为8(至少在操场上)。如果大小为0,则程序不必从NULL
指针加载任何内容。
指向引用的NULL
指针的有效强制转换仍然是未定义的行为,但这是因为类型欺诈,而不是因为内存访问欺诈:拥有真正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!
}
}
这个程序实际上根本没有构造函数指针——它总是直接调用foo
和这两个闭包。
每个Rust函数,无论是闭包还是fn
项,都有一个唯一的匿名类型。此类型根据需要实现Fn
/FnMut
/FnOnce
特征。fn
项的匿名类型是零大小的,就像没有捕获的闭包类型一样。
因此,表达式create(foo)
用foo
的类型实例化create
的参数F
——这不是函数指针类型fn()
,而是一个匿名的零大小类型,仅用于foo
。在错误消息中,rustc调用此类型fn(){foo}
,您可以看到此错误消息。
内部create::
最后,在
调用者中:
同样的逻辑也适用于闭包
||println!("好的...")
,它像foo
有一个匿名的零大小类型,这次称为类似于[closure@src/main.rs: 2:14:2:36]
。
第二个闭包就不那么幸运了——它的类型不是零大小的,因为它必须包含对变量
val
的引用。这次,FnMut::call_mut(closure)
实际上需要取消引用closure
来完成它的工作。所以它崩溃了2。
1像这样构造空引用在技术上是未定义的行为,因此编译器不对该程序的总体行为做出promise。但是,将
0
替换为具有F
对齐方式的其他“地址”,可以避免像fn(){foo}
这样的零大小类型的问题,并给出相同的行为!)
2同样,构造一个空(或悬挂)引用是实际承担责任的操作——在那之后,任何事情都会发生。一个段落错误只是一种可能性-一个未来版本的rustc,或者在一个稍微不同的程序上运行的相同版本,可能会做完全不同的事情!
可能重复: 通过空类指针调用类方法 显然,不会有人打电话给他们。这是标准吗?或者只是一些编译器优化,因为这个指针没有在show()成员函数中使用?
为什么允许使用memcpy指针更改常量变量? 此代码: 编译时只包含一个警告: 警告:传递“memcpy”的参数 1 将丢弃指针目标类型中的“const”限定符 [默认启用] 但运行得很好,并更改了常量变量的值。
问题内容: 除了JSONP,为什么要遵循相同的域策略? 问题答案: 出于安全原因,已实施“同源起源策略”;引用维基百科的相关句子: 这种机制对现代Web应用程序具有特殊的意义,因为Web服务器广泛依赖于HTTP cookie来维护经过身份验证的用户会话,因为服务器基于HTTP cookie信息进行操作以揭示敏感信息或执行状态更改操作。 必须在客户端维护不相关站点提供的内容之间的严格分隔,以防止丢失
问题内容: 如JDK文档中所指定,Hashtable不允许空键或值。HashMap允许一个null键和任意数量的null值。为什么是这样? 问题答案: Hashtable是较老的类,通常不鼓励使用Hashtable。也许他们看到了对null键的需求,更重要的是对null值的需求,并将其添加到HashMap实现中。 HashMap是较新的,并且具有更高级的功能,这些基本上只是对Hashtable功能
通过将函数传递给函数call by pointer方法call by pointer将参数的地址复制到形式参数中。 在函数内部,该地址用于访问调用中使用的实际参数。 这意味着对参数所做的更改会影响传递的参数。 要通过指针传递值,参数指针将像任何其他值一样传递给函数。 因此,您需要将函数参数声明为指针类型,如以下函数swap() ,该函数swap()其参数指向的两个整数变量的值。 // functi
问题内容: 我刚接触PHP,但是多年来我一直在使用类似的语言进行编程。我被以下内容弄糊涂了: 它产生了语法错误:这就是调用。 但这很好用: 碰了一会儿之后,我被告知您不能在默认属性中调用函数。你必须在做。我的问题是:为什么?这是“功能”还是草率的实现?有什么根据? 问题答案: 编译器代码建议这是设计使然,尽管我不知道其背后的官方原因是什么。我也不确定要可靠地实现此功能需要花费多少精力,但是目前完成