UPD Ignore this reply. I'll leave it here as an example of stupidity and how it is a bad idea to consider complicated things when already tired... Of course, keys of BtreeMap
are immutable and what I wanted to achieve won't work without inner mutability pattern which would be too expensive to make sense. It was a while since I was able to work on the project, enough to forget certain details. Now I need to think twice if in-place replacement is still reasonable or it is utterly excessive... Thanks for letting me to have a second look into own code.
It'd be too much for a playground, but declarations alone should be sufficient to demonstrate my plan.
#[derive(Hash, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub enum BorOw<'a, B>
where
B: ToOwned + ?Sized + 'a,
<B as ToOwned>::Owned: Clone + Hash + Debug + PartialEq + Eq + Ord + PartialOrd + DeserializeOwned,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ValueString<'a>(BorOw<'a, str>);
#[derive(Debug, Clone, PartialEq)]
pub enum MapBackend<'a> {
#[cfg(feature = "ordered_objects")]
Ordered(IndexMap<ValueString<'a>, Value<'a>>),
Unordered(BTreeMap<ValueString<'a>, Value<'a>>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Map<'a>(MapBackend<'a>);
ValueString
and Map
implement a trait ValueBorOw
:
pub trait ValueBorOw<'a> {
type Owned: 'a;
fn own(self) -> Self::Owned;
fn make_owned(&mut self);
fn clone_owned(&self) -> Self::Owned;
}
BTW, clone_owned
here is exactly what's suggested. But the trick is about make_owned
which is responsible for modifying objects in place without changing life time; and own
is the same, but changes it. Map
does it like this:
fn own(mut self) -> Self::Owned {
self.make_owned();
unsafe { mem::transmute(self) }
}
fn make_owned(&mut self) {
match &mut self.0 {
#[cfg(feature = "ordered_objects")]
MapBackend::Ordered(map) => {
let old_map = std::mem::replace(map, IndexMap::new());
for (mut k, mut v) in old_map {
k.make_owned();
v.make_owned();
map.insert(k, v);
}
}
MapBackend::Unordered(map) => {
let old_map = std::mem::replace(map, BTreeMap::new());
for (mut k, mut v) in old_map {
k.make_owned();
v.make_owned();
map.insert(k, v);
}
}
}
}
Since I guarantee that ValueString
shares the same hash value for both borrowed and owned variants, I make sure that hash internal structures remain intact and the only allocation that takes place is creation of new String
objects. But the latter is, obviously, inevitable.
Another point is this implementation of ValueBorOw
trait (clone_owned
is intentionally omitted):
impl<'b, T> ValueBorOw<'b> for Box<T>
where
T: for<'a> ValueBorOw<'a>,
{
type Owned = Box<<T as ValueBorOw<'b>>::Owned>;
#[inline]
fn own(mut self) -> Self::Owned {
self.as_mut().make_owned();
unsafe { std::mem::transmute(self) }
}
#[inline]
fn make_owned(&mut self) {
self.as_mut().make_owned();
}
}
Though I strongly suspect now that replacing Box
content would be as fast as the in-place replacement.