• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1{{#title Result<T> — Rust ♡ C++}}
2# Result\<T\>
3
4Result\<T\> is allowed as the return type of an extern function in either
5direction. Its behavior is to translate to/from C++ exceptions. If your codebase
6does not use C++ exceptions, or prefers to represent fallibility using something
7like outcome\<T\>, leaf::result\<T\>, StatusOr\<T\>, etc then you'll need to
8handle the translation of those to Rust Result\<T\> using your own shims for
9now. Better support for this is planned.
10
11If an exception is thrown from an `extern "C++"` function that is *not* declared
12by the CXX bridge to return Result, the program calls C++'s `std::terminate`.
13The behavior is equivalent to the same exception being thrown through a
14`noexcept` C++ function.
15
16If a panic occurs in *any* `extern "Rust"` function, regardless of whether it is
17declared by the CXX bridge to return Result, a message is logged and the program
18calls Rust's `std::process::abort`.
19
20## Returning Result from Rust to C++
21
22An `extern "Rust"` function returning a Result turns into a `throw` in C++ if
23the Rust side produces an error.
24
25Note that the return type written inside of cxx::bridge must be written without
26a second type parameter. Only the Ok type is specified for the purpose of the
27FFI. The Rust *implementation* (outside of the bridge module) may pick any error
28type as long as it has a std::fmt::Display impl.
29
30```rust,noplayground
31# use std::io;
32#
33#[cxx::bridge]
34mod ffi {
35    extern "Rust" {
36        fn fallible1(depth: usize) -> Result<String>;
37        fn fallible2() -> Result<()>;
38    }
39}
40
41fn fallible1(depth: usize) -> anyhow::Result<String> {
42    if depth == 0 {
43        return Err(anyhow::Error::msg("fallible1 requires depth > 0"));
44    }
45    ...
46}
47
48fn fallible2() -> Result<(), io::Error> {
49    ...
50    Ok(())
51}
52```
53
54The exception that gets thrown by CXX on the C++ side is always of type
55`rust::Error` and has the following C++ public API. The `what()` member function
56gives the error message according to the Rust error's std::fmt::Display impl.
57
58```cpp,hidelines
59// rust/cxx.h
60#
61# namespace rust {
62
63class Error final : public std::exception {
64public:
65  Error(const Error &);
66  Error(Error &&) noexcept;
67  ~Error() noexcept;
68
69  Error &operator=(const Error &);
70  Error &operator=(Error &&) noexcept;
71
72  const char *what() const noexcept override;
73};
74#
75# } // namespace rust
76```
77
78## Returning Result from C++ to Rust
79
80An `extern "C++"` function returning a Result turns into a `catch` in C++ that
81converts the exception into an Err for Rust.
82
83Note that the return type written inside of cxx::bridge must be written without
84a second type parameter. Only the Ok type is specified for the purpose of the
85FFI. The resulting error type created by CXX when an `extern "C++"` function
86throws will always be of type **[`cxx::Exception`]**.
87
88[`cxx::Exception`]: https://docs.rs/cxx/*/cxx/struct.Exception.html
89
90```rust,noplayground
91# use std::process;
92#
93#[cxx::bridge]
94mod ffi {
95    unsafe extern "C++" {
96        include!("example/include/example.h");
97        fn fallible1(depth: usize) -> Result<String>;
98        fn fallible2() -> Result<()>;
99    }
100}
101
102fn main() {
103    if let Err(err) = ffi::fallible1(99) {
104        eprintln!("Error: {}", err);
105        process::exit(1);
106    }
107}
108```
109
110The specific set of caught exceptions and the conversion to error message are
111both customizable. The way you do this is by defining a template function
112`rust::behavior::trycatch` with a suitable signature inside any one of the
113headers `include!`'d by your cxx::bridge.
114
115The template signature is required to be:
116
117```cpp,hidelines
118namespace rust {
119namespace behavior {
120
121template <typename Try, typename Fail>
122static void trycatch(Try &&func, Fail &&fail) noexcept;
123
124} // namespace behavior
125} // namespace rust
126```
127
128The default `trycatch` used by CXX if you have not provided your own is the
129following. You must follow the same pattern: invoke `func` with no arguments,
130catch whatever exception(s) you want, and invoke `fail` with the error message
131you'd like for the Rust error to have.
132
133```cpp,hidelines
134# #include <exception>
135#
136# namespace rust {
137# namespace behavior {
138#
139template <typename Try, typename Fail>
140static void trycatch(Try &&func, Fail &&fail) noexcept try {
141  func();
142} catch (const std::exception &e) {
143  fail(e.what());
144}
145#
146# } // namespace behavior
147# } // namespace rust
148```
149