我在玩零尺寸类型(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完全没有关系吗?
答案 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">×</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, edx
。 ref.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将在不久的将来解决这个问题。