在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
的调用之间执行代码就会中断例如{1}}语句(因为mayReturnNone
将直接跳转到match
,跳过中间指令)。
答案 0 :(得分:8)
完全取决于优化。考虑这个实现(playground):
#![feature(asm)]
extern crate rand;
use rand::Rng;
#[inline(never)]
fn is_full_moon() -> bool {
rand::thread_rng().gen()
}
fn may_return_none() -> Option<i32> {
if is_full_moon() { None } else { Some(1) }
}
#[inline(never)]
fn usage() {
let optional = may_return_none();
match optional {
None => unsafe { asm!("nop") },
Some(v) => unsafe { asm!("nop; nop") },
}
}
fn main() {
usage();
}
在这里,我使用了内联汇编而不是打印,因为它不会使得到的输出混乱不堪。这是在发布模式下编译时usage
的程序集:
.section .text._ZN10playground5usage17hc2760d0a512fe6f1E,"ax",@progbits
.p2align 4, 0x90
.type _ZN10playground5usage17hc2760d0a512fe6f1E,@function
_ZN10playground5usage17hc2760d0a512fe6f1E:
.cfi_startproc
pushq %rax
.Ltmp6:
.cfi_def_cfa_offset 16
callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
testb %al, %al
je .LBB1_2
#APP
nop
#NO_APP
popq %rax
retq
.LBB1_2:
#APP
nop
nop
#NO_APP
popq %rax
retq
.Lfunc_end1:
.size _ZN10playground5usage17hc2760d0a512fe6f1E, .Lfunc_end1-_ZN10playground5usage17hc2760d0a512fe6f1E
.cfi_endproc
快速纲要是:
is_full_moon
函数(callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
)。testb %al, %al
)nop
,另一个分支转到nop; nop
其他一切都已经过优化。函数may_return_none
基本上不存在;没有创建Option
,1
的值从未实现。
我确信不同的人有不同的意见,但我认为我可以写得更优化。
同样,如果我们使用Some
中的值(我更改为42更容易找到):
Some(v) => unsafe { asm!("nop; nop" : : "r"(v)) },
然后在使用它的分支中内联该值:
.section .text._ZN10playground5usage17hc2760d0a512fe6f1E,"ax",@progbits
.p2align 4, 0x90
.type _ZN10playground5usage17hc2760d0a512fe6f1E,@function
_ZN10playground5usage17hc2760d0a512fe6f1E:
.cfi_startproc
pushq %rax
.Ltmp6:
.cfi_def_cfa_offset 16
callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
testb %al, %al
je .LBB1_2
#APP
nop
#NO_APP
popq %rax
retq
.LBB1_2:
movl $42, %eax ;; Here it is
#APP
nop
nop
#NO_APP
popq %rax
retq
.Lfunc_end1:
.size _ZN10playground5usage17hc2760d0a512fe6f1E, .Lfunc_end1-_ZN10playground5usage17hc2760d0a512fe6f1E
.cfi_endproc
然而,没有什么能够优化&#34;围绕合同义务;如果某个函数必须返回Option
,则必须返回Option
:
#[inline(never)]
pub fn may_return_none() -> Option<i32> {
if is_full_moon() { None } else { Some(42) }
}
这会产生一些Deep Magic程序集:
.section .text._ZN10playground15may_return_none17ha1178226d153ece2E,"ax",@progbits
.p2align 4, 0x90
.type _ZN10playground15may_return_none17ha1178226d153ece2E,@function
_ZN10playground15may_return_none17ha1178226d153ece2E:
.cfi_startproc
pushq %rax
.Ltmp6:
.cfi_def_cfa_offset 16
callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
movabsq $180388626432, %rdx
leaq 1(%rdx), %rcx
testb %al, %al
cmovneq %rdx, %rcx
movq %rcx, %rax
popq %rcx
retq
.Lfunc_end1:
.size _ZN10playground15may_return_none17ha1178226d153ece2E, .Lfunc_end1-_ZN10playground15may_return_none17ha1178226d153ece2E
.cfi_endproc
让我们希望我做对了......
Option
;它是None
变体。Some
变体。这里的要点是,无论优化如何,一个表示要以特定格式返回数据的功能都必须这样做。只有当它与其他代码一起内联时,删除该抽象才有效。
答案 1 :(得分:2)
您可以查看Rust playground
上的代码该函数编译为:
.cfi_startproc
pushq %rbp
.Ltmp6:
.cfi_def_cfa_offset 16
.Ltmp7:
.cfi_offset %rbp, -16
movq %rsp, %rbp
.Ltmp8:
.cfi_def_cfa_register %rbp
subq $16, %rsp
.Ltmp9:
.loc 1 6 0 prologue_end
callq is_full_moon@PLT
movb %al, -9(%rbp)
movb -9(%rbp), %al
testb $1, %al
jne .LBB1_3
jmp .LBB1_4
.LBB1_3:
.loc 1 7 0
movl $0, -8(%rbp)
.loc 1 6 0
jmp .LBB1_5
.LBB1_4:
.loc 1 10 0
movl $1, -8(%rbp)
movl $1, -4(%rbp)
.LBB1_5:
.loc 1 12 0
movq -8(%rbp), %rax
addq $16, %rsp
popq %rbp
retq
.Ltmp10:
.Lfunc_end1:
.size _ZN8rust_out15may_return_none17hb9719b83eae05d85E, .Lfunc_end1-_ZN8rust_out15may_return_none17hb9719b83eae05d85E
.cfi_endproc
这并没有真正回到不同的地方。 Option<i32>
的空格也包含i32
值。这意味着您的功能只是编写None/Some
标记:
movl $0, -8(%rbp)
或者也是值:
movl $1, -8(%rbp)
movl $1, -4(%rbp)
所以我想你的问题的答案是:
Rust提出了提供零成本抽象的观点
是一个不适用于每一个案例的假设。