Reputation: 63
I'm currently trying to implement a mutable slice/view into a buffer that supports taking subslices safely for in-memory message traversal. A minimal example would be
struct MutView<'s> {
data: &'s mut [u8]
}
impl<'s> MutView<'s> {
pub fn new(data: &'s mut [u8]) -> Self {
MutView { data }
}
pub fn subview<'a>(&'a mut self, start: usize, end: usize) -> MutView<'a> {
MutView::new(&mut self.data[start..end])
}
pub fn get<'a>(&'a mut self) -> &'a mut [u8] {
self.data
}
}
However there is a problem with this design, in that if I have a MutView<'s>
that is created locally in a function and 's is a longer than the scope of the function (say 's = 'static) I have no way of returning a subview from the function. Something like
fn get_subview(data: &'s mut [u8]) -> MutView<'s> {
MutView::new(data).subview(0, 5)
}
Gives a compile error since MutView::new(data)
is a local temporary, so clearly MutView<'a>
returned by subview()
cannot be returned from the function.
Changing the signature of subview()
to
pub fn subview<'a>(&'a mut self, start: usize, end: usize) -> MutView<'s> {
MutView::new(&mut self.data[start..end])
}
Isn't possible since the borrow checker complains that the lifetime of the returned object must be longer than the lifetime of &'a mut self
.
The problem I see is that the borrow checker is set up to handle cases where subview()
is returning data owned by the &'a mut self
, whereas in this case I am returning data owned by some underlying buffer that outlives lifetime 'a, so for safety reasons I still want to do a mutable borrow of &'a mut self
for the duration of the lifetime of returned object while also allowing the &'a mut self
to be dropped without shortening the lifetime of the returned subview.
The only option I see is to add an into_subview
and into_slice
method that consumes self. However for my specific case I am generating get_/set_ methods for reading messages with a given schema, meaning I have get/set methods per field in the message body and adding an extra into_* method means a lot of extra code to generate/compile, as well as additional usage complexity. Therefore I would like to avoid this if possible.
Is there a good way of handling this kind of dependency currently in Rust?
Upvotes: 6
Views: 157
Reputation: 60537
Returning a MutView<'s>
from subview
is unsound.
It would allow users to call subview
multiple times and yield potentially overlapping ranges which would violate Rust's referential guarantees that mutable references are exclusive. This can be done easily with immutable references since they can be shared, but there are much stricter requirements for mutable references. For this reason, mutable references derived from self
must have their lifetime bound to self
in order to "lock out" access to it while the mutable borrow is still in use. The compiler is enforcing that by telling you &mut self.data[..]
is &'a mut [u8]
instead of &'s mut [u8]
.
The only option I see is to add an
into_subview
andinto_slice
method that consumes self.
That the main option I see, the key part you need to need to guarantee is exclusivity, and consuming self
would remove it from the equation. You can also take inspiration from the mutable methods on slices like split_mut
, split_at_mut
, chunks_mut
, etc. which are carefully designed to get multiple mutable elements/sub-slices at the same time.
You could use std::mem::transmute
to force the lifetimes to be what you want (Warning: transmute
is very unsafe
and is easy to use incorrectly), however, you then are burdened with upholding the referential guarantees mentioned above yourself. The subview() -> MutView<'s>
function should then be marked unsafe
with the safety requirement that the ranges are exclusive. I do not recommend doing that except in exceptional cases where you are returning multiple mutable references and have checked that they don't overlap.
I'd have to see exactly what kind of API you're hoping to design to give better advice.
Upvotes: 3