I am trying to understand the problems with duplicated code for &
and &mut
in getter-type functions. I'm trying to understand whether a particular solution to this problem using a cast inside an unsafe
block would be safe.
The following is an example of the problem. It is taken from the really nice tutorial Learning Rust With Entirely Too Many Linked Lists.
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
})
}
}
It seems to me that you can use a cast in an unsafe
block to solve this problem. The solution seems to have the following properties:
The following is the solution:
// 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())
}
}
These are the arguments for why I think it seems safe:
peek()
is from a known source with a known alias situation.&mut self
there are no refs to its elements.peek()
in unaliased.peek()
does not escape this function body.&
to &mut
doesn't seem to violate the pointer aliasing rules.The same problem is discussed the following question: How to avoid writing duplicate accessor functions for mutable and immutable references in Rust?
This question is different because it asks about details of one specific solution to the problem.
There are other kinds of solutions to the problem, such as this one: Abstracting over mutability in Rust
But all other solutions seem to introduce quite a bit of extra complexity to the code.
The Nomicon asserts strongly that it is always undefined behaviour to cast &
to &mut
, which implies that this solution is not safe. But it makes no attempt whatsoever to explain why.
The Rust Reference states that "Breaking the pointer aliasing rules" is UB. To me it seems like this solution doesn't do that, for reasons given above.
I have the following questions to people with deeper Rust knowledge than myself:
peek_mut
safe?I believe this code will invoke undefined behaviour. To quote the Nomicon:
- Transmuting an & to &mut is UB
- Transmuting an & to &mut is always UB
- No you can't do it
- No you're not special
More to the point, the Rust compiler will mark the return value of peek()
as immutable in the LLVM intermediate representation, and LLVM is free to make optimisations based on this assertion. It might not currently happen in this specific case, but I still think it's undefined behaviour. If you want to avoid the duplication at all costs, you can use a macro.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With