pw_sync: add class InlineBorrowable

InlineBorrowable owns an object and a mutex that guards the object.
Both the object and mutex are constructed in-place, and the object can
only be accessed by calling acquire() or one of the acquire_*() methods.

InlineBorrowable should be used whenever an object needs to be guarded
for its entire lifetime by a single mutex. InlineBorrowable extends
Borrowable, so it can be passed to existing APIs by reference.

Change-Id: Ib6f2a0471cf609af10524da7acc8a7de18f2874c
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/103180
Commit-Queue: Anton Markov <amarkov@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_sync/BUILD.bazel b/pw_sync/BUILD.bazel
index 079d8ca..78fc7b2 100644
--- a/pw_sync/BUILD.bazel
+++ b/pw_sync/BUILD.bazel
@@ -122,6 +122,20 @@
 )
 
 pw_cc_library(
+    name = "inline_borrowable",
+    hdrs = [
+        "public/pw_sync/inline_borrowable.h",
+        "public/pw_sync/internal/borrowable_storage.h",
+    ],
+    includes = ["public"],
+    deps = [
+        ":borrow",
+        ":mutex",
+        ":virtual_basic_lockable",
+    ],
+)
+
+pw_cc_library(
     name = "virtual_basic_lockable",
     hdrs = [
         "public/pw_sync/virtual_basic_lockable.h",
@@ -438,6 +452,20 @@
 )
 
 pw_cc_test(
+    name = "inline_borrowable_test",
+    srcs = [
+        "inline_borrowable_test.cc",
+    ],
+    deps = [
+        ":inline_borrowable",
+        ":interrupt_spin_lock",
+        ":lock_annotations",
+        ":mutex",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
     name = "binary_semaphore_facade_test",
     srcs = [
         "binary_semaphore_facade_test.cc",
diff --git a/pw_sync/BUILD.gn b/pw_sync/BUILD.gn
index 2da99c0..2d7a867 100644
--- a/pw_sync/BUILD.gn
+++ b/pw_sync/BUILD.gn
@@ -69,6 +69,19 @@
   ]
 }
 
+pw_source_set("inline_borrowable") {
+  public = [
+    "public/pw_sync/inline_borrowable.h",
+    "public/pw_sync/internal/borrowable_storage.h",
+  ]
+  public_deps = [
+    ":borrow",
+    ":mutex",
+    ":virtual_basic_lockable",
+  ]
+  public_configs = [ ":public_include_path" ]
+}
+
 pw_source_set("virtual_basic_lockable") {
   public_configs = [ ":public_include_path" ]
   public = [ "public/pw_sync/virtual_basic_lockable.h" ]
@@ -206,6 +219,7 @@
     ":interrupt_spin_lock_facade_test",
     ":thread_notification_facade_test",
     ":timed_thread_notification_facade_test",
+    ":inline_borrowable_test",
   ]
 }
 
@@ -218,6 +232,16 @@
   ]
 }
 
+pw_test("inline_borrowable_test") {
+  sources = [ "inline_borrowable_test.cc" ]
+  deps = [
+    ":inline_borrowable",
+    ":interrupt_spin_lock",
+    ":lock_annotations",
+    ":mutex",
+  ]
+}
+
 pw_test("binary_semaphore_facade_test") {
   enable_if = pw_sync_BINARY_SEMAPHORE_BACKEND != ""
   sources = [
diff --git a/pw_sync/CMakeLists.txt b/pw_sync/CMakeLists.txt
index 192a0b1..11f268d 100644
--- a/pw_sync/CMakeLists.txt
+++ b/pw_sync/CMakeLists.txt
@@ -58,6 +58,18 @@
     pw_sync.virtual_basic_lockable
 )
 
+pw_add_module_library(pw_sync.inline_borrowable
+  HEADERS
+    public/pw_sync/inline_borrowable.h
+    public/pw_sync/internal/borrowable_storage.h
+  PUBLIC_INCLUDES
+    public
+  PUBLIC_DEPS
+    pw_sync.borrow
+    pw_sync.mutex
+    pw_sync.virtual_basic_lockable
+)
+
 pw_add_module_library(pw_sync.virtual_basic_lockable
   HEADERS
     public/pw_sync/virtual_basic_lockable.h
@@ -195,6 +207,19 @@
     pw_sync
 )
 
+pw_add_test(pw_sync.inline_borrowable_test
+  SOURCES
+    inline_borrowable_test.cc
+  DEPS
+    pw_sync.inline_borrowable
+    pw_sync.interrupt_spin_lock
+    pw_sync.lock_annotations
+    pw_sync.mutex
+  GROUPS
+    modules
+    pw_sync
+)
+
 if(NOT "${pw_sync.binary_semaphore_BACKEND}" STREQUAL
    "pw_sync.binary_semaphore.NO_BACKEND_SET")
   pw_add_test(pw_sync.binary_semaphore_facade_test
diff --git a/pw_sync/docs.rst b/pw_sync/docs.rst
index a4fc780..6e2cd53 100644
--- a/pw_sync/docs.rst
+++ b/pw_sync/docs.rst
@@ -1196,10 +1196,135 @@
     return buffer;
   }
 
+InlineBorrowable
+=================
+``InlineBorrowable`` is a helper to simplify the common use case where an object
+is wrapped in a ``Borrowable`` for its entire lifetime. The InlineBorrowable
+owns the guarded object and the lock object.
+
+InlineBorrowable has a separate parameter for the concrete lock type
+that is instantiated and a (possibly virtual) lock interface type that is
+referenced by users of the guarded object. The default lock is
+``pw::sync::VirtualMutex`` and the default lock interface is
+``pw::sync::VirtualBasicLockable``.
+
+An InlineBorrowable is a Borrowable with the same guarded object and lock
+interface types, and it can be passed directly to APIs that expect a Borrowable
+reference.
+
+Why use InlineBorrowable?
+-------------------------
+It is a safer and simpler way to guard an object for its entire lifetime. The
+unguarded object is never exposed and doesn't need to be stored in a separate
+variable or data member. The guarded object and its lock are guaranteed to have
+the same lifetime, and the lock cannot be re-used for any other purpose.
+
+Constructing objects in-place
+-----------------------------
+The guarded object and its lock are constructed in-place by the
+InlineBorrowable, and any constructor parameters required by the object or
+its lock must be passed through the InlineBorrowable constructor. There are
+several ways to do this:
+
+* Pass the parameters for the guarded object inline to the constructor. This is
+  the recommended way to construct the object when the lock does not require any
+  constructor parameters. Use the ``std::in_place`` marker to invoke the inline
+  constructor.
+
+  .. code-block:: cpp
+
+    InlineBorrowable<Foo> foo(std::in_place, foo_arg1, foo_arg2);
+    InlineBorrowable<std::array<int, 2>> foo_array(std::in_place, 1, 2);
+
+* Pass the parameters inside tuples:
+
+  .. code-block:: cpp
+
+    InlineBorrowable<Foo> foo(std::forward_as_tuple(foo_arg1, foo_arg2));
+
+    InlineBorrowable<Foo, MyLock> foo_lock(
+        std::forward_as_tuple(foo_arg1, foo_arg2),
+        std::forward_as_tuple(lock_arg1, lock_arg2));
+
+  .. note:: This approach only supports list initialization starting with C++20.
+
+* Use callables to construct the guarded object and lock object:
+
+  .. code-block:: cpp
+
+    InlineBorrowable<Foo> foo([&]{ return Foo{foo_arg1, foo_arg2}; });
+
+    InlineBorrowable<Foo, MyLock> foo_lock(
+        [&]{ return Foo{foo_arg1, foo_arg2}; }
+        [&]{ return MyLock{lock_arg1, lock_arg2}; }
+
+  .. note:: It is possible to construct and return objects that are not copyable
+    or movable, thanks to mandatory copy ellision (return value optimization).
+
+C++
+---
+.. cpp:class:: template <typename GuardedType, typename Lock = pw::sync::VirtualMutex, typename LockInterface = pw::sync::VirtualBasicLockable> InlineBorrowable
+
+  Holds an instance of ``GuardedType`` and ``Lock``. Access to the members of
+  ``GuardedType`` are protected by the ``Lock``.
+
+  This class implements ``Borrowable<GuardedType, LockInterface>``.
+
+  .. cpp:function:: template <typename... Args> constexpr explicit InlineBorrowable(std::in_place_t, Args&&... args)
+
+    Construct the guarded object by providing its cosntructor parameters inline.
+    The lock is constructed using its default constructor.
+
+    This constructor supports list initialization for arrays, structs, and
+    other objects such as ``std::array``.
+
+  .. cpp:function:: template <typename... ObjectArgs, typename... LockArgs> constexpr explicit InlineBorrowable( std::tuple<ObjectArgs...>&& object_args, std::tuple<LockArgs...>&& lock_args)
+
+    Construct the guarded object and lock by providing their construction
+    parameters using separate tuples. The 2nd tuple can be ommitted to construct
+    the lock using its default constructor.
+
+  .. cpp:function:: template <typename ObjectConstructor, typename LockConstructor> constexpr explicit InlineBorrowable( const ObjectConstructor& object_ctor, const LockConstructor& lock_ctor)
+
+    Construct the guarded object and lock by invoking the given callables. The
+    2nd callable can be ommitted to construct the lock using its default
+    constructor.
+
+Example in C++
+^^^^^^^^^^^^^^
+.. code-block:: cpp
+
+  #include <utility>
+
+  #include "pw_bytes/span.h"
+  #include "pw_i2c/initiator.h"
+  #include "pw_status/result.h"
+  #include "pw_sync/inline_borrowable.h"
+
+  struct I2cOptions;
+
+  class ExampleI2c : public pw::i2c::Initiator {
+   public:
+    ExampleI2c(int bus_id, I2cOptions options);
+    // ...
+  };
+
+  int kBusId;
+  I2cOptions opts;
+
+  pw::sync::InlineBorrowable<ExampleI2c> i2c(std::in_place, kBusId, opts);
+
+  pw::Result<ConstByteSpan> ReadI2cData(
+    pw::sync::Borrowable<pw::i2c::Initiator>& initiator,
+    ByteSpan buffer);
+
+  pw::Result<ConstByteSpan> ReadData(ByteSpan buffer) {
+    return ReadI2cData(i2c, buffer);
+  }
+
 --------------------
 Signaling Primitives
 --------------------
-
 Native signaling primitives tend to vary more compared to critial section locks
 across different platforms. For example, although common signaling primtives
 like semaphores are in most if not all RTOSes and even POSIX, it was not in the
diff --git a/pw_sync/inline_borrowable_test.cc b/pw_sync/inline_borrowable_test.cc
new file mode 100644
index 0000000..a626027
--- /dev/null
+++ b/pw_sync/inline_borrowable_test.cc
@@ -0,0 +1,202 @@
+// Copyright 2021 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_sync/inline_borrowable.h"
+
+#include <array>
+#include <chrono>
+#include <tuple>
+
+#include "gtest/gtest.h"
+#include "pw_sync/interrupt_spin_lock.h"
+#include "pw_sync/lock_annotations.h"
+#include "pw_sync/mutex.h"
+
+namespace pw::sync {
+namespace {
+
+using namespace std::chrono_literals;
+
+// A trivial type that is copyable and movable.
+struct TrivialType {
+  bool yes() const { return true; }
+};
+
+// A custom type that is neither copyable nor movable.
+class CustomType {
+ public:
+  constexpr CustomType(int x, int y) : x_(x), y_(y) {}
+
+  CustomType(const CustomType&) = delete;
+  CustomType& operator=(const CustomType&) = delete;
+  CustomType(CustomType&&) = delete;
+  CustomType&& operator=(CustomType&&) = delete;
+
+  std::pair<int, int> data() const { return std::make_pair(x_, y_); }
+
+ private:
+  int x_, y_;
+};
+
+// A custom lockable interface.
+class PW_LOCKABLE("VirtualCustomLocakble") VirtualCustomLockable {
+ public:
+  virtual ~VirtualCustomLockable() {}
+
+  virtual void lock() PW_EXCLUSIVE_LOCK_FUNCTION() = 0;
+  virtual void unlock() PW_UNLOCK_FUNCTION() = 0;
+};
+
+// A custom mutex type that requires a constructor parameter.
+class PW_LOCKABLE("VirtualCustomMutex") VirtualCustomMutex
+    : public VirtualCustomLockable {
+ public:
+  explicit VirtualCustomMutex(int id) : mutex_{}, id_{id} {}
+
+  void lock() override PW_EXCLUSIVE_LOCK_FUNCTION() { mutex_.lock(); }
+  void unlock() override PW_UNLOCK_FUNCTION() { mutex_.unlock(); }
+
+  int id() const { return id_; }
+
+ private:
+  pw::sync::Mutex mutex_;
+  int id_;
+};
+
+TEST(InlineBorrowableTest, TestTrivialType) {
+  InlineBorrowable<TrivialType> trivial;
+  EXPECT_TRUE(trivial.acquire()->yes());
+}
+
+TEST(InlineBorrowableTest, TestCustomTypeInPlace) {
+  InlineBorrowable<CustomType> custom(std::in_place, 1, 2);
+  EXPECT_EQ(custom.acquire()->data(), std::make_pair(1, 2));
+}
+
+TEST(InlineBorrowableTest, TestCustomTypeFromTuple) {
+  InlineBorrowable<CustomType> custom(std::make_tuple(1, 2));
+  EXPECT_EQ(custom.acquire()->data(), std::make_pair(1, 2));
+}
+
+TEST(InlineBorrowableTest, TestCustomTypeFromFactory) {
+  InlineBorrowable<CustomType> custom([] { return CustomType(1, 2); });
+  EXPECT_EQ(custom.acquire()->data(), std::make_pair(1, 2));
+}
+
+TEST(InlineBorrowableTest, TestTrivialTypeWithInterruptSpinLock) {
+  InlineBorrowable<TrivialType, VirtualInterruptSpinLock>
+      trivial_interrupt_safe;
+  EXPECT_TRUE(trivial_interrupt_safe.acquire()->yes());
+}
+
+TEST(InlineBorrowableTest, TestCustomTypeWithInterruptSpinLock) {
+  InlineBorrowable<CustomType, VirtualInterruptSpinLock> custom_interrupt_safe(
+      std::in_place, 1, 2);
+  EXPECT_EQ(custom_interrupt_safe.acquire()->data(), std::make_pair(1, 2));
+}
+
+TEST(InlineBorrowableTest, TestCustomTypeWithCustomMutexFromTuple) {
+  InlineBorrowable<CustomType, VirtualCustomMutex, VirtualCustomLockable>
+      custom_mutex(std::make_tuple(1, 2), std::make_tuple(42));
+  EXPECT_EQ(custom_mutex.acquire()->data(), std::make_pair(1, 2));
+}
+
+TEST(InlineBorrowableTest, TestCustomTypeWithCustomMutexFromFactory) {
+  InlineBorrowable<CustomType, VirtualCustomMutex, VirtualCustomLockable>
+      custom_mutex([] { return CustomType(1, 2); },
+                   [] { return VirtualCustomMutex(42); });
+  EXPECT_EQ(custom_mutex.acquire()->data(), std::make_pair(1, 2));
+}
+
+TEST(InlineBorrowableTest, TestArrayAggregateInitializationInPlace) {
+  using ArrayAggregate = std::array<int, 2>;
+  InlineBorrowable<ArrayAggregate> aggregate{std::in_place, 1, 2};
+  EXPECT_EQ((*aggregate.acquire())[0], 1);
+  EXPECT_EQ((*aggregate.acquire())[1], 2);
+}
+
+struct StructAggregate {
+  int a;
+  int b;
+};
+
+TEST(InlineBorrowableTest, TestStructAggregateInitializationInPlace) {
+  InlineBorrowable<StructAggregate> aggregate{std::in_place, 1, 2};
+  EXPECT_EQ(aggregate.acquire()->a, 1);
+  EXPECT_EQ(aggregate.acquire()->b, 2);
+}
+
+TEST(InlineBorrowableTest, TestStructAggregateInitializationFromFactory) {
+  InlineBorrowable<StructAggregate> aggregate(
+      []() -> StructAggregate { return {.a = 1, .b = 2}; });
+  EXPECT_EQ(aggregate.acquire()->a, 1);
+  EXPECT_EQ(aggregate.acquire()->b, 2);
+}
+
+TEST(InlineBorrowableTest,
+     TestStructAggregateInitializationFromMutableFactory) {
+  int i = 0;
+  auto factory = [&i]() mutable -> StructAggregate {
+    i++;
+    return {.a = 1, .b = 2};
+  };
+  InlineBorrowable<StructAggregate> aggregate(factory);
+  EXPECT_EQ(aggregate.acquire()->a, 1);
+  EXPECT_EQ(aggregate.acquire()->b, 2);
+}
+
+struct ReferenceTypes {
+  ReferenceTypes(const int& a, int& b, BorrowedPointer<int>&& c)
+      : in(a), out(b), borrowed(std::move(c)) {}
+  const int& in;
+  int& out;
+  BorrowedPointer<int> borrowed;  // move-only type
+};
+
+class InlineBorrowableReferenceTypesTest : public ::testing::Test {
+ protected:
+  int input_ = 1;
+  int output_ = 2;
+  InlineBorrowable<int> borrowable_{std::in_place, 3};
+
+  void Validate(BorrowedPointer<ReferenceTypes>&& references) {
+    EXPECT_EQ(references->in, 1);
+    EXPECT_EQ(references->out, 2);
+    EXPECT_EQ(*references->borrowed, 3);
+
+    references->out = -2;
+    EXPECT_EQ(output_, -2);
+  }
+};
+
+TEST_F(InlineBorrowableReferenceTypesTest, TestInPlace) {
+  InlineBorrowable<ReferenceTypes> references(
+      std::in_place, input_, output_, borrowable_.acquire());
+  Validate(references.acquire());
+}
+
+TEST_F(InlineBorrowableReferenceTypesTest, TestFromTuple) {
+  InlineBorrowable<ReferenceTypes> references(
+      std::forward_as_tuple(input_, output_, borrowable_.acquire()));
+  Validate(references.acquire());
+}
+
+TEST_F(InlineBorrowableReferenceTypesTest, TestFromFactory) {
+  InlineBorrowable<ReferenceTypes> references(
+      [&] { return ReferenceTypes(input_, output_, borrowable_.acquire()); });
+  Validate(references.acquire());
+}
+
+}  // namespace
+}  // namespace pw::sync
diff --git a/pw_sync/public/pw_sync/inline_borrowable.h b/pw_sync/public/pw_sync/inline_borrowable.h
new file mode 100644
index 0000000..e9d0d93
--- /dev/null
+++ b/pw_sync/public/pw_sync/inline_borrowable.h
@@ -0,0 +1,121 @@
+// Copyright 2022 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 <tuple>
+#include <utility>
+
+#include "pw_sync/borrow.h"
+#include "pw_sync/internal/borrowable_storage.h"
+#include "pw_sync/mutex.h"
+#include "pw_sync/virtual_basic_lockable.h"
+
+namespace pw::sync {
+
+// InlineBorrowable holds an object of GuardedType and a Lock that guards
+// access to the object. It should be used when an object should be guarded for
+// its entire lifecycle by a single lock.
+//
+// This object should be shared with other componetns as a reference of type
+// Borrowable<GuardedType, LockInterface>.
+//
+template <typename GuardedType,
+          typename Lock = pw::sync::VirtualMutex,
+          typename LockInterface = pw::sync::VirtualBasicLockable>
+class InlineBorrowable : private internal::BorrowableStorage<GuardedType, Lock>,
+                         public Borrowable<GuardedType, LockInterface> {
+  using Storage = internal::BorrowableStorage<GuardedType, Lock>;
+  using Base = Borrowable<GuardedType, LockInterface>;
+
+ public:
+  // Construct the guarded object and lock using their default constructors.
+  constexpr InlineBorrowable()
+      : Storage(std::in_place), Base(Storage::object_, Storage::lock_) {}
+
+  // Construct the guarded object by providing its constructor arguments inline.
+  //
+  // This constructor supports list initialization for arrays, structs, and
+  // other objects such as std::array.
+  //
+  // Example:
+  //
+  //   InlineBorrowable<Foo> foo(std::in_place, foo_arg1, foo_arg2);
+  //
+  //   InlineBorrowable<std::array<int, 2>> foo_array(std::in_place, 1, 2);
+  //
+  template <typename... Args>
+  constexpr explicit InlineBorrowable(std::in_place_t, Args&&... args)
+      : Storage(std::in_place, std::forward<Args>(args)...),
+        Base(Storage::object_, Storage::lock_) {}
+
+  // Construct the guarded object and lock by providing their construction
+  // parameters using separate tuples.
+  //
+  // Example:
+  //
+  //   InlineBorrowable<Foo> foo(std::forward_as_tuple(foo_arg1, foo_arg2));
+  //
+  //   InlineBorrowable<Foo, MyLock> foo_lock(
+  //       std::forward_as_tuple(foo_arg1, foo_arg2),
+  //       std::forward_as_tuple(lock_arg1, lock_arg2));
+  //
+  // Note: this constructor only supports list initialization with C++20 or
+  // later, because it requires https://wg21.link/p0960.
+  //
+  template <typename... ObjectArgs, typename... LockArgs>
+  constexpr explicit InlineBorrowable(
+      std::tuple<ObjectArgs...>&& object_args,
+      std::tuple<LockArgs...>&& lock_args = std::make_tuple())
+      : Storage(std::forward<std::tuple<ObjectArgs...>>(object_args),
+                std::forward<std::tuple<LockArgs...>>(lock_args)),
+        Base(Storage::object_, Storage::lock_) {}
+
+  // Construct the guarded object and lock by providing factory functions.
+  //
+  // Example:
+  //
+  //    InlineBorrowable<Foo> foo([&]{ return Foo{foo_arg1, foo_arg2}; });
+  //
+  //    InlineBorrowable<Foo, MyLock> foo_lock(
+  //        [&]{ return Foo{foo_arg1, foo_arg2}; }
+  //        [&]{ return MyLock{lock_arg1, lock_arg2}; }
+  //
+  template <typename ObjectConstructor, typename LockConstructor = Lock()>
+  constexpr explicit InlineBorrowable(
+      const ObjectConstructor& object_ctor,
+      const LockConstructor& lock_ctor = internal::DefaultConstruct<Lock>)
+      : Storage(object_ctor, lock_ctor),
+        Base(Storage::object_, Storage::lock_) {}
+
+  template <typename ObjectConstructor, typename LockConstructor = Lock()>
+  constexpr explicit InlineBorrowable(
+      ObjectConstructor& object_ctor,
+      const LockConstructor& lock_ctor = internal::DefaultConstruct<Lock>)
+      : Storage(object_ctor, lock_ctor),
+        Base(Storage::object_, Storage::lock_) {}
+
+  template <typename ObjectConstructor, typename LockConstructor = Lock()>
+  constexpr explicit InlineBorrowable(const ObjectConstructor& object_ctor,
+                                      LockConstructor& lock_ctor)
+      : Storage(object_ctor, lock_ctor),
+        Base(Storage::object_, Storage::lock_) {}
+
+  template <typename ObjectConstructor, typename LockConstructor = Lock()>
+  constexpr explicit InlineBorrowable(ObjectConstructor& object_ctor,
+                                      LockConstructor& lock_ctor)
+      : Storage(object_ctor, lock_ctor),
+        Base(Storage::object_, Storage::lock_) {}
+};
+
+}  // namespace pw::sync
diff --git a/pw_sync/public/pw_sync/internal/borrowable_storage.h b/pw_sync/public/pw_sync/internal/borrowable_storage.h
new file mode 100644
index 0000000..d05965e
--- /dev/null
+++ b/pw_sync/public/pw_sync/internal/borrowable_storage.h
@@ -0,0 +1,70 @@
+// Copyright 2022 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 <tuple>
+
+namespace pw::sync::internal {
+
+// BorrowableStorage stores an object and associated lock. Both objects are
+// constructed in-place.
+template <typename ObjectType, typename Lock>
+class BorrowableStorage {
+ protected:
+  // Construct the object in-place using a list of arguments.
+  template <typename... Args>
+  constexpr BorrowableStorage(std::in_place_t, Args&&... args)
+      : object_{std::forward<Args>(args)...}, lock_ {}
+  {}
+
+  // Construct the object and lock in-place using the provided parameters.
+  template <typename... ObjectArgs, typename... LockArgs>
+  constexpr BorrowableStorage(std::tuple<ObjectArgs...>&& object_args,
+                              std::tuple<LockArgs...>&& lock_args)
+      : object_{std::make_from_tuple<ObjectType>(
+            std::forward<std::tuple<ObjectArgs...>>(object_args))},
+        lock_{std::make_from_tuple<Lock>(
+            std::forward<std::tuple<LockArgs...>>(lock_args))} {}
+
+  // Construct the object and lock in-place using the provided factories.
+  template <typename ObjectConstructor, typename LockConstructor>
+  constexpr BorrowableStorage(const ObjectConstructor& object_ctor,
+                              const LockConstructor& lock_ctor)
+      : object_{object_ctor()}, lock_{lock_ctor()} {}
+
+  template <typename ObjectConstructor, typename LockConstructor>
+  constexpr BorrowableStorage(ObjectConstructor& object_ctor,
+                              const LockConstructor& lock_ctor)
+      : object_{object_ctor()}, lock_{lock_ctor()} {}
+
+  template <typename ObjectConstructor, typename LockConstructor>
+  constexpr BorrowableStorage(const ObjectConstructor& object_ctor,
+                              LockConstructor& lock_ctor)
+      : object_{object_ctor()}, lock_{lock_ctor()} {}
+
+  template <typename ObjectConstructor, typename LockConstructor>
+  constexpr BorrowableStorage(ObjectConstructor& object_ctor,
+                              LockConstructor& lock_ctor)
+      : object_{object_ctor()}, lock_{lock_ctor()} {}
+
+  ObjectType object_;
+  Lock lock_;
+};
+
+template <typename T>
+T DefaultConstruct() {
+  return T();
+}
+
+}  // namespace pw::sync::internal