There might be more value in exploring generic versions of the wrapper types you want to avoid than in merely fixing the entry API.
First, we should consider a Cow-centric entry API like:
impl<K, V, S> HashMap<K, V, S> where K: Eq + Hash, S: BuildHasher
{
pub fn entry_cow<'a,Q>(&'a mut self, k: Cow<'a,Q>) -> Entry<'a,K, V>
where Q: ?Sized + Hash + Eq + ToOwned<Owned=K> { .. }
pub fn entry(&mut self, key: K) -> Entry<K, V> where K: Clone {
self.entry_cow(Cow::Owned(key))
}
We’d use Cow internally to VacantEntry too of course.
pub struct VacantEntry<'a, K: 'a, V: 'a> {
hash: SafeHash,
key: Cow<'a,K>,
elem: VacantEntryState<K, V, &'a mut RawTable<K, V>>,
}
Is the only issue here the K: Clone restriction implicit in Cow? If you make an Entry using a borrowed key then sometimes you must either make the owned version or return an error, but the error case no longer requires entry. If you need to make an owned version, then basically you do have functionality like ToOwned, but the problem is that doing ToOwned temporarily might require two wrapper types and six trait impls, yes?
We could define another Cow-like type that specifies the function explicit:
pub enum OneStep<A,B,F: fn(A) -> B> {
First(pub A),
Second(pub B)
}
pub fn one_step<F: F: fn(A) -> B>(a: B,f: F) -> OneStep<A,B,f> { OneStep(a) }
We have a canonical mapping that makes .into() work:
impl<'a,B: 'a + ToOwned + ?Sized> From<Cow<'a,B>> for OneStep<&'a B, <B as ToOwned>::Owned,<B as ToOwned>::to_owned> { .. }
and
pub fn entry_borrowed<'a,Q,F: fn(&'a Q) -> K>(&'a mut self, k: OneStep<&'a Q,K,F>) -> Entry<'a,K, V>
where Q: ?Sized + Hash + Eq { .. }
pub fn entry_ref<'a,Q>(&'a mut self, k: &Q) -> Entry<'a,K, V>
where Q: ?Sized + Hash + Eq + ToOwned<Owned=K> {
self.entry_borrowed(Cow::Borrowed(k).into())
}
pub fn entry(&mut self, key: K) -> Entry<K, V> {
fn do_nothing(k: K) { k }
self.entry_borrowed(one_step(k,do_nothing))
}
There are existing traits like std::ops::Generator<Yield=&'a Q,Return=K> that kinda work too, albeit imperfectly.
I dislike users having control over the hash so directly too, not because they make mistakes, but because it’ll increase their stress level. I’m wondering if some simpler hashing interface plus another state transition might simplify this, ala
pub trait SimpleHasher64<BuildHasher> {
fn hashed(&self) -> u64;
}
pub struct PreHashed64<T,B: BuildHasher = RandomState>(T, Option<u64>)
impl<T,B: BuildHasher> SimpleHasher<B> for PreHashed64<T,B> { .. }
In any case, these issues both look like very simple one-step state machines provide the right abstraction, even if we want the state machine to always compile down to nothing.