Support derive(Hash)
diff --git a/gen/src/write.rs b/gen/src/write.rs
index ab9d3c0..873904d 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -134,6 +134,8 @@
                 _ => {}
             }
         }
+
+        write_std_specializations(out, apis);
     }
 
     for api in apis {
@@ -148,6 +150,36 @@
     }
 }
 
+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 @@
         }
     }
 
+    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 be8ad82..f614add 100644
--- a/macro/src/derive.rs
+++ b/macro/src/derive.rs
@@ -16,6 +16,7 @@
             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 => expanded.extend(struct_hash(strct, span)),
             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 @@
                 traits.push(quote_spanned!(span=> ::std::cmp::Eq));
                 has_eq = true;
             }
+            Trait::Hash => expanded.extend(enum_hash(enm, span)),
             Trait::Ord => expanded.extend(enum_ord(enm, span)),
             Trait::PartialEq => {
                 traits.push(quote_spanned!(span=> ::std::cmp::PartialEq));
@@ -155,6 +157,21 @@
     }
 }
 
+fn struct_hash(strct: &Struct, span: Span) -> TokenStream {
+    let ident = &strct.name.rust;
+    let fields = strct.fields.iter().map(|field| &field.ident);
+
+    quote_spanned! {span=>
+        impl ::std::hash::Hash for #ident {
+            fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
+                #(
+                    ::std::hash::Hash::hash(&self.#fields, state);
+                )*
+            }
+        }
+    }
+}
+
 fn struct_ord(strct: &Struct, span: Span) -> TokenStream {
     let ident = &strct.name.rust;
     let fields = strct.fields.iter().map(|field| &field.ident);
@@ -246,6 +263,18 @@
     }
 }
 
+fn enum_hash(enm: &Enum, span: Span) -> TokenStream {
+    let ident = &enm.name.rust;
+
+    quote_spanned! {span=>
+        impl ::std::hash::Hash for #ident {
+            fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
+                ::std::hash::Hash::hash(&self.repr, state);
+            }
+        }
+    }
+}
+
 fn enum_ord(enm: &Enum, span: Span) -> TokenStream {
     let ident = &enm.name.rust;
 
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index e065a55..c218267 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -230,6 +230,19 @@
                     });
                 }
             }
+            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 a8b6bde..1121211 100644
--- a/syntax/derive.rs
+++ b/syntax/derive.rs
@@ -13,6 +13,7 @@
     Debug,
     Default,
     Eq,
+    Hash,
     Ord,
     PartialEq,
     PartialOrd,
@@ -26,6 +27,7 @@
             "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 @@
             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 26cd3c0..0d395e3 100644
--- a/syntax/mangle.rs
+++ b/syntax/mangle.rs
@@ -26,7 +26,13 @@
 }
 
 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.
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index cb0f916..5427b89 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -25,7 +25,7 @@
         msg: String,
     }
 
-    #[derive(Debug)]
+    #[derive(Debug, Hash)]
     enum Enum {
         AVal,
         BVal = 2020,
@@ -64,6 +64,7 @@
     }
 
     #[namespace = "second"]
+    #[derive(Hash)]
     struct Second {
         i: i32,
         e: COwnedEnum,
@@ -184,6 +185,7 @@
     }
 
     #[repr(u32)]
+    #[derive(Hash)]
     enum COwnedEnum {
         CVal1,
         CVal2,