Implement fallible Rust functions
diff --git a/gen/write.rs b/gen/write.rs
index d57e21e..ccb5a0f 100644
--- a/gen/write.rs
+++ b/gen/write.rs
@@ -271,7 +271,11 @@
 }
 
 fn write_rust_function_decl(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);
     }
@@ -305,7 +309,10 @@
         write_type_space(out, &arg.ty);
         write!(out, "{}", arg.ident);
     }
-    write!(out, ") noexcept");
+    write!(out, ")");
+    if !efn.throws {
+        write!(out, " noexcept");
+    }
     if out.header {
         writeln!(out, ";");
     } else {
@@ -339,6 +346,9 @@
                 _ => {}
             }
         }
+        if efn.throws {
+            write!(out, "::rust::Str::Repr error$ = ");
+        }
         for name in out.namespace.clone() {
             write!(out, "{}$", name);
         }
@@ -374,6 +384,11 @@
             }
         }
         writeln!(out, ";");
+        if efn.throws {
+            writeln!(out, "  if (error$.ptr) {{");
+            writeln!(out, "    throw ::rust::Error(error$);");
+            writeln!(out, "  }}");
+        }
         if indirect_return {
             writeln!(out, "  return ::std::move(return$.value);");
         }
@@ -391,7 +406,7 @@
 fn indirect_return(efn: &ExternFn, types: &Types) -> bool {
     efn.ret
         .as_ref()
-        .map_or(false, |ret| types.needs_indirect_abi(ret))
+        .map_or(false, |ret| efn.throws || types.needs_indirect_abi(ret))
 }
 
 fn write_extern_return_type(out: &mut OutFile, ty: &Option<Type>, types: &Types) {
diff --git a/include/cxx.h b/include/cxx.h
index e02145b..b48abb5 100644
--- a/include/cxx.h
+++ b/include/cxx.h
@@ -138,6 +138,18 @@
 };
 #endif // CXXBRIDGE02_RUST_BOX
 
+class Error final : std::exception {
+public:
+  Error(const Error &);
+  Error(Error &&) noexcept;
+  Error(Str::Repr) noexcept;
+  ~Error() noexcept;
+  const char *what() const noexcept override;
+
+private:
+  Str::Repr msg;
+};
+
 std::ostream &operator<<(std::ostream &, const String &);
 std::ostream &operator<<(std::ostream &, const Str &);
 
@@ -145,6 +157,7 @@
 using string = String;
 using str = Str;
 template <class T> using box = Box<T>;
+using error = Error;
 
 struct unsafe_bitcopy_t {
   explicit unsafe_bitcopy_t() = default;
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index fcfa1d9..defa565 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -136,7 +136,7 @@
     });
     let ret = expand_extern_return_type(&efn.ret, types);
     let mut outparam = None;
-    if indirect_return(&efn.ret, types) {
+    if indirect_return(efn, types) {
         let ret = expand_extern_type(efn.ret.as_ref().unwrap());
         outparam = Some(quote!(__return: *mut #ret));
     }
@@ -154,7 +154,7 @@
     let decl = expand_cxx_function_decl(namespace, efn, types);
     let args = &efn.args;
     let ret = expand_return_type(&efn.ret);
-    let indirect_return = indirect_return(&efn.ret, types);
+    let indirect_return = indirect_return(efn, types);
     let vars = efn.args.iter().map(|arg| {
         let var = &arg.ident;
         match &arg.ty {
@@ -267,9 +267,7 @@
         }
     });
     let mut outparam = None;
-    let call = quote! {
-        ::cxx::private::catch_unwind(__fn, move || super::#ident(#(#vars),*))
-    };
+    let call = quote!(super::#ident(#(#vars),*));
     let mut expr = efn
         .ret
         .as_ref()
@@ -289,12 +287,22 @@
             _ => None,
         })
         .unwrap_or(call);
-    if indirect_return(&efn.ret, types) {
+    let indirect_return = indirect_return(efn, types);
+    if indirect_return {
         let ret = expand_extern_type(efn.ret.as_ref().unwrap());
         outparam = Some(quote!(__return: *mut #ret));
+    }
+    if efn.throws {
+        expr = quote!(::cxx::private::r#try(__return, #expr));
+    } else if indirect_return {
         expr = quote!(::std::ptr::write(__return, #expr));
     }
-    let ret = expand_extern_return_type(&efn.ret, types);
+    expr = quote!(::cxx::private::catch_unwind(__fn, move || #expr));
+    let ret = if efn.throws {
+        quote!(-> ::std::option::Option<::cxx::private::RustStr>)
+    } else {
+        expand_extern_return_type(&efn.ret, types)
+    };
     let link_name = format!("{}cxxbridge02${}", namespace, ident);
     let local_name = format_ident!("__{}", ident);
     let catch_unwind_label = format!("::{}", ident);
@@ -407,9 +415,10 @@
     }
 }
 
-fn indirect_return(ret: &Option<Type>, types: &Types) -> bool {
-    ret.as_ref()
-        .map_or(false, |ret| types.needs_indirect_abi(ret))
+fn indirect_return(efn: &ExternFn, types: &Types) -> bool {
+    efn.ret
+        .as_ref()
+        .map_or(false, |ret| efn.throws || types.needs_indirect_abi(ret))
 }
 
 fn expand_extern_type(ty: &Type) -> TokenStream {
diff --git a/src/cxx.cc b/src/cxx.cc
index 6fae0f9..0c1b8af 100644
--- a/src/cxx.cc
+++ b/src/cxx.cc
@@ -135,6 +135,32 @@
   return os;
 }
 
+extern "C" {
+const char *cxxbridge02$error(const char *ptr, size_t len) {
+  char *copy = new char[len];
+  strncpy(copy, ptr, len);
+  return copy;
+}
+} // extern "C"
+
+Error::Error(Str::Repr msg) noexcept : msg(msg) {}
+
+Error::Error(const Error &other) {
+  this->msg.ptr = cxxbridge02$error(other.msg.ptr, other.msg.len);
+  this->msg.len = other.msg.len;
+}
+
+Error::Error(Error &&other) noexcept {
+  delete[] this->msg.ptr;
+  this->msg = other.msg;
+  other.msg.ptr = nullptr;
+  other.msg.len = 0;
+}
+
+Error::~Error() noexcept { delete[] this->msg.ptr; }
+
+const char *Error::what() const noexcept { return this->msg.ptr; }
+
 } // namespace cxxbridge02
 } // namespace rust
 
diff --git a/src/exception.rs b/src/exception.rs
new file mode 100644
index 0000000..3f32b45
--- /dev/null
+++ b/src/exception.rs
@@ -0,0 +1,35 @@
+use crate::rust_str::RustStr;
+use std::fmt::Display;
+use std::ptr;
+use std::slice;
+use std::str;
+
+pub unsafe fn r#try<T, E>(ret: *mut T, result: Result<T, E>) -> Option<RustStr>
+where
+    E: Display,
+{
+    match result {
+        Ok(ok) => {
+            ptr::write(ret, ok);
+            None
+        }
+        Err(err) => Some(to_c_string(err.to_string())),
+    }
+}
+
+unsafe fn to_c_string(msg: String) -> RustStr {
+    let mut msg = msg;
+    msg.as_mut_vec().push(b'\0');
+    let ptr = msg.as_ptr();
+    let len = msg.len();
+
+    extern "C" {
+        #[link_name = "cxxbridge02$error"]
+        fn error(ptr: *const u8, len: usize) -> *const u8;
+    }
+
+    let copy = error(ptr, len);
+    let slice = slice::from_raw_parts(copy, len);
+    let string = str::from_utf8_unchecked(slice);
+    RustStr::from(string)
+}
diff --git a/src/lib.rs b/src/lib.rs
index 3744bda..109090c 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;
@@ -370,6 +371,7 @@
 // Not public API.
 #[doc(hidden)]
 pub mod private {
+    pub use crate::exception::r#try;
     pub use crate::opaque::Opaque;
     pub use crate::rust_str::RustStr;
     pub use crate::rust_string::RustString;
diff --git a/syntax/check.rs b/syntax/check.rs
index 4c2fff3..b781445 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -75,12 +75,6 @@
                         "fallible C++ functions are not implemented yet",
                     ));
                 }
-                if efn.throws && efn.lang == Rust {
-                    errors.push(Error::new_spanned(
-                        efn,
-                        "fallible Rust functions are not implemented yet",
-                    ));
-                }
             }
             _ => {}
         }