免责声明:此类事情的规则尚未一成不变。所以,目前还没有明确的答案。我将根据(a)LLVM 所做的/我们最终想要做的编译器转换类型,以及(b)我脑海中定义答案的模型类型进行一些猜测。
另外,我看到了两个部分:数据布局透视图和别名透视图。布局问题是NotReallyImmutable
原则上,可以有一个完全不同的布局ReallyImmutable
。我对数据布局不太了解,但是UnsafeCell
变得repr(transparent)
这是两种类型之间的唯一区别,我认为intent是为了这个工作。然而,你依赖于repr(transparent)
“结构性”是指它应该允许您替换更大类型的事物,我不确定是否已在任何地方明确地写下这些内容。听起来像是后续 RFC 的提案,该提案扩展了repr(transparent)
适当保证?
就别名而言,问题在于违反规则&T
。我会这么说,只要你从未有过现场表演&T
通过书写时可以在任何地方&UnsafeCell<T>
,你很好——但我认为我们还不能保证这一点。让我们更详细地看看。
编译器视角
这里的相关优化是利用&T
是只读的。因此,如果您重新排序最后两行(transmute
和赋值),该代码可能是 UB,因为我们可能希望编译器能够“预取”共享引用后面的值并稍后重新使用该值(即在内联后)。
但在您的代码中,我们只会发出“只读”注释(noalias
在 LLVM 中)之后transmute
返回,数据确实从那里开始是只读的。所以,这应该是好的。
内存型号
我的记忆模型本质上是“最激进的”断言所有值始终有效,我认为即使该模型也应该适合您的代码。&UnsafeCell
是该模型中的一个特例,有效性就停止了,并且没有提及该引用背后的内容。此刻transmute
返回时,我们抓取它指向的内存并将其全部设为只读,即使我们通过“递归”执行此操作Rc
(我的模型没有,但只是因为我找不到一个好的方法来让它这样做)你会没事的,因为你不会再变异了transmute
。 (您可能已经注意到,这与编译器角度的限制相同。毕竟,这些模型的目的是允许编译器优化。;)
(作为旁注,我真的希望 miri 现在状况更好。似乎我必须尝试并在那里再次进行验证,因为那样我可以告诉你只在 miri 中运行你的代码,它会告诉你如果我的模型的那个版本适合你正在做的事情,那就是你:D)
我正在考虑目前仅检查“访问时”内容的其他模型,但尚未解决UnsafeCell
该模型的故事还没有。这个例子表明,模型可能必须首先包含内存“相变”的方法UnsafeCell
,但后来具有只读保证的正常共享。感谢您提出这个问题,这将成为一些值得思考的好例子!
所以,我想我可以说(至少从我这边)intent允许这种代码,这样做似乎不会阻止任何优化。我无法预测我们是否真的能够找到一个每个人都能同意并且仍然允许这样做的模型。
相反的方向:T -> UnsafeCell<T>
现在,这更有趣了。问题是,正如我上面所说,你不能有一个&T
通过写作时生活UnsafeCell<T>
。但这里的“生活”是什么意思呢?这是一个很难回答的问题!在我的一些模型中,这可能与“该类型的引用存在于某处并且生命周期仍然处于活动状态”一样弱,即,它可能与引用是否实际上无关used。 (这很有用,因为它可以让我们进行更多优化,例如将负载移出循环,即使我们无法证明循环曾经运行过 - 这将引入对其他未使用的引用的使用。)&T
is Copy
,你甚至无法真正摆脱这样的引用。所以,如果你有x: &T
, 然后let y: &UnsafeCell<T> = transmute(x)
, 老人x
仍然存在并且它的生命周期仍然活跃,所以写通过y
很可能是UB。
我认为你必须以某种方式限制别名&T
允许,非常小心地确保没有人仍然持有这样的参考。我不会说“这是不可能的”,因为人们总是让我感到惊讶(尤其是在这个社区中;)但说实话,我想不出一种方法来实现这项工作。我很好奇你是否有一个你认为这是合理的例子。