aboutsummaryrefslogtreecommitdiff
path: root/book/src/binding/result.md
blob: e49dcf4de0a8a3e25ff096445837b1c78a8801d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
{{#title Result<T> — Rust ♡ C++}}
# Result\<T\>

Result\<T\> is allowed as the return type of an extern function in either
direction. Its behavior is to translate to/from C++ exceptions. If your codebase
does not use C++ exceptions, or prefers to represent fallibility using something
like outcome\<T\>, leaf::result\<T\>, StatusOr\<T\>, etc then you'll need to
handle the translation of those to Rust Result\<T\> using your own shims for
now. Better support for this is planned.

If an exception is thrown from an `extern "C++"` function that is *not* declared
by the CXX bridge to return Result, the program calls C++'s `std::terminate`.
The behavior is equivalent to the same exception being thrown through a
`noexcept` C++ function.

If a panic occurs in *any* `extern "Rust"` function, regardless of whether it is
declared by the CXX bridge to return Result, a message is logged and the program
calls Rust's `std::process::abort`.

## Returning Result from Rust to C++

An `extern "Rust"` function returning a Result turns into a `throw` in C++ if
the Rust side produces an error.

Note that the return type written inside of cxx::bridge must be written without
a second type parameter. Only the Ok type is specified for the purpose of the
FFI. The Rust *implementation* (outside of the bridge module) may pick any error
type as long as it has a std::fmt::Display impl.

```rust,noplayground
# use std::io;
#
#[cxx::bridge]
mod ffi {
    extern "Rust" {
        fn fallible1(depth: usize) -> Result<String>;
        fn fallible2() -> Result<()>;
    }
}

fn fallible1(depth: usize) -> anyhow::Result<String> {
    if depth == 0 {
        return Err(anyhow::Error::msg("fallible1 requires depth > 0"));
    }
    ...
}

fn fallible2() -> Result<(), io::Error> {
    ...
    Ok(())
}
```

The exception that gets thrown by CXX on the C++ side is always of type
`rust::Error` and has the following C++ public API. The `what()` member function
gives the error message according to the Rust error's std::fmt::Display impl.

```cpp,hidelines
// rust/cxx.h
#
# namespace rust {

class Error final : public std::exception {
public:
  Error(const Error &);
  Error(Error &&) noexcept;
  ~Error() noexcept;

  Error &operator=(const Error &);
  Error &operator=(Error &&) noexcept;

  const char *what() const noexcept override;
};
#
# } // namespace rust
```

## Returning Result from C++ to Rust

An `extern "C++"` function returning a Result turns into a `catch` in C++ that
converts the exception into an Err for Rust.

Note that the return type written inside of cxx::bridge must be written without
a second type parameter. Only the Ok type is specified for the purpose of the
FFI. The resulting error type created by CXX when an `extern "C++"` function
throws will always be of type **[`cxx::Exception`]**.

[`cxx::Exception`]: https://docs.rs/cxx/*/cxx/struct.Exception.html

```rust,noplayground
# use std::process;
#
#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("example/include/example.h");
        fn fallible1(depth: usize) -> Result<String>;
        fn fallible2() -> Result<()>;
    }
}

fn main() {
    if let Err(err) = ffi::fallible1(99) {
        eprintln!("Error: {}", err);
        process::exit(1);
    }
}
```

The specific set of caught exceptions and the conversion to error message are
both customizable. The way you do this is by defining a template function
`rust::behavior::trycatch` with a suitable signature inside any one of the
headers `include!`'d by your cxx::bridge.

The template signature is required to be:

```cpp,hidelines
namespace rust {
namespace behavior {

template <typename Try, typename Fail>
static void trycatch(Try &&func, Fail &&fail) noexcept;

} // namespace behavior
} // namespace rust
```

The default `trycatch` used by CXX if you have not provided your own is the
following. You must follow the same pattern: invoke `func` with no arguments,
catch whatever exception(s) you want, and invoke `fail` with the error message
you'd like for the Rust error to have.

```cpp,hidelines
# #include <exception>
#
# namespace rust {
# namespace behavior {
#
template <typename Try, typename Fail>
static void trycatch(Try &&func, Fail &&fail) noexcept try {
  func();
} catch (const std::exception &e) {
  fail(e.what());
}
#
# } // namespace behavior
# } // namespace rust
```