Repository: dtolnay/cxx
Pull request #522 (2020-11-28 02:14:34)

Support derive(Hash)
Part of #109.

`derive(Hash)` on a shared struct turns into an explicit specialization of https://en.cppreference.com/w/cpp/utility/hash which routes to the type's `Hash` trait impl in Rust.

```cpp
namespace std {
template <> struct hash<::your::Struct> {
  size_t operator()(const ::your::Struct &self) const noexcept {
    return ::your::cxxbridge1$Struct$operator$hash(self);
  }
}
}
```


Patch:
diff --git a/gen/src/write.rs b/gen/src/write.rs
index ab9d3c02..873904db 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -134,6 +134,8 @@ fn write_functions<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
                 _ => {}
             }
         }
+
+        write_std_specializations(out, apis);
     }
 
     for api in apis {
@@ -148,6 +150,36 @@ fn write_functions<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
     }
 }
 
+fn write_std_specializations(out: &mut OutFile, apis: &[Api]) {
+    out.set_namespace(Default::default());
+    out.begin_block(Block::Namespace("std"));
+
+    for api in apis {
+        if let Api::Struct(strct) = api {
+            if derive::contains(&strct.derives, Trait::Hash) {
+                out.next_section();
+                let qualified = strct.name.to_fully_qualified();
+                writeln!(out, "template <> struct hash<{}> {{", qualified);
+                writeln!(
+                    out,
+                    "  size_t operator()(const {} &self) const noexcept {{",
+                    qualified,
+                );
+                let link_name = mangle::operator(&strct.name, "hash");
+                write!(out, "    return ::");
+                for name in &strct.name.namespace {
+                    write!(out, "{}::", name);
+                }
+                writeln!(out, "{}(self);", link_name);
+                writeln!(out, "  }}");
+                writeln!(out, "}};");
+            }
+        }
+    }
+
+    out.end_block(Block::Namespace("std"));
+}
+
 fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
     for api in apis {
         if let Api::Include(include) = api {
@@ -432,6 +464,15 @@ fn write_struct_operator_decls<'a>(out: &mut OutFile<'a>, strct: &'a Struct) {
         }
     }
 
+    if derive::contains(&strct.derives, Trait::Hash) {
+        let link_name = mangle::operator(&strct.name, "hash");
+        writeln!(
+            out,
+            "size_t {}(const {} &) noexcept;",
+            link_name, strct.name.cxx,
+        );
+    }
+
     out.end_block(Block::ExternC);
 }
 
diff --git a/macro/src/derive.rs b/macro/src/derive.rs
index be8ad821..a015ed03 100644
--- a/macro/src/derive.rs
+++ b/macro/src/derive.rs
@@ -16,6 +16,7 @@ pub fn expand_struct(strct: &Struct, actual_derives: &mut Option<TokenStream>) -
             Trait::Debug => expanded.extend(struct_debug(strct, span)),
             Trait::Default => expanded.extend(struct_default(strct, span)),
             Trait::Eq => traits.push(quote_spanned!(span=> ::std::cmp::Eq)),
+            Trait::Hash => traits.push(quote_spanned!(span=> ::std::hash::Hash)),
             Trait::Ord => expanded.extend(struct_ord(strct, span)),
             Trait::PartialEq => traits.push(quote_spanned!(span=> ::std::cmp::PartialEq)),
             Trait::PartialOrd => expanded.extend(struct_partial_ord(strct, span)),
@@ -56,6 +57,7 @@ pub fn expand_enum(enm: &Enum, actual_derives: &mut Option<TokenStream>) -> Toke
                 traits.push(quote_spanned!(span=> ::std::cmp::Eq));
                 has_eq = true;
             }
+            Trait::Hash => traits.push(quote_spanned!(span=> ::std::hash::Hash)),
             Trait::Ord => expanded.extend(enum_ord(enm, span)),
             Trait::PartialEq => {
                 traits.push(quote_spanned!(span=> ::std::cmp::PartialEq));
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index e065a554..c2182673 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -230,6 +230,19 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                     });
                 }
             }
+            Trait::Hash => {
+                let link_name = mangle::operator(&strct.name, "hash");
+                let local_name = format_ident!("__operator_hash_{}", strct.name.rust);
+                operators.extend(quote_spanned! {span=>
+                    #[doc(hidden)]
+                    #[export_name = #link_name]
+                    extern "C" fn #local_name(this: &#ident) -> usize {
+                        let mut hasher = ::std::collections::hash_map::DefaultHasher::new();
+                        ::std::hash::Hash::hash(this, &mut hasher);
+                        ::std::hash::Hasher::finish(&hasher) as usize
+                    }
+                });
+            }
             _ => {}
         }
     }
diff --git a/syntax/derive.rs b/syntax/derive.rs
index a8b6bde5..11212110 100644
--- a/syntax/derive.rs
+++ b/syntax/derive.rs
@@ -13,6 +13,7 @@ pub enum Trait {
     Debug,
     Default,
     Eq,
+    Hash,
     Ord,
     PartialEq,
     PartialOrd,
@@ -26,6 +27,7 @@ impl Derive {
             "Debug" => Trait::Debug,
             "Default" => Trait::Default,
             "Eq" => Trait::Eq,
+            "Hash" => Trait::Hash,
             "Ord" => Trait::Ord,
             "PartialEq" => Trait::PartialEq,
             "PartialOrd" => Trait::PartialOrd,
@@ -50,6 +52,7 @@ impl AsRef<str> for Trait {
             Trait::Debug => "Debug",
             Trait::Default => "Default",
             Trait::Eq => "Eq",
+            Trait::Hash => "Hash",
             Trait::Ord => "Ord",
             Trait::PartialEq => "PartialEq",
             Trait::PartialOrd => "PartialOrd",
diff --git a/syntax/mangle.rs b/syntax/mangle.rs
index 26cd3c0a..0d395e3d 100644
--- a/syntax/mangle.rs
+++ b/syntax/mangle.rs
@@ -26,7 +26,13 @@ pub fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol {
 }
 
 pub fn operator(receiver: &Pair, operator: &'static str) -> Symbol {
-    join!(receiver.namespace, CXXBRIDGE, receiver.cxx, "operator", operator)
+    join!(
+        receiver.namespace,
+        CXXBRIDGE,
+        receiver.cxx,
+        "operator",
+        operator,
+    )
 }
 
 // The C half of a function pointer trampoline.


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index cb0f916e..5427b895 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -25,7 +25,7 @@ pub mod ffi {
         msg: String,
     }
 
-    #[derive(Debug)]
+    #[derive(Debug, Hash)]
     enum Enum {
         AVal,
         BVal = 2020,
@@ -64,6 +64,7 @@ pub mod ffi {
     }
 
     #[namespace = "second"]
+    #[derive(Hash)]
     struct Second {
         i: i32,
         e: COwnedEnum,
@@ -184,6 +185,7 @@ pub mod ffi {
     }
 
     #[repr(u32)]
+    #[derive(Hash)]
     enum COwnedEnum {
         CVal1,
         CVal2,