在Rust中,Option
被定义为:
pub enum Option<T> {
None,
Some(T),
}
用法如下:
fn may_return_none() -> Option<i32> {
if is_full_moon {
None
} else {
Some(1)
}
}
fn main() {
let optional = may_return_none();
match optional {
None => println!("None"),
Some(v) => println!("Some"),
}
}
我不熟悉Rust的内部结构,但最初我假设它可能类似于.NET中的Nullable
,因此我上面的Rust代码的编译逻辑如下所示:
// occupies `sizeof(T) + 1` memory space, possibly more depending on `Bool`'s alignment, so `Nullable<Int32>` consumes 5 bytes.
struct Nullable<T> {
Bool hasValue;
T value;
}
Nullable<Int32> MayReturnNone() {
if( isFullMoon )
// as a `struct`, the Nullable<Int32> instance is returned via the stack
return Nullable<Int32>() { HasValue = false }
else
return Nullable<Int32>() { HasValue = true, Value = 1 }
}
void Test() {
Nullable<Int32> optional = may_return_none();
if( !optional.HasValue ) println("None");
else println("Some");
}
然而,这不是一个零成本的抽象,因为Bool hasValue
标志需要空间-Rust强调提供零成本的抽象。
我意识到Option
可以通过编译器的直接返回跳转来实现,尽管它需要在堆栈上提供确切的跳转值作为参数-就好像你可以推送多个返回地址一样:
(伪代码)
mayReturnNone(returnToIfNone, returnToIfHasValue) {
if( isFullMoon ) {
cleanup-current-stackframe
jump-to returnToIfNone
else {
cleanup-current-stackframe
push-stack 1
jump-to returnToIfHasValue
}
test() {
mayReturnNone( instructionAddressOf( ifHasValue ), instructionAddressOf( ifNoValue ) )
ifHasValue:
println("Some")
ifNoValue:
println("None")
}
这种方法也适用于Rust中的其他enum
类型-但我演示的这个特定应用程序非常脆弱,如果你想在调用mayReturnNone
和match
语句之间执行代码,就会中断(因为mayReturnNone
将直接跳转到match
,跳过中间指令)。
3条答案
按热度按时间llew8vvj1#
这完全取决于优化。考虑以下实现(playground):
在这里,我使用了内联汇编而不是打印,因为它不会使结果输出变得混乱。下面是
usage
在release模式下编译时的汇编:快速运行是:
1.它调用
is_full_moon
函数(callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
)。1.测试随机值的结果(
testb %al, %al
)1.一个分支连接到
nop
,另一个连接到nop; nop
其他的都已经优化掉了。函数
may_return_none
基本上不存在;Option
从未被创建,1
的值从未被具体化。我相信不同的人有不同的意见,但我不认为我可以写这个更优化。
同样,如果我们使用
Some
中的值(为了更容易找到,我将其改为42):然后该值被内联到使用它的分支中:
但是,没有什么可以围绕一个契约性义务“优化”;如果一个函数必须返回一个
Option
,* 它必须返回一个Option
*:这会生成一些Deep Magic程序集:
希望我没弄错...
1.将64位值0x 2A 00000000加载到%rdx。0x 2A是42。这是我们正在构建的
Option
;是None
的变种1.将%rdx + 1加载到%rcx。这是
Some
变体。1.我们测试随机值
1.根据测试的结果,是否将无效值移动到%rcx
1.将%rcx移动到%rax -返回寄存器
这里的要点是,不管优化如何,一个函数说它将以特定的格式返回数据,它必须这样做。只有当它与其他代码内联时,才能有效地删除该抽象。
cu6pst1q2#
警告:这来自调试版本,而不是发布版本。请参阅另一个优化版本的答案,其行为不同。
你可以在Rust playground上检查代码
该函数编译为:
这并不是真的返回到不同的地方。
Option<i32>
的空间也包含i32
值。这意味着你的函数只写None/Some
标记:或者值:
所以我想你问题的答案是:
Rust强调提供零成本的抽象
是一个并不适用于每一个案例的假设
js81xvg63#
有点晚的党,但我谷歌了同样的事情,我相信这个线程缺乏一些点。
如果给定枚举类型存在无效值,Rust优化器可以省略判别式。这是编译器对所有枚举类型的优化,而不仅仅是
Option
。像Option<bool>
或Option<&mut T>
这样的东西不会存储额外的标志,但是Option<i32>
会,因为i32
没有无效值。在Rust术语中,这种无效的位模式被称为 niche。
当然,其他优化也可以应用,正如上面已经提到的那样。编译器可以将常量传播等优化应用于elide
Option
,但这超出了这个线程的范围。旁注:这实际上与C程序员所做的非常相似。可选指针通常表示为
NULL
,但可空的int
有点问题,一些函数(如open
)选择返回在给定情况下没有意义的值,如-1
,但由于明显的原因,这是不可能自动完成的。参考文献: