pw_persistent_ram: Default construct from mutator

Allows users of a persistent RAM object to optionally default-construct
an object when requesting a Mutator handle.

Change-Id: I228d8afd31ada60414cddf9a9bebc2fd5a908830
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/41340
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Armando Montanez <amontanez@google.com>
diff --git a/pw_persistent_ram/BUILD.gn b/pw_persistent_ram/BUILD.gn
index 729656f..f992280 100644
--- a/pw_persistent_ram/BUILD.gn
+++ b/pw_persistent_ram/BUILD.gn
@@ -47,7 +47,10 @@
 }
 
 pw_test("persistent_test") {
-  deps = [ ":pw_persistent_ram" ]
+  deps = [
+    ":pw_persistent_ram",
+    dir_pw_random,
+  ]
   sources = [ "persistent_test.cc" ]
 }
 
diff --git a/pw_persistent_ram/docs.rst b/pw_persistent_ram/docs.rst
index 5286c86..19384bd 100644
--- a/pw_persistent_ram/docs.rst
+++ b/pw_persistent_ram/docs.rst
@@ -188,13 +188,14 @@
       // Once this scope ends, we know the persistent object has been updated
       // to reflect changes.
       {
-        auto& mutable_crash_info = persistent_crash_info.mutator();
-        vsnprintf(mutable_crash_info.reason,
-                  sizeof(mutable_crash_info.reason),
+        auto& mutable_crash_info =
+            persistent_crash_info.mutator(GetterAction::kReset);
+        vsnprintf(mutable_crash_info->reason,
+                  sizeof(mutable_crash_info->reason),
                   fmt,
                   args);
-        mutable_crash_info.uptime_ms = system::GetUptimeMs();
-        mutable_crash_info.boot_id = system::GetBootId();
+        mutable_crash_info->uptime_ms = system::GetUptimeMs();
+        mutable_crash_info->boot_id = system::GetBootId();
       }
       // ...
     }
diff --git a/pw_persistent_ram/persistent_test.cc b/pw_persistent_ram/persistent_test.cc
index 868ec1d..bd3de4f 100644
--- a/pw_persistent_ram/persistent_test.cc
+++ b/pw_persistent_ram/persistent_test.cc
@@ -16,6 +16,7 @@
 #include <type_traits>
 
 #include "gtest/gtest.h"
+#include "pw_random/xor_shift.h"
 
 namespace pw::persistent_ram {
 namespace {
@@ -92,6 +93,13 @@
 
   // Emulate invalidation of persistent section(s).
   void ZeroPersistentMemory() { memset(&buffer_, 0, sizeof(buffer_)); }
+  void RandomFillMemory() {
+    random::XorShiftStarRng64 rng(0x9ad75);
+    StatusWithSize sws = rng.Get(std::span<std::byte>(
+        reinterpret_cast<std::byte*>(&buffer_), sizeof(buffer_)));
+    ASSERT_TRUE(sws.ok());
+    ASSERT_EQ(sws.size(), sizeof(buffer_));
+  }
 
   // Allocate a chunk of aligned storage that can be independently controlled.
   std::aligned_storage_t<sizeof(Persistent<Coordinate>),
@@ -134,5 +142,35 @@
   }
 }
 
+TEST_F(MutablePersistentTest, ResetObject) {
+  {
+    // Emulate a boot where the persistent sections were lost and ended up in
+    // random data.
+    RandomFillMemory();
+    auto& persistent = *(new (&buffer_) Persistent<Coordinate>());
+
+    // Default construct of a Coordinate.
+    ASSERT_FALSE(persistent.has_value());
+    {
+      auto mutable_persistent = persistent.mutator(GetterAction::kReset);
+      mutable_persistent->x = 42;
+    }
+
+    EXPECT_EQ(42, persistent.value().x);
+    EXPECT_EQ(0, persistent.value().y);
+    EXPECT_EQ(0, persistent.value().z);
+
+    persistent.~Persistent();  // Emulate shutdown / global destructors.
+  }
+
+  {
+    // Emulate a boot where persistent memory was kept as is.
+    auto& persistent = *(new (&buffer_) Persistent<Coordinate>());
+    ASSERT_TRUE(persistent.has_value());
+    EXPECT_EQ(42, persistent.value().x);
+    EXPECT_EQ(0, persistent.value().y);
+  }
+}
+
 }  // namespace
 }  // namespace pw::persistent_ram
diff --git a/pw_persistent_ram/public/pw_persistent_ram/persistent.h b/pw_persistent_ram/public/pw_persistent_ram/persistent.h
index 556fd53..6067d3b 100644
--- a/pw_persistent_ram/public/pw_persistent_ram/persistent.h
+++ b/pw_persistent_ram/public/pw_persistent_ram/persistent.h
@@ -24,6 +24,15 @@
 
 namespace pw::persistent_ram {
 
+// Behavior to use when attempting to get a handle to the underlying data stored
+// in persistent memory.
+enum class GetterAction {
+  // Default-construct the object before returning a handle.
+  kReset,
+  // Assert that the object is valid before returning a handle.
+  kAssertValid,
+};
+
 // The Persistent class intentionally uses uninitialized memory, which triggers
 // compiler warnings. Disable those warnings for this file.
 PW_MODIFY_DIAGNOSTICS_PUSH();
@@ -119,9 +128,17 @@
 
   // Get a mutable handle to the underlying data.
   //
+  // Args:
+  //   action: Whether to default-construct the underlying value before
+  //           providing a mutator, or to assert that the object is valid
+  //           without modifying the underlying data.
   // Precondition: has_value() must be true.
-  Mutator mutator() {
-    PW_ASSERT(has_value());
+  Mutator mutator(GetterAction action = GetterAction::kAssertValid) {
+    if (action == GetterAction::kReset) {
+      emplace();
+    } else {
+      PW_ASSERT(has_value());
+    }
     return Mutator(*this);
   }