我正在尝试理解get函数中的&
和&mut
重复代码问题。我试图理解是否使用unsafe
块内的强制类型转换来解决这个问题是安全的。
以下是问题的示例,取自非常好的教程使用过多的链接列表学习Rust。
type Link<T> = Option<Box<Node<T>>>;
pub struct List<T> {
head: Link<T>,
}
struct Node<T> {
elem: T,
next: Link<T>,
}
impl<T> List<T> {
// Other methods left out...
// The implementation of peek is simple, but still long enough
// that you'd like to avoid duplicating it if that is possible.
// Some other getter-type functions could be much more complex
// so that you'd want to avoid duplication even more.
pub fn peek(&self) -> Option<&T> {
self.head.as_ref().map(|node| {
&node.elem
})
}
// Exact duplicate of `peek`, except for the types
pub fn peek_mut(&mut self) -> Option<&mut T> {
self.head.as_mut().map(|node| {
&mut node.elem
})
}
}
解决方案
在一个unsafe
块中使用转换似乎可以解决这个问题。该解决方案具有以下特性:
- 它可以以安全的方式完成。
- 它为不安全的实现提供了安全接口。
- 实现简单。
- 它消除了代码重复。
以下是解决方案:
// Implemention of peek_mut by casting return value of `peek()`
pub fn peek_mut(&mut self) -> Option<&mut T> {
unsafe {
std::mem::transmute(self.peek())
}
}
这是我认为它看起来安全的理由:
peek()
的返回值是从已知源具有已知别名情况的位置获取的。- 由于参数类型是
&mut self
,因此不存在对其元素的引用。 - 因此,
peek()
的返回值未被赋予别名。 peek()
的返回值不会逃逸出该函数体。- 将未赋予别名的
&
强制转换为&mut
似乎不违反指针别名规则。 - 转换的目标和源的生命周期相匹配。
其他说明
同样的问题在以下问题中讨论: 如何避免在Rust中为可变和不可变引用编写重复的访问器函数?
这个问题不同,因为它询问一个特定解决方案的细节。
还有其他种类的解决方案,比如这个: Rust中的可变性抽象
但所有其他解决方案似乎都会给代码引入相当多的额外复杂性。
Nomicon强烈断言,将
&
转换为&mut
总是未定义行为,这意味着这个解决方案不安全。但它根本没有尝试解释为什么。Rust参考手册指出,“违反指针别名规则”是UB。对我来说,这个解决方案似乎没有这样做,原因如上所述。
问题
我有以下问题想要询问比我更了解Rust的人士:
peek_mut
提供的实现是否安全?- 是否还需要其他必要的参数来确定其是否安全,我已经漏掉了吗?
- 如果确实不安全,那么原因是什么?你能详细解释一下吗?
- 如果是这样,是否有类似的解决方案是安全的?
peek_raw
函数,该函数返回原始指针,并使用它来编写peek
和peek_mut
- 我认为这是一种避免从&T
转换为&mut T
不安全性的方法,因为原始指针可能与任何东西别名。但是,它生成的代码可能不如简单地两次编写“相同”的(或几乎相同的)代码好。 - trent&
的peek
实现将比peek_raw
更容易工作,因为它不会有正常的Rust安全检查。 - Liipeek
和peek_mut
执行,但在我看来,这与您建议使用transmute
基本相同。 - trent