Skip to content

Commit f6f6569

Browse files
authored
Add tests and impl for StatusOr<T> (googleapis#11)
StatusOr is a template type that wraps a value, usually a protobuf/grpc message, or a Status indicating why an operation failed. StatusOr is copyable and movable and constructable from Status or T by const or rvalue reference. It provides overloads for operators *, ->, and bool() with similar semantics to a smart pointer. If StatusOr is constructed from a Status or copied/moved from a StatusOr that was construced from a Status, the underlying T is _not_ constructed or destructed. The semantics are similar to Rust's Result<T,E> Constructing a StatusOr from an OK status is not allowed: it results in a spurious free on value_ when the StatusOr is destructed. Instead of allowing this behavior, StatusOr(Status) checks and aborts if this precondition is violated. Similarly, calling value() if !ok() generates an abort. This is different from invoking operator*, which results in Undefined Behavior when !ok().
1 parent 68a05a9 commit f6f6569

File tree

5 files changed

+472
-2
lines changed

5 files changed

+472
-2
lines changed

gax/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ cc_library(
2727
"backoff_policy.h",
2828
"retry_policy.h",
2929
"status.h",
30+
"status_or.h",
3031
],
3132
deps = [
3233
],
@@ -36,6 +37,7 @@ gax_unit_tests = [
3637
"backoff_policy_unittest.cc",
3738
"retry_policy_unittest.cc",
3839
"status_unittest.cc",
40+
"status_or_unittest.cc",
3941
]
4042

4143
[cc_test(

gax/status.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ enum class StatusCode {
5757
class Status {
5858
public:
5959
Status() : code_(StatusCode::kOk) {}
60-
explicit Status(StatusCode code, std::string msg) : code_(code),
61-
msg_(std::move(msg)) {}
60+
Status(StatusCode code, std::string msg) : code_(code),
61+
msg_(std::move(msg)) {}
62+
Status(Status const& rhs) : Status(rhs.code_, rhs.msg_) {}
63+
Status(Status&& rhs) : Status(rhs.code_, std::move(rhs.msg_)) {}
6264

6365
inline bool IsOk() const { return code_ == StatusCode::kOk; }
6466
inline bool IsTransientFailure() const {

gax/status_or.h

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// Copyright 2019 Google Inc. All rights reserved
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_GAX_STATUS_OR_H_
16+
#define GOOGLE_GAX_STATUS_OR_H_
17+
18+
#include <cstdlib>
19+
#include <iostream>
20+
#include <memory>
21+
22+
#include "status.h"
23+
24+
namespace google {
25+
namespace gax {
26+
27+
/**
28+
* Holds a value or a `Status` indicating why there is no value.
29+
*
30+
* `StatusOr<T>` represents either a usable `T` value or a `Status` object
31+
* explaining why a `T` value is not present. Typical usage of `StatusOr<T>`
32+
* looks like usage of a smart pointer, or even a std::optional<T>, in that you
33+
* first check its validity using a conversion to bool (or by calling
34+
* `StatusOr::ok()`), then you may dereference the object to access the
35+
* contained value. It is undefined behavior (UB) to dereference a
36+
* `StatusOr<T>` that is not "ok". For example:
37+
*
38+
* @code
39+
* StatusOr<Foo> foo = FetchFoo();
40+
* if (!foo) { // Same as !foo.ok()
41+
* // handle error and probably look at foo.status()
42+
* } else {
43+
* foo->DoSomethingFooey(); // UB if !foo
44+
* }
45+
* @endcode
46+
*
47+
* Alternatively, you may call the `StatusOr::value()` member function,
48+
* which will invoke `std::abort()` if `!StatusOr::ok()`.
49+
*
50+
* @code
51+
* StatusOr<Foo> foo = FetchFoo();
52+
* foo.value().DoSomethingFooey(); // May throw/crash if there is no value
53+
* @endcode
54+
*
55+
* Functions that can fail will often return a `StatusOr<T>` instead of
56+
* returning an error code and taking a `T` out-param, or rather than directly
57+
* returning the `T` and throwing an exception on error. StatusOr is used so
58+
* that callers can choose whether they want to explicitly check for errors,
59+
* crash the program, or throw exceptions. Since constructors do not have a
60+
* return value, they should be designed in such a way that they cannot fail by
61+
* moving the object's complex initialization logic into a separate factory
62+
* function that itself can return a `StatusOr<T>`. For example:
63+
*
64+
* @code
65+
* class Bar {
66+
* public:
67+
* Bar(Arg arg);
68+
* ...
69+
* };
70+
* StatusOr<Bar> MakeBar() {
71+
* ... complicated logic that might fail
72+
* return Bar(std::move(arg));
73+
* }
74+
* @endcode
75+
*
76+
* TODO(...) - the current implementation is fairly naive with respect to `T`,
77+
* it is unlikely to work correctly for reference types, arrays, and so forth.
78+
*
79+
* @tparam T the type of the value.
80+
*/
81+
template<typename T>
82+
class StatusOr final {
83+
public:
84+
/**
85+
* StatusOr is not default constructible.
86+
*
87+
* There is no good definition of a default StatusOr:
88+
* it can't default construct T because T may not have a default constructor,
89+
* and using an unknown error code and generic message is not helpful.
90+
*/
91+
StatusOr() = delete;
92+
93+
/**
94+
* Creates a new `StatusOr<T>` holding the error condition @p rhs.
95+
* Creating a StatusOr from an OK status is not permitted
96+
* and invokes `std::abort()`.
97+
*
98+
* @par Post-conditions
99+
* `ok() == false` and `status() == rhs`.
100+
*
101+
* @param rhs the status to initialize the object.
102+
*/
103+
StatusOr(Status rhs) : status_(std::move(rhs)) {
104+
if(status_.IsOk()){
105+
std::cerr << "Constructing StatusOr<T> from OK status is not allowed" << std::endl;
106+
std::abort();
107+
}
108+
}
109+
110+
/**
111+
* Creates a new `StatusOr<T>` holding the value @p rhs.
112+
*
113+
* @par Post-conditions
114+
* `ok() == true` and `value() == rhs`.
115+
*
116+
* @param rhs the value used to initialize the object.
117+
*/
118+
StatusOr(T const& rhs) : status_() {
119+
new (&value_) T(rhs);
120+
}
121+
122+
StatusOr(T&& rhs) : status_() {
123+
new (&value_) T(std::move(rhs));
124+
}
125+
126+
StatusOr(StatusOr const& rhs) : status_(rhs.status_) {
127+
if(ok()) {
128+
new (&value_) T(rhs.value_);
129+
}
130+
}
131+
132+
StatusOr(StatusOr&& rhs) : status_(std::move(rhs.status_)) {
133+
if(ok()) {
134+
new (&value_) T(std::move(rhs.value_));
135+
}
136+
}
137+
138+
~StatusOr() {
139+
if(ok()) {
140+
value_.~T();
141+
}
142+
}
143+
144+
/**
145+
* @name Status accessors.
146+
*
147+
* @return All these member functions return the (properly ref and
148+
* const-qualified) status. Iff the object contains a value then
149+
* `status().ok() == true`.
150+
*/
151+
Status const& status() const { return status_; }
152+
153+
inline bool ok() const { return status_.IsOk(); }
154+
explicit inline operator bool() const { return ok(); }
155+
156+
/**
157+
* @name Deference operators.
158+
*
159+
* @warning Using these operators when `ok() == false` results in undefined
160+
* behavior.
161+
*
162+
* @return All these return a (properly ref and const-qualified) reference to
163+
* the underlying value.
164+
*/
165+
T& operator*() & { return value_; }
166+
T const& operator*() const& { return value_; }
167+
T&& operator*() && { return std::move(value_); }
168+
169+
/**
170+
* @name Member access operators.
171+
*
172+
* @warning Using these operators when `ok() == false` results in undefined
173+
* behavior.
174+
*
175+
* @return All these return a (properly ref and const-qualified) pointer to
176+
* the underlying value.
177+
*/
178+
T* operator->() & { return &value_; }
179+
T const* operator->() const& { return &value_; }
180+
181+
/**
182+
* @name Value accessors.
183+
*
184+
* @return All these member functions return a (properly ref and
185+
* const-qualified) reference to the underlying value.
186+
*
187+
* Aborts the program if `!ok()`
188+
*/
189+
T& value() & {
190+
check_value();
191+
return **this;
192+
}
193+
194+
T const& value() const& {
195+
check_value();
196+
return **this;
197+
}
198+
199+
T&& value() && {
200+
check_value();
201+
return std::move(**this);
202+
}
203+
204+
private:
205+
void check_value() const {
206+
if(!ok()) {
207+
std::cerr << status_ << std::endl;
208+
std::abort();
209+
}
210+
}
211+
212+
Status const status_;
213+
union{
214+
T value_;
215+
};
216+
};
217+
218+
} // namespace gax
219+
} // namespace google
220+
221+
#endif // GOOGLE_GAX_STATUS_OR_H_

0 commit comments

Comments
 (0)