在Rust中,Option是否编译为运行时检查或指令跳转?

Dai*_*Dai 7 compiler-construction compiler-optimization rust

在Rust中,Option定义为:

pub enum Option<T> {
    None,
    Some(T),
}
Run Code Online (Sandbox Code Playgroud)

像这样使用:

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"),
    }
}
Run Code Online (Sandbox Code Playgroud)

我不熟悉Rust内部,但最初我认为它可能与Nullable.NET 类似,所以我上面的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");
}
Run Code Online (Sandbox Code Playgroud)

然而,由于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")
}
Run Code Online (Sandbox Code Playgroud)

这是如何实现的?这种方法也适用enum于Rust中的其他类型 - 但是我已经演示的这个特定应用程序非常脆弱,如果你想在调用mayReturnNonematch语句之间执行代码,例如(因为它mayReturnNone会直接跳转到match,跳过)中间指示).

She*_*ter 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();
}
Run Code Online (Sandbox Code Playgroud)

在这里,我使用内联汇编而不是打印,因为它不会使得到的输出混乱.这是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
Run Code Online (Sandbox Code Playgroud)

快速简介是:

  1. 它调用is_full_moon函数(callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E).
  2. 测试随机值的结果(testb %al, %al)
  3. 一个分支进入nop,另一个进入nop; nop

其他一切都已经过优化.该功能may_return_none基本上不存在; 没有Option创造过,价值1永远不会实现.

我确信不同的人有不同的意见,但认为我不能写得更优化了.


同样,如果我们使用Some(我更改为42更容易找到)中的值:

Some(v) => unsafe { asm!("nop; nop" : : "r"(v)) },
Run Code Online (Sandbox Code Playgroud)

然后在使用它的分支中内联该值:

    .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
Run Code Online (Sandbox Code Playgroud)

但是,没有任何东西可以围绕合同义务"优化"; 如果一个函数必须返回一个Option,它必须返回一个Option:

#[inline(never)]
pub fn may_return_none() -> Option<i32> {
    if is_full_moon() { None } else { Some(42) }
}
Run Code Online (Sandbox Code Playgroud)

这使得一些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
Run Code Online (Sandbox Code Playgroud)

让我们希望我做对了......

  1. 将64位值0x2A00000000加载到%rdx.0x2A是42.这是我们Option正在建造的; 这是None变种.
  2. 将%rdx + 1加载到%rcx中.这是Some变种.
  3. 我们测试随机值
  4. 根据测试结果,将无效值移动到%rcx或不移动
  5. 将%rcx移动到%rax - 返回寄存器

这里的要点是,无论优化如何,一个表明它将以特定格式返回数据的函数必须这样做.只有当它与其他代码内联时,删除该抽象才有效.