Add ExternType derive for opaque Rust types
diff --git a/gen/src/nested.rs b/gen/src/nested.rs
index 22b0c9f..fe1664b 100644
--- a/gen/src/nested.rs
+++ b/gen/src/nested.rs
@@ -52,7 +52,7 @@
 mod tests {
     use super::NamespaceEntries;
     use crate::syntax::namespace::Namespace;
-    use crate::syntax::{Api, Doc, ExternType, Pair};
+    use crate::syntax::{Api, Doc, ExternType, Lang, Pair};
     use proc_macro2::{Ident, Span};
     use std::iter::FromIterator;
     use syn::Token;
@@ -126,7 +126,9 @@
     fn make_api(ns: Option<&str>, ident: &str) -> Api {
         let ns = ns.map_or(Namespace::ROOT, |ns| syn::parse_str(ns).unwrap());
         Api::CxxType(ExternType {
+            lang: Lang::Rust,
             doc: Doc::new(),
+            derives: Vec::new(),
             type_token: Token![type](Span::call_site()),
             name: Pair::new(ns, Ident::new(ident, Span::call_site())),
             semi_token: Token![;](Span::call_site()),
diff --git a/macro/src/derive.rs b/macro/src/derive.rs
index a015ed0..3379c9c 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::ExternType => unreachable!(),
             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)),
@@ -57,6 +58,7 @@
                 traits.push(quote_spanned!(span=> ::std::cmp::Eq));
                 has_eq = true;
             }
+            Trait::ExternType => unreachable!(),
             Trait::Hash => traits.push(quote_spanned!(span=> ::std::hash::Hash)),
             Trait::Ord => expanded.extend(enum_ord(enm, span)),
             Trait::PartialEq => {
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index c218267..ae13beb 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -617,9 +617,24 @@
     let span = ident.span();
     let unsafe_impl = quote_spanned!(ety.type_token.span=> unsafe impl);
 
-    quote_spanned! {span=>
+    let mut impls = quote_spanned! {span=>
         #unsafe_impl ::cxx::private::RustType for #ident {}
+    };
+
+    for derive in &ety.derives {
+        if derive.what == Trait::ExternType {
+            let type_id = type_id(&ety.name);
+            let span = derive.span;
+            impls.extend(quote_spanned! {span=>
+                unsafe impl ::cxx::ExternType for #ident {
+                    type Id = #type_id;
+                    type Kind = ::cxx::kind::Opaque;
+                }
+            });
+        }
     }
+
+    impls
 }
 
 fn expand_rust_type_assert_sized(ety: &ExternType) -> TokenStream {
diff --git a/syntax/check.rs b/syntax/check.rs
index 725105d..a60332d 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -232,6 +232,13 @@
         }
     }
 
+    for derive in &strct.derives {
+        if derive.what == Trait::ExternType {
+            let msg = format!("derive({}) on shared struct is not supported", derive);
+            cx.error(derive, msg);
+        }
+    }
+
     for field in &strct.fields {
         if let Type::Fn(_) = field.ty {
             cx.error(
@@ -258,11 +265,9 @@
     }
 
     for derive in &enm.derives {
-        if derive.what == Trait::Default {
-            cx.error(
-                derive,
-                "derive(Default) on shared enums is not supported yet",
-            );
+        if derive.what == Trait::Default || derive.what == Trait::ExternType {
+            let msg = format!("derive({}) on shared enum is not supported", derive);
+            cx.error(derive, msg);
         }
     }
 }
@@ -270,6 +275,21 @@
 fn check_api_type(cx: &mut Check, ety: &ExternType) {
     check_reserved_name(cx, &ety.name.rust);
 
+    for derive in &ety.derives {
+        if derive.what == Trait::ExternType && ety.lang == Lang::Rust {
+            continue;
+        }
+        let lang = match ety.lang {
+            Lang::Rust => "Rust",
+            Lang::Cxx => "C++",
+        };
+        let msg = format!(
+            "derive({}) on opaque {} type is not supported yet",
+            derive, lang,
+        );
+        cx.error(derive, msg);
+    }
+
     if let Some(reason) = cx.types.required_trivial.get(&ety.name.rust) {
         let what = match reason {
             TrivialReason::StructField(strct) => format!("a field of `{}`", strct.name.rust),
diff --git a/syntax/derive.rs b/syntax/derive.rs
index 1121211..96b3eea 100644
--- a/syntax/derive.rs
+++ b/syntax/derive.rs
@@ -1,4 +1,5 @@
 use proc_macro2::{Ident, Span};
+use std::fmt::{self, Display};
 
 #[derive(Copy, Clone)]
 pub struct Derive {
@@ -13,6 +14,7 @@
     Debug,
     Default,
     Eq,
+    ExternType,
     Hash,
     Ord,
     PartialEq,
@@ -27,6 +29,7 @@
             "Debug" => Trait::Debug,
             "Default" => Trait::Default,
             "Eq" => Trait::Eq,
+            "ExternType" => Trait::ExternType,
             "Hash" => Trait::Hash,
             "Ord" => Trait::Ord,
             "PartialEq" => Trait::PartialEq,
@@ -52,6 +55,7 @@
             Trait::Debug => "Debug",
             Trait::Default => "Default",
             Trait::Eq => "Eq",
+            Trait::ExternType => "ExternType",
             Trait::Hash => "Hash",
             Trait::Ord => "Ord",
             Trait::PartialEq => "PartialEq",
@@ -60,6 +64,12 @@
     }
 }
 
+impl Display for Derive {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str(self.what.as_ref())
+    }
+}
+
 pub fn contains(derives: &[Derive], query: Trait) -> bool {
     derives.iter().any(|derive| derive.what == query)
 }
diff --git a/syntax/mod.rs b/syntax/mod.rs
index bd7efc0..e8288a9 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -67,7 +67,9 @@
 }
 
 pub struct ExternType {
+    pub lang: Lang,
     pub doc: Doc,
+    pub derives: Vec<Derive>,
     pub type_token: Token![type],
     pub name: Pair,
     pub semi_token: Token![;],
diff --git a/syntax/parse.rs b/syntax/parse.rs
index a009e19..e46cdb4 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -327,12 +327,14 @@
     namespace: &Namespace,
 ) -> Api {
     let mut doc = Doc::new();
+    let mut derives = Vec::new();
     let mut namespace = namespace.clone();
     attrs::parse(
         cx,
         &foreign_type.attrs,
         attrs::Parser {
             doc: Some(&mut doc),
+            derives: Some(&mut derives),
             namespace: Some(&mut namespace),
             ..Default::default()
         },
@@ -345,7 +347,9 @@
         Lang::Rust => Api::RustType,
     };
     api_type(ExternType {
+        lang,
         doc,
+        derives,
         type_token,
         name: Pair::new(namespace, ident),
         semi_token,