Here is one possible implementation. [playground]
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use std::ptr;
fn get(map: HashMap<(String, String), i64>, a: &String, b: &String) -> Option<i64> {
unsafe {
// The 24-byte string headers of `a` and `b` may not be adjacent in
// memory. Copy them (just the headers) so that they are adjacent. This
// makes a `(String, String)` backed by the same data as `a` and `b`.
let k = (ptr::read(a), ptr::read(b));
// Make sure not to drop the strings, even if `get` panics. The caller
// or whoever owns `a` and `b` will drop them.
let k = ManuallyDrop::new(k);
// Deref `k` to get `&(String, String)` and perform lookup.
let v = map.get(&k);
// Turn `Option<&i64>` into `Option<i64>`.
v.cloned()
}
}
fn main() {
let mut map = HashMap::new();
map.insert(("a".to_owned(), "b".to_owned()), 2);
map.insert(("c".to_owned(), "d".to_owned()), 3);
let a = "a".to_owned();
let b = "b".to_owned();
println!("{:?}", get(map, &a, &b));
}