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