零大小类型的引用之间的生命周期差异

时间:2017-01-27 16:43:30

标签: rust

我在玩零尺寸类型(ZST)时遇到了一个有趣的案例。对空数组的引用将模拟为具有任何生命周期的引用:

fn mold_slice<'a, T>(_: &'a T) -> &'a [T] {
    &[]
}

我想到了这是怎么可能的,因为基本上是&#34;值&#34;这里存在于函数的堆栈框架中,但签名承诺返回对具有更长生命周期的值的引用('a包含函数调用)。我得出的结论是,因为空数组[]是一个ZST,它基本上只是静态存在。编译器可以&#34;假&#34;引用所指的值。

所以我尝试了这个:

fn mold_unit<'a, T>(_: &'a T) -> &'a () {
    &()
}

然后编译器抱怨:

error: borrowed value does not live long enough
 --> <anon>:7:6
  |
7 |     &()
  |      ^^ temporary value created here
8 | }
  | - temporary value only lives until here
  |
note: borrowed value must be valid for the lifetime 'a as defined on the block at 6:40...
 --> <anon>:6:41
  |
6 | fn mold_unit<'a, T>(_: &'a T) -> &'a () {
  |                                         ^

它不适用于单位()类型,它也不适用于空结构:

struct Empty;

// fails to compile as well
fn mold_struct<'a, T>(_: &'a T) -> &'a Empty {
    &Empty
}

不知何故,单元类型和空结构的处理方式与空数组不同。除了仅仅是ZST之外,这些值之间是否还有其他差异?这些差异(&[]适合任何生命,而&()&Empty不是)与ZST完全没有关系吗?

Playground example

2 个答案:

答案 0 :(得分:4)

[]不是零大小(尽管它是),[]是一个常量的编译时文字。这意味着编译器可以将其存储在可执行文件中,而不必在堆或堆栈上动态分配它。反过来,这意味着指向它的指针会持续多久,因为可执行文件中的数据不会到达任何地方。

令人讨厌的是,这并没有延伸到像&[0]这样的东西,因为Rust并不是非常聪明地认识到[0]肯定是常数。您可以使用以下内容解决此问题:

fn mold_slice<'a, T>(_: &'a T) -> &'a [i32] {
    const C: &'static [i32] = &[0];
    C
}

这个技巧也适用于任何,你可以放在const中,例如()Empty

但实际上,让这样的函数返回&'static借用会更简单,因为这可以自动强制转换为其他生存期。

修改:之前的版本指出&[]不是零大小,这有点切线。

答案 1 :(得分:2)

  

差异(<li data-toggle="modal" data-target="#myModal" posts_id="<?php echo $post_id; ?>" id="dynamicdata"><a href="#" >See post</a></li> <div id="myModal" class="modal fade" role="dialog"> <div class="modal-dialog"> <!-- Modal content--> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal">&times;</button> <h4 class="modal-title">Post</h4> </div> <div class="modal-body" id="mymodelbody"> // include the posts.php here </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> </div> </div> </div> </div>适合任何生命,而&[]&()没有)与ZST完全没有关系吗?

我认为情况确实如此。编译器可能只是以不同的方式处理数组,并且背后有没有更深层次的推理

唯一可能起作用的区别是&Empty是一个胖指针,由数据指针和长度组成。这个胖指针本身表示它背后实际上没有数据(因为长度= 0)。另一方面,&[]只是一个普通的指针。在这里,类型系统表达了它没有指向任何真实的事实。但我只是在这里猜测。

澄清:任何生命周期的引用拟合意味着引用的生命周期为&()。因此,我们可以返回一个静态引用而不是引入一些生命周期'static,并且具有相同的效果('a可以正常工作,其他人不会这样做。

an RFC指定对constexpr rvalues的引用将存储在可执行文件的静态数据部分中,而不是存储在堆栈中。在实施此RFC(tracking issue)之后,您的所有示例都将进行编译,因为&[][]()是constexpr rvalues。对它的引用始终为Empty。但RFC的重要部分是它也适用于非ZST:例如'static的类型为&27

为了获得乐趣,让我们看看生成的程序集(我使用了惊人的Compiler Explorer)!首先让我们试试工作版:

&'static i32

使用pub fn mold_slice() -> &'static [i32] { &[] } 标志(意思是:启用了优化;我也检查了未经优化的版本,并且它没有显着差异),这被编译为:

-O

胖指针在mold_slice: push rbp mov rbp, rsp lea rax, [rip + ref.0] xor edx, edx pop rbp ret ref.0: (数据指针)和rax(长度)寄存器中返回。如您所见,长度设置为0(rdx),数据指针设置为此神秘xor edx, edxref.0实际上并没有引用任何内容。它只是一个空标记。这意味着我们只返回一些指向数据部分的指针。

现在让我们告诉编译器在ref.0上信任我们以便编译它:

&()

结果:

pub fn possibly_broken() -> &'static () {
    unsafe { std::mem::transmute(&()) } 
}
哇,我们几乎看到了同样的结果!指针(通过possibly_broken: push rbp mov rbp, rsp lea rax, [rip + ref.1] pop rbp ret ref.1: 返回)指向数据部分。所以它实际上是代码生成后的rax引用。只有终身检查器不能完全知道并且仍然拒绝编译代码。嗯......我想这没什么大不了的,特别是因为上面提到的RFC将在不久的将来解决这个问题。