pw_result: Add experimental result module
The pw_result module defines a Result type which stores a Status
alongside some data. It is provided as a convenient way to return a
value and a status together, without requiring an "out" pointer.
Change-Id: Ibb630c7167faf6832e2b5e943220250ac820604e
diff --git a/BUILD.gn b/BUILD.gn
index c63a20b..776133c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -56,6 +56,7 @@
"$dir_pw_polyfill",
"$dir_pw_preprocessor",
"$dir_pw_protobuf",
+ "$dir_pw_result",
"$dir_pw_span",
"$dir_pw_status",
"$dir_pw_string",
@@ -86,6 +87,7 @@
"$dir_pw_polyfill:tests",
"$dir_pw_preprocessor:tests",
"$dir_pw_protobuf:tests",
+ "$dir_pw_result:tests",
"$dir_pw_span:tests",
"$dir_pw_status:tests",
"$dir_pw_string:tests",
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 640fb9e..9edef7f 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -79,6 +79,7 @@
"$dir_pw_presubmit:docs",
"$dir_pw_protobuf:docs",
"$dir_pw_protobuf_compiler:docs",
+ "$dir_pw_result:docs",
"$dir_pw_span:docs",
"$dir_pw_status:docs",
"$dir_pw_string:docs",
diff --git a/modules.gni b/modules.gni
index 6fd85fe..824ce7a 100644
--- a/modules.gni
+++ b/modules.gni
@@ -40,6 +40,7 @@
dir_pw_presubmit = "$dir_pigweed/pw_presubmit"
dir_pw_protobuf = "$dir_pigweed/pw_protobuf"
dir_pw_protobuf_compiler = "$dir_pigweed/pw_protobuf_compiler"
+dir_pw_result = "$dir_pigweed/pw_result"
dir_pw_span = "$dir_pigweed/pw_span"
dir_pw_status = "$dir_pigweed/pw_status"
dir_pw_string = "$dir_pigweed/pw_string"
diff --git a/pw_result/BUILD b/pw_result/BUILD
new file mode 100644
index 0000000..b296313
--- /dev/null
+++ b/pw_result/BUILD
@@ -0,0 +1,37 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+ "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"]) # Apache License 2.0
+
+pw_cc_library(
+ name = "pw_result",
+ hdrs = [
+ "public/pw_result/result.h",
+ ],
+ includes = ["public"],
+)
+
+pw_cc_test(
+ name = "result_test",
+ srcs = [ "result_test.cc" ],
+ deps = ["//pw_result"],
+)
diff --git a/pw_result/BUILD.gn b/pw_result/BUILD.gn
new file mode 100644
index 0000000..26bbb7f
--- /dev/null
+++ b/pw_result/BUILD.gn
@@ -0,0 +1,39 @@
+# Copyright 2019 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("default_config") {
+ include_dirs = [ "public" ]
+}
+
+source_set("pw_result") {
+ public_configs = [ ":default_config" ]
+ public_deps = [ "$dir_pw_status" ]
+ public = [ "public/pw_result/result.h" ]
+}
+
+pw_test_group("tests") {
+ tests = [ ":result_test" ]
+}
+
+pw_test("result_test") {
+ deps = [ ":pw_result" ]
+ sources = [ "result_test.cc" ]
+}
+
+pw_doc_group("docs") {
+ sources = [ "docs.rst" ]
+}
diff --git a/pw_result/README.md b/pw_result/README.md
new file mode 100644
index 0000000..a1a5c60
--- /dev/null
+++ b/pw_result/README.md
@@ -0,0 +1 @@
+# pw\_result: Experimental status with value class
diff --git a/pw_result/docs.rst b/pw_result/docs.rst
new file mode 100644
index 0000000..db9ba14
--- /dev/null
+++ b/pw_result/docs.rst
@@ -0,0 +1,16 @@
+.. default-domain:: cpp
+
+.. highlight:: sh
+
+.. _chapter-pw-result:
+
+---------
+pw_result
+---------
+``pw::Result`` is an experimental class that combines a status with a value to
+simplify writing and using APIs.
+
+.. warning::
+
+ This module is experimental. Its impact on code size and stack usage has not
+ yet been profiled. Use at your own risk.
diff --git a/pw_result/public/pw_result/result.h b/pw_result/public/pw_result/result.h
new file mode 100644
index 0000000..3a2231f
--- /dev/null
+++ b/pw_result/public/pw_result/result.h
@@ -0,0 +1,91 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <algorithm>
+
+#include "pw_status/status.h"
+
+namespace pw {
+
+// A Result represents the result of an operation which can fail. It is a
+// convenient wrapper around returning a Status alongside some data when the
+// status is OK.
+template <typename T>
+class Result {
+ public:
+ constexpr Result(T&& value) : value_(std::move(value)), status_(Status::OK) {}
+ constexpr Result(const T& value) : value_(value), status_(Status::OK) {}
+
+ template <typename... Args>
+ constexpr Result(std::in_place_t, Args&&... args)
+ : value_(std::forward<Args>(args)...), status_(Status::OK) {}
+
+ constexpr Result(Status status) : status_(status) { EnsureOk(); }
+ constexpr Result(Status::Code code) : status_(code) { EnsureOk(); }
+
+ constexpr Result(const Result&) = default;
+ constexpr Result& operator=(const Result&) = default;
+
+ constexpr Result(Result&&) = default;
+ constexpr Result& operator=(Result&&) = default;
+
+ constexpr Status status() const { return status_; }
+ constexpr bool ok() const { return status_.ok(); }
+
+ constexpr T& value() & {
+ EnsureOk();
+ return value_;
+ }
+
+ constexpr const T& value() const& {
+ EnsureOk();
+ return value_;
+ }
+
+ constexpr T&& value() && {
+ EnsureOk();
+ return std::move(value_);
+ }
+
+ template <typename U>
+ constexpr T value_or(U&& default_value) const& {
+ if (ok()) {
+ return value_;
+ }
+ return std::forward<U>(default_value);
+ }
+
+ template <typename U>
+ constexpr T value_or(U&& default_value) && {
+ if (ok()) {
+ return std::move(value_);
+ }
+ return std::forward<U>(default_value);
+ }
+
+ private:
+ union {
+ T value_;
+ };
+ Status status_;
+
+ void EnsureOk() const {
+ if (!ok()) {
+ // TODO(frolv): Crash.
+ }
+ }
+};
+
+} // namespace pw
diff --git a/pw_result/result_test.cc b/pw_result/result_test.cc
new file mode 100644
index 0000000..cad7ae4
--- /dev/null
+++ b/pw_result/result_test.cc
@@ -0,0 +1,76 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_result/result.h"
+
+#include "gtest/gtest.h"
+
+namespace pw {
+namespace {
+
+TEST(Result, CreateOk) {
+ Result<const char*> res("hello");
+ EXPECT_TRUE(res.ok());
+ EXPECT_EQ(res.status(), Status::OK);
+ EXPECT_EQ(res.value(), "hello");
+}
+
+TEST(Result, CreateNotOk) {
+ Result<int> res(Status::DATA_LOSS);
+ EXPECT_FALSE(res.ok());
+ EXPECT_EQ(res.status(), Status::DATA_LOSS);
+}
+
+TEST(Result, ValueOr) {
+ Result<int> good(3);
+ Result<int> bad(Status::DATA_LOSS);
+ EXPECT_EQ(good.value_or(42), 3);
+ EXPECT_EQ(bad.value_or(42), 42);
+}
+
+TEST(Result, ConstructType) {
+ struct Point {
+ Point(int a, int b) : x(a), y(b) {}
+
+ int x;
+ int y;
+ };
+
+ Result<Point> origin{std::in_place, 0, 0};
+ ASSERT_TRUE(origin.ok());
+ ASSERT_EQ(origin.value().x, 0);
+ ASSERT_EQ(origin.value().y, 0);
+}
+
+Result<float> Divide(float a, float b) {
+ if (b == 0) {
+ return Status::INVALID_ARGUMENT;
+ }
+ return a / b;
+}
+
+TEST(Divide, ReturnOk) {
+ Result res = Divide(10, 5);
+ ASSERT_TRUE(res.ok());
+ EXPECT_EQ(res.value(), 2.0f);
+}
+
+TEST(Divide, ReturnNotOk) {
+ Result res = Divide(10, 0);
+ EXPECT_FALSE(res.ok());
+ EXPECT_EQ(res.status(), Status::INVALID_ARGUMENT);
+}
+
+} // namespace
+} // namespace pw