Implement fallible C++ functions
diff --git a/gen/include.rs b/gen/include.rs
index a4b416a..da6678d 100644
--- a/gen/include.rs
+++ b/gen/include.rs
@@ -18,6 +18,7 @@
 pub struct Includes {
     custom: Vec<String>,
     pub cstdint: bool,
+    pub cstring: bool,
     pub memory: bool,
     pub string: bool,
     pub type_traits: bool,
@@ -41,6 +42,9 @@
         if self.cstdint {
             writeln!(f, "#include <cstdint>")?;
         }
+        if self.cstring {
+            writeln!(f, "#include <cstring>")?;
+        }
         if self.memory {
             writeln!(f, "#include <memory>")?;
         }
diff --git a/gen/write.rs b/gen/write.rs
index ccb5a0f..3398f9a 100644
--- a/gen/write.rs
+++ b/gen/write.rs
@@ -45,6 +45,7 @@
 
     if !header {
         out.begin_block("extern \"C\"");
+        write_exception_glue(out, apis);
         for api in apis {
             let (efn, write): (_, fn(_, _, _)) = match api {
                 Api::CxxFunction(efn) => (efn, write_cxx_function_shim),
@@ -186,8 +187,32 @@
     writeln!(out, "using {} = {};", ident, ident);
 }
 
+fn write_exception_glue(out: &mut OutFile, apis: &[Api]) {
+    let mut has_cxx_throws = false;
+    for api in apis {
+        if let Api::CxxFunction(efn) = api {
+            if efn.throws {
+                has_cxx_throws = true;
+                break;
+            }
+        }
+    }
+
+    if has_cxx_throws {
+        out.next_section();
+        write!(
+            out,
+            "const char *cxxbridge02$exception(const char *, size_t);",
+        );
+    }
+}
+
 fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
-    write_extern_return_type(out, &efn.ret, types);
+    if efn.throws {
+        write!(out, "::rust::Str::Repr ");
+    } else {
+        write_extern_return_type(out, &efn.ret, types);
+    }
     for name in out.namespace.clone() {
         write!(out, "{}$", name);
     }
@@ -221,6 +246,11 @@
     }
     writeln!(out, ") = {};", efn.ident);
     write!(out, "  ");
+    if efn.throws {
+        writeln!(out, "::rust::Str::Repr throw$;");
+        writeln!(out, "  try {{");
+        write!(out, "    ");
+    }
     if indirect_return {
         write!(out, "new (return$) ");
         write_type(out, efn.ret.as_ref().unwrap());
@@ -267,6 +297,19 @@
         write!(out, ")");
     }
     writeln!(out, ";");
+    if efn.throws {
+        out.include.cstring = true;
+        writeln!(out, "    throw$.ptr = nullptr;");
+        writeln!(out, "  }} catch (const ::std::exception &catch$) {{");
+        writeln!(out, "    const char *return$ = catch$.what();");
+        writeln!(out, "    throw$.len = ::std::strlen(return$);");
+        writeln!(
+            out,
+            "    throw$.ptr = cxxbridge02$exception(return$, throw$.len);",
+        );
+        writeln!(out, "  }}");
+        writeln!(out, "  return throw$;");
+    }
     writeln!(out, "}}");
 }
 
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 88fc812..e11b2d3 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -134,7 +134,11 @@
             quote!(#ident: #ty)
         }
     });
-    let ret = expand_extern_return_type(&efn.ret, types);
+    let ret = if efn.throws {
+        quote!(-> ::cxx::private::Result)
+    } else {
+        expand_extern_return_type(&efn.ret, types)
+    };
     let mut outparam = None;
     if indirect_return(efn, types) {
         let ret = expand_extern_type(efn.ret.as_ref().unwrap());
@@ -153,7 +157,15 @@
     let doc = &efn.doc;
     let decl = expand_cxx_function_decl(namespace, efn, types);
     let args = &efn.args;
-    let ret = expand_return_type(&efn.ret);
+    let ret = if efn.throws {
+        let ok = match &efn.ret {
+            Some(ret) => quote!(#ret),
+            None => quote!(()),
+        };
+        quote!(-> ::std::result::Result<#ok, ::cxx::Exception>)
+    } else {
+        expand_return_type(&efn.ret)
+    };
     let indirect_return = indirect_return(efn, types);
     let vars = efn.args.iter().map(|arg| {
         let var = &arg.ident;
@@ -192,10 +204,21 @@
         let ret = expand_extern_type(efn.ret.as_ref().unwrap());
         setup.extend(quote! {
             let mut __return = ::std::mem::MaybeUninit::<#ret>::uninit();
-            #local_name(#(#vars,)* __return.as_mut_ptr());
         });
+        if efn.throws {
+            setup.extend(quote! {
+                #local_name(#(#vars,)* __return.as_mut_ptr()).exception()?;
+            });
+            quote!(::std::result::Result::Ok(__return.assume_init()))
+        } else {
+            setup.extend(quote! {
+                #local_name(#(#vars,)* __return.as_mut_ptr());
+            });
+            quote!(__return.assume_init())
+        }
+    } else if efn.throws {
         quote! {
-            __return.assume_init()
+            #local_name(#(#vars),*).exception()
         }
     } else {
         quote! {
diff --git a/src/exception.rs b/src/exception.rs
new file mode 100644
index 0000000..c436196
--- /dev/null
+++ b/src/exception.rs
@@ -0,0 +1,29 @@
+use std::fmt::{self, Debug, Display};
+use std::slice;
+
+/// Exception thrown from an `extern "C"` function.
+#[derive(Debug)]
+pub struct Exception {
+    pub(crate) what: Box<str>,
+}
+
+impl Display for Exception {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(&self.what)
+    }
+}
+
+impl std::error::Error for Exception {}
+
+impl Exception {
+    pub fn what(&self) -> &str {
+        &self.what
+    }
+}
+
+#[export_name = "cxxbridge02$exception"]
+unsafe extern "C" fn exception(ptr: *const u8, len: usize) -> *const u8 {
+    let slice = slice::from_raw_parts(ptr, len);
+    let boxed = String::from_utf8_lossy(slice).into_owned().into_boxed_str();
+    Box::leak(boxed).as_ptr()
+}
diff --git a/src/lib.rs b/src/lib.rs
index b985e1d..5dd2649 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -354,6 +354,7 @@
 
 mod cxx_string;
 mod error;
+mod exception;
 mod gen;
 mod opaque;
 mod paths;
@@ -365,6 +366,7 @@
 mod unwind;
 
 pub use crate::cxx_string::CxxString;
+pub use crate::exception::Exception;
 pub use crate::unique_ptr::UniquePtr;
 pub use cxxbridge_macro::bridge;
 
diff --git a/src/result.rs b/src/result.rs
index 6d04a2d..047260b 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -1,5 +1,7 @@
+use crate::exception::Exception;
 use crate::rust_str::RustStr;
 use std::fmt::Display;
+use std::mem;
 use std::ptr;
 use std::result::Result as StdResult;
 use std::slice;
@@ -41,3 +43,18 @@
     let err = RustStr::from(string);
     Result { err }
 }
+
+impl Result {
+    pub unsafe fn exception(self) -> StdResult<(), Exception> {
+        if self.ok.is_null() {
+            Ok(())
+        } else {
+            let err = self.err;
+            let slice = slice::from_raw_parts(err.ptr.as_ptr(), err.len);
+            let s = str::from_utf8_unchecked(slice);
+            Err(Exception {
+                what: mem::transmute::<*const str, Box<str>>(s),
+            })
+        }
+    }
+}
diff --git a/src/rust_str.rs b/src/rust_str.rs
index 110889b..51e4835 100644
--- a/src/rust_str.rs
+++ b/src/rust_str.rs
@@ -7,8 +7,8 @@
 #[repr(C)]
 #[derive(Copy, Clone)]
 pub struct RustStr {
-    ptr: NonNull<u8>,
-    len: usize,
+    pub(crate) ptr: NonNull<u8>,
+    pub(crate) len: usize,
 }
 
 impl RustStr {
diff --git a/syntax/check.rs b/syntax/check.rs
index b781445..ef3faff 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -1,5 +1,5 @@
 use crate::syntax::atom::Atom::{self, *};
-use crate::syntax::{error, ident, Api, ExternFn, Lang::*, Ref, Ty1, Type, Types, Var};
+use crate::syntax::{error, ident, Api, ExternFn, Ref, Ty1, Type, Types, Var};
 use proc_macro2::Ident;
 use syn::{Error, Result};
 
@@ -69,12 +69,6 @@
                         errors.push(return_by_value(ty, types));
                     }
                 }
-                if efn.throws && efn.lang == Cxx {
-                    errors.push(Error::new_spanned(
-                        efn,
-                        "fallible C++ functions are not implemented yet",
-                    ));
-                }
             }
             _ => {}
         }
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index fd54654..fe6a364 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -30,6 +30,10 @@
         fn c_take_str(s: &str);
         fn c_take_rust_string(s: String);
         fn c_take_unique_ptr_string(s: UniquePtr<CxxString>);
+
+        fn c_try_return_void() -> Result<()>;
+        fn c_try_return_primitive() -> Result<usize>;
+        fn c_fail_return_primitive() -> Result<usize>;
     }
 
     extern "Rust" {
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 0ec2658..4f36f9b 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -1,6 +1,7 @@
 #include "tests/ffi/tests.h"
 #include "tests/ffi/lib.rs"
 #include <cstring>
+#include <stdexcept>
 
 extern "C" void cxx_test_suite_set_correct() noexcept;
 extern "C" tests::R *cxx_test_suite_get_box() noexcept;
@@ -91,6 +92,12 @@
   }
 }
 
+void c_try_return_void() {}
+
+size_t c_try_return_primitive() { return 2020; }
+
+size_t c_fail_return_primitive() { throw std::logic_error("logic error"); }
+
 extern "C" C *cxx_test_suite_get_unique_ptr() noexcept {
   return std::unique_ptr<C>(new C{2020}).release();
 }
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index 7cff6fa..6a5f802 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -36,4 +36,8 @@
 void c_take_rust_string(rust::String s);
 void c_take_unique_ptr_string(std::unique_ptr<std::string> s);
 
+void c_try_return_void();
+size_t c_try_return_primitive();
+size_t c_fail_return_primitive();
+
 } // namespace tests
diff --git a/tests/test.rs b/tests/test.rs
index a30aefb..d86ebd2 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -38,6 +38,13 @@
             .to_str()
             .unwrap()
     );
+
+    assert_eq!((), ffi::c_try_return_void().unwrap());
+    assert_eq!(2020, ffi::c_try_return_primitive().unwrap());
+    assert_eq!(
+        "logic error",
+        ffi::c_fail_return_primitive().unwrap_err().what(),
+    );
 }
 
 #[test]