Bug 1418624 - Allow mozilla::Result to be moved, make unwrap{,Err}() move, and add inspect() APIs that return references. r=froydnj
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 13 Aug 2019 08:26:18 +0000
changeset 548107 9bc4747798bcdef658b12251d7381bc6bd8a7175
parent 548106 8cc56f3f8a0881cdc99356d545117c03997a34a9
child 548108 1fbe01c0e461673cf85e4c4c9334dad1ebbac3d5
push id11848
push userffxbld-merge
push dateMon, 26 Aug 2019 19:26:25 +0000
treeherdermozilla-beta@9b31bfdfac10 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1418624
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1418624 - Allow mozilla::Result to be moved, make unwrap{,Err}() move, and add inspect() APIs that return references. r=froydnj Also adjust some of the callers that were either calling unwrap() repeatedly on the same result, or were doing silly copies, to use inspect(). We could try to use stuff like: https://clang.llvm.org/docs/AttributeReference.html#consumed-annotation-checking Differential Revision: https://phabricator.services.mozilla.com/D41425
browser/app/winlauncher/ErrorHandler.h
browser/app/winlauncher/test/TestSameBinary.cpp
dom/base/nsContentSink.cpp
dom/media/MediaRecorder.cpp
mfbt/Result.h
mfbt/tests/TestResult.cpp
mozglue/misc/interceptor/PatcherDetour.h
toolkit/mozapps/extensions/AddonManagerStartup.cpp
toolkit/xre/LauncherRegistryInfo.cpp
xpcom/components/nsComponentManager.cpp
--- a/browser/app/winlauncher/ErrorHandler.h
+++ b/browser/app/winlauncher/ErrorHandler.h
@@ -23,17 +23,17 @@ void HandleLauncherError(const LauncherE
 // the main implementation.
 template <typename T>
 inline void HandleLauncherError(const LauncherResult<T>& aResult) {
   MOZ_ASSERT(aResult.isErr());
   if (aResult.isOk()) {
     return;
   }
 
-  HandleLauncherError(aResult.unwrapErr());
+  HandleLauncherError(aResult.inspectErr());
 }
 
 // This function is simply a convenience overload that unwraps the provided
 // GenericErrorResult<LauncherError> and forwards it to the main implementation.
 inline void HandleLauncherError(
     const GenericErrorResult<LauncherError>& aResult) {
   LauncherVoidResult r(aResult);
   HandleLauncherError(r);
--- a/browser/app/winlauncher/test/TestSameBinary.cpp
+++ b/browser/app/winlauncher/test/TestSameBinary.cpp
@@ -45,35 +45,35 @@ inline void PrintWinError(const char* aM
   printf("%s%s: %S\n", kMsgStart, aMsg, err.AsString().get());
 }
 
 template <typename T>
 inline void PrintLauncherError(const mozilla::LauncherResult<T>& aResult,
                                const char* aMsg = nullptr) {
   const char* const kSep = aMsg ? ": " : "";
   const char* msg = aMsg ? aMsg : "";
-  mozilla::LauncherError err = aResult.unwrapErr();
+  const mozilla::LauncherError& err = aResult.inspectErr();
   printf("%s%s%s%S (%s:%d)\n", kMsgStart, msg, kSep,
          err.mError.AsString().get(), err.mFile, err.mLine);
 }
 
 static int ChildMain(DWORD aExpectedParentPid) {
   mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
   if (parentPid.isErr()) {
     PrintLauncherError(parentPid);
     return 1;
   }
 
-  if (parentPid.unwrap() != aExpectedParentPid) {
+  if (parentPid.inspect() != aExpectedParentPid) {
     PrintErrorMsg("Unexpected mismatch of parent PIDs");
     return 1;
   }
 
   const DWORD kAccess = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE;
-  nsAutoHandle parentProcess(::OpenProcess(kAccess, FALSE, parentPid.unwrap()));
+  nsAutoHandle parentProcess(::OpenProcess(kAccess, FALSE, parentPid.inspect()));
   if (!parentProcess) {
     PrintWinError("Unexpectedly failed to call OpenProcess on parent");
     return 1;
   }
 
   mozilla::LauncherResult<bool> expectedSameBinary =
       mozilla::IsSameBinaryAsParentProcess();
   if (expectedSameBinary.isErr()) {
@@ -99,17 +99,17 @@ static int ChildMain(DWORD aExpectedPare
   ::CloseHandle(parentProcess.disown());
 
   // Querying a pid on a terminated process may still succeed some time after
   // that process has been terminated. For the purposes of this test, we'll poll
   // the OS until we cannot succesfully open the parentPid anymore.
   const uint32_t kMaxAttempts = 100;
   uint32_t curAttempt = 0;
   while (HANDLE p = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
-                                  parentPid.unwrap())) {
+                                  parentPid.inspect())) {
     ::CloseHandle(p);
     ::Sleep(100);
     ++curAttempt;
     if (curAttempt >= kMaxAttempts) {
       PrintErrorMsg(
           "Exhausted retry attempts waiting for parent pid to become invalid");
       return 1;
     }
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -722,17 +722,17 @@ nsresult nsContentSink::ProcessStyleLink
   };
 
   auto loadResultOrErr =
       mCSSLoader->LoadStyleLink(info, mRunsToCompletion ? nullptr : this);
   if (loadResultOrErr.isErr()) {
     return loadResultOrErr.unwrapErr();
   }
 
-  if (loadResultOrErr.unwrap().ShouldBlock() && !mRunsToCompletion) {
+  if (loadResultOrErr.inspect().ShouldBlock() && !mRunsToCompletion) {
     ++mPendingSheetCount;
     mScriptLoader->AddParserBlockingScriptExecutionBlocker();
   }
 
   return NS_OK;
 }
 
 nsresult nsContentSink::ProcessMETATag(nsIContent* aContent) {
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -426,23 +426,23 @@ class MediaRecorder::Session : public Pr
     {
       auto tracks(std::move(mMediaStreamTracks));
       for (RefPtr<MediaStreamTrack>& track : tracks) {
         track->RemovePrincipalChangeObserver(this);
       }
     }
 
     if (mRunningState.isOk() &&
-        mRunningState.unwrap() == RunningState::Idling) {
+        mRunningState.inspect() == RunningState::Idling) {
       LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this));
       // End the Session directly if there is no encoder.
       DoSessionEndTask(NS_OK);
     } else if (mRunningState.isOk() &&
-               (mRunningState.unwrap() == RunningState::Starting ||
-                mRunningState.unwrap() == RunningState::Running)) {
+               (mRunningState.inspect() == RunningState::Starting ||
+                mRunningState.inspect() == RunningState::Running)) {
       mRunningState = RunningState::Stopping;
     }
   }
 
   nsresult Pause() {
     LOG(LogLevel::Debug, ("Session.Pause"));
     MOZ_ASSERT(NS_IsMainThread());
 
@@ -627,17 +627,17 @@ class MediaRecorder::Session : public Pr
       return;
     }
 
     if (mMediaStreamReady) {
       return;
     }
 
     if (!mRunningState.isOk() ||
-        mRunningState.unwrap() != RunningState::Idling) {
+        mRunningState.inspect() != RunningState::Idling) {
       return;
     }
 
     nsTArray<RefPtr<mozilla::dom::MediaStreamTrack>> tracks;
     mMediaStream->GetTracks(tracks);
     uint8_t trackTypes = 0;
     int32_t audioTracks = 0;
     int32_t videoTracks = 0;
@@ -744,17 +744,17 @@ class MediaRecorder::Session : public Pr
     return PrincipalSubsumes(principal);
   }
 
   void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate) {
     LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mRunningState.isOk() ||
-        mRunningState.unwrap() != RunningState::Idling) {
+        mRunningState.inspect() != RunningState::Idling) {
       MOZ_ASSERT_UNREACHABLE("Double-init");
       return;
     }
 
     // Create a TaskQueue to read encode media data from MediaEncoder.
     MOZ_RELEASE_ASSERT(!mEncoderThread);
     RefPtr<SharedThreadPool> pool =
         GetMediaThreadPool(MediaThreadType::WEBRTC_DECODER);
@@ -889,25 +889,25 @@ class MediaRecorder::Session : public Pr
   void DoSessionEndTask(nsresult rv) {
     MOZ_ASSERT(NS_IsMainThread());
     if (mRunningState.isErr()) {
       // We have already ended with an error.
       return;
     }
 
     if (mRunningState.isOk() &&
-        mRunningState.unwrap() == RunningState::Stopped) {
+        mRunningState.inspect() == RunningState::Stopped) {
       // We have already ended gracefully.
       return;
     }
 
     bool needsStartEvent = false;
     if (mRunningState.isOk() &&
-        (mRunningState.unwrap() == RunningState::Idling ||
-         mRunningState.unwrap() == RunningState::Starting)) {
+        (mRunningState.inspect() == RunningState::Idling ||
+         mRunningState.inspect() == RunningState::Starting)) {
       needsStartEvent = true;
     }
 
     if (rv == NS_OK) {
       mRunningState = RunningState::Stopped;
     } else {
       mRunningState = Err(rv);
     }
@@ -980,17 +980,17 @@ class MediaRecorder::Session : public Pr
 
     NS_DispatchToMainThread(NewRunnableFrom([self = RefPtr<Session>(this), this,
                                              mime = mEncoder->MimeType()]() {
       if (mRunningState.isErr()) {
         return NS_OK;
       }
       mMimeType = mime;
       mRecorder->SetMimeType(mime);
-      auto state = mRunningState.unwrap();
+      RunningState state = mRunningState.inspect();
       if (state == RunningState::Starting || state == RunningState::Stopping) {
         if (state == RunningState::Starting) {
           // We set it to Running in the runnable since we can only assign
           // mRunningState on main thread. We set it before running the start
           // event runnable since that dispatches synchronously (and may cause
           // js calls to methods depending on mRunningState).
           mRunningState = RunningState::Running;
         }
--- a/mfbt/Result.h
+++ b/mfbt/Result.h
@@ -42,61 +42,81 @@ enum class PackingStrategy {
 template <typename V, typename E, PackingStrategy Strategy>
 class ResultImplementation;
 
 template <typename V, typename E>
 class ResultImplementation<V, E, PackingStrategy::Variant> {
   mozilla::Variant<V, E> mStorage;
 
  public:
+  ResultImplementation(ResultImplementation&&) = default;
+  ResultImplementation(const ResultImplementation&) = default;
+  ResultImplementation& operator=(const ResultImplementation&) = default;
+  ResultImplementation& operator=(ResultImplementation&&) = default;
+
+  explicit ResultImplementation(V&& aValue)
+      : mStorage(std::forward<V>(aValue)) {}
   explicit ResultImplementation(const V& aValue) : mStorage(aValue) {}
-  explicit ResultImplementation(E aErrorValue) : mStorage(aErrorValue) {}
+  explicit ResultImplementation(E aErrorValue)
+      : mStorage(std::forward<E>(aErrorValue)) {}
 
   bool isOk() const { return mStorage.template is<V>(); }
 
   // The callers of these functions will assert isOk() has the proper value, so
   // these functions (in all ResultImplementation specializations) don't need
   // to do so.
-  V unwrap() const { return mStorage.template as<V>(); }
-  E unwrapErr() const { return mStorage.template as<E>(); }
+  V unwrap() { return std::move(mStorage.template as<V>()); }
+  const V& inspect() const { return mStorage.template as<V>(); }
+
+  E unwrapErr() { return std::move(mStorage.template as<E>()); }
+  const E& inspectErr() const { return mStorage.template as<E>(); }
 };
 
 /**
  * mozilla::Variant doesn't like storing a reference. This is a specialization
  * to store E as pointer if it's a reference.
  */
 template <typename V, typename E>
 class ResultImplementation<V, E&, PackingStrategy::Variant> {
   mozilla::Variant<V, E*> mStorage;
 
  public:
+  explicit ResultImplementation(V&& aValue)
+      : mStorage(std::forward<V>(aValue)) {}
   explicit ResultImplementation(const V& aValue) : mStorage(aValue) {}
   explicit ResultImplementation(E& aErrorValue) : mStorage(&aErrorValue) {}
 
   bool isOk() const { return mStorage.template is<V>(); }
-  V unwrap() const { return mStorage.template as<V>(); }
-  E& unwrapErr() const { return *mStorage.template as<E*>(); }
+
+  const V& inspect() const { return mStorage.template as<V>(); }
+  V unwrap() { return std::move(mStorage.template as<V>()); }
+
+  E& unwrapErr() { return *mStorage.template as<E*>(); }
+  const E& inspectErr() const { return *mStorage.template as<E*>(); }
 };
 
 /**
  * Specialization for when the success type is Ok (or another empty class) and
  * the error type is a reference.
  */
 template <typename V, typename E>
 class ResultImplementation<V, E&, PackingStrategy::NullIsOk> {
   E* mErrorValue;
 
  public:
   explicit ResultImplementation(V) : mErrorValue(nullptr) {}
   explicit ResultImplementation(E& aErrorValue) : mErrorValue(&aErrorValue) {}
 
   bool isOk() const { return mErrorValue == nullptr; }
 
-  V unwrap() const { return V(); }
-  E& unwrapErr() const { return *mErrorValue; }
+  const V& inspect() const = delete;
+  V unwrap() { return V(); }
+
+  const E& inspectErr() const { return *mErrorValue; }
+  E& unwrapErr() { return *mErrorValue; }
 };
 
 /**
  * Specialization for when the success type is Ok (or another empty class) and
  * the error type is a value type which can never have the value 0 (as
  * determined by UnusedZero<>).
  */
 template <typename V, typename E>
@@ -108,18 +128,21 @@ class ResultImplementation<V, E, Packing
  public:
   explicit ResultImplementation(V) : mErrorValue(NullValue) {}
   explicit ResultImplementation(E aErrorValue) : mErrorValue(aErrorValue) {
     MOZ_ASSERT(aErrorValue != NullValue);
   }
 
   bool isOk() const { return mErrorValue == NullValue; }
 
-  V unwrap() const { return V(); }
-  E unwrapErr() const { return mErrorValue; }
+  const V& inspect() const = delete;
+  V unwrap() { return V(); }
+
+  const E& inspectErr() const { return mErrorValue; }
+  E unwrapErr() { return std::move(mErrorValue); }
 };
 
 /**
  * Specialization for when alignment permits using the least significant bit as
  * a tag bit.
  */
 template <typename V, typename E>
 class ResultImplementation<V*, E&, PackingStrategy::LowBitTagIsError> {
@@ -134,18 +157,21 @@ class ResultImplementation<V*, E&, Packi
   explicit ResultImplementation(E& aErrorValue)
       : mBits(reinterpret_cast<uintptr_t>(&aErrorValue) | 1) {
     MOZ_ASSERT((uintptr_t(&aErrorValue) % MOZ_ALIGNOF(E)) == 0,
                "Result errors must not be misaligned");
   }
 
   bool isOk() const { return (mBits & 1) == 0; }
 
-  V* unwrap() const { return reinterpret_cast<V*>(mBits); }
-  E& unwrapErr() const { return *reinterpret_cast<E*>(mBits ^ 1); }
+  V* inspect() const { return reinterpret_cast<V*>(mBits); }
+  V* unwrap() { return inspect(); }
+
+  E& inspectErr() const { return *reinterpret_cast<E*>(mBits ^ 1); }
+  E& unwrapErr() { return inspectErr(); }
 };
 
 // Return true if any of the struct can fit in a word.
 template <typename V, typename E>
 struct IsPackableVariant {
   struct VEbool {
     V v;
     E e;
@@ -169,28 +195,31 @@ struct IsPackableVariant {
  */
 template <typename V, typename E>
 class ResultImplementation<V, E, PackingStrategy::PackedVariant> {
   using Impl = typename IsPackableVariant<V, E>::Impl;
   Impl data;
 
  public:
   explicit ResultImplementation(V aValue) {
-    data.v = aValue;
+    data.v = std::move(aValue);
     data.ok = true;
   }
   explicit ResultImplementation(E aErrorValue) {
-    data.e = aErrorValue;
+    data.e = std::move(aErrorValue);
     data.ok = false;
   }
 
   bool isOk() const { return data.ok; }
 
-  V unwrap() const { return data.v; }
-  E unwrapErr() const { return data.e; }
+  const V& inspect() const { return data.v; }
+  V unwrap() { return std::move(data.v); }
+
+  const E& inspectErr() const { return data.e; }
+  E unwrapErr() { return std::move(data.e); }
 };
 
 // To use nullptr as a special value, we need the counter part to exclude zero
 // from its range of valid representations.
 //
 // By default assume that zero can be represented.
 template <typename T>
 struct UnusedZero {
@@ -292,65 +321,80 @@ auto ToResult(Result<V, E>&& aValue)
  */
 template <typename V, typename E>
 class MOZ_MUST_USE_TYPE Result final {
   using Impl = typename detail::SelectResultImpl<V, E>::Type;
 
   Impl mImpl;
 
  public:
-  /**
-   * Create a success result.
-   */
+  /** Create a success result. */
+  MOZ_IMPLICIT Result(V&& aValue) : mImpl(std::forward<V>(aValue)) {
+    MOZ_ASSERT(isOk());
+  }
+
+  /** Create a success result. */
   MOZ_IMPLICIT Result(const V& aValue) : mImpl(aValue) { MOZ_ASSERT(isOk()); }
 
-  /**
-   * Create an error result.
-   */
-  explicit Result(E aErrorValue) : mImpl(aErrorValue) { MOZ_ASSERT(isErr()); }
+  /** Create an error result. */
+  explicit Result(E aErrorValue) : mImpl(std::forward<E>(aErrorValue)) {
+    MOZ_ASSERT(isErr());
+  }
 
   /**
    * Implementation detail of MOZ_TRY().
    * Create an error result from another error result.
    */
   template <typename E2>
   MOZ_IMPLICIT Result(const GenericErrorResult<E2>& aErrorResult)
       : mImpl(aErrorResult.mErrorValue) {
     static_assert(mozilla::IsConvertible<E2, E>::value,
                   "E2 must be convertible to E");
     MOZ_ASSERT(isErr());
   }
 
   Result(const Result&) = default;
+  Result(Result&&) = default;
   Result& operator=(const Result&) = default;
+  Result& operator=(Result&&) = default;
 
   /** True if this Result is a success result. */
   bool isOk() const { return mImpl.isOk(); }
 
   /** True if this Result is an error result. */
   bool isErr() const { return !mImpl.isOk(); }
 
-  /** Get the success value from this Result, which must be a success result. */
-  V unwrap() const {
+  /** Take the success value from this Result, which must be a success result.
+   */
+  V unwrap() {
     MOZ_ASSERT(isOk());
     return mImpl.unwrap();
   }
 
   /**
-   *  Get the success value from this Result, which must be a success result.
-   *  If it is an error result, then return the aValue.
+   * Take the success value from this Result, which must be a success result.
+   * If it is an error result, then return the aValue.
    */
-  V unwrapOr(V aValue) const { return isOk() ? mImpl.unwrap() : aValue; }
+  V unwrapOr(V aValue) { return isOk() ? mImpl.unwrap() : std::move(aValue); }
 
-  /** Get the error value from this Result, which must be an error result. */
-  E unwrapErr() const {
+  /** Take the error value from this Result, which must be an error result. */
+  E unwrapErr() {
     MOZ_ASSERT(isErr());
     return mImpl.unwrapErr();
   }
 
+  /** See the success value from this Result, which must be a success result. */
+  const V& inspect() const { return mImpl.inspect(); }
+
+  /** See the error value from this Result, which must be an error result. */
+  const E& inspectErr() const {
+    MOZ_ASSERT(isErr());
+    return mImpl.inspectErr();
+  }
+
   /**
    * Map a function V -> W over this result's success variant. If this result is
    * an error, do not invoke the function and return a copy of the error.
    *
    * Mapping over success values invokes the function to produce a new success
    * value:
    *
    *     // Map Result<int, E> to another Result<int, E>
@@ -367,17 +411,17 @@ class MOZ_MUST_USE_TYPE Result final {
    *
    *     Result<V, int> res(5);
    *     MOZ_ASSERT(res.isErr());
    *     Result<W, int> res2 = res.map([](V v) { ... });
    *     MOZ_ASSERT(res2.isErr());
    *     MOZ_ASSERT(res2.unwrapErr() == 5);
    */
   template <typename F>
-  auto map(F f) const -> Result<decltype(f(*((V*)nullptr))), E> {
+  auto map(F f) -> Result<decltype(f(*((V*)nullptr))), E> {
     using RetResult = Result<decltype(f(*((V*)nullptr))), E>;
     return isOk() ? RetResult(f(unwrap())) : RetResult(unwrapErr());
   }
 
   /**
    * Given a function V -> Result<W, E>, apply it to this result's success value
    * and return its result. If this result is an error value, then return a
    * copy.
@@ -402,17 +446,17 @@ class MOZ_MUST_USE_TYPE Result final {
    *
    *     Result<int, const char*> res("some error");
    *     auto res2 = res.andThen([](int x) { ... });
    *     MOZ_ASSERT(res2.isErr());
    *     MOZ_ASSERT(res.unwrapErr() == res2.unwrapErr());
    */
   template <typename F, typename = typename EnableIf<detail::IsResult<decltype(
                             (*((F*)nullptr))(*((V*)nullptr)))>::value>::Type>
-  auto andThen(F f) const -> decltype(f(*((V*)nullptr))) {
+  auto andThen(F f) -> decltype(f(*((V*)nullptr))) {
     return isOk() ? f(unwrap()) : GenericErrorResult<E>(unwrapErr());
   }
 };
 
 /**
  * A type that auto-converts to an error Result. This is like a Result without
  * a success type. It's the best return type for functions that always return
  * an error--functions designed to build and populate error objects. It's also
--- a/mfbt/tests/TestResult.cpp
+++ b/mfbt/tests/TestResult.cpp
@@ -1,21 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <string.h>
 #include "mozilla/Result.h"
+#include "mozilla/UniquePtr.h"
 
 using mozilla::Err;
 using mozilla::GenericErrorResult;
 using mozilla::Ok;
 using mozilla::Result;
+using mozilla::UniquePtr;
 
 struct Failed {
   int x;
 };
 
 static_assert(sizeof(Result<Ok, Failed&>) == sizeof(uintptr_t),
               "Result with empty value type should be pointer-sized");
 static_assert(sizeof(Result<int*, Failed&>) == sizeof(uintptr_t),
@@ -219,19 +221,52 @@ static void AndThenTest() {
   Result<int, const char*> r4 = r3.andThen([](int x) {
     MOZ_RELEASE_ASSERT(false);
     return Result<int, const char*>(1);
   });
   MOZ_RELEASE_ASSERT(r4.isErr());
   MOZ_RELEASE_ASSERT(r3.unwrapErr() == r4.unwrapErr());
 }
 
+using UniqueResult = Result<UniquePtr<int>, const char*>;
+
+static UniqueResult UniqueTask() { return mozilla::MakeUnique<int>(3); }
+static UniqueResult UniqueTaskError() { return Err("bad"); }
+
+static void UniquePtrTest() {
+  {
+    auto result = UniqueTask();
+    MOZ_RELEASE_ASSERT(result.isOk());
+    auto ptr = result.unwrap();
+    MOZ_RELEASE_ASSERT(ptr);
+    MOZ_RELEASE_ASSERT(*ptr == 3);
+    auto moved = result.unwrap();
+    MOZ_RELEASE_ASSERT(!moved);
+  }
+
+  {
+    auto err = UniqueTaskError();
+    MOZ_RELEASE_ASSERT(err.isErr());
+    auto ptr = err.unwrapOr(mozilla::MakeUnique<int>(4));
+    MOZ_RELEASE_ASSERT(ptr);
+    MOZ_RELEASE_ASSERT(*ptr == 4);
+  }
+
+  {
+    auto result = UniqueTaskError();
+    result = UniqueResult(mozilla::MakeUnique<int>(6));
+    MOZ_RELEASE_ASSERT(result.isOk());
+    MOZ_RELEASE_ASSERT(result.inspect() && *result.inspect() == 6);
+  }
+}
+
 /* * */
 
 int main() {
   BasicTests();
   TypeConversionTests();
   EmptyValueTest();
   ReferenceTest();
   MapTest();
   AndThenTest();
+  UniquePtrTest();
   return 0;
 }
--- a/mozglue/misc/interceptor/PatcherDetour.h
+++ b/mozglue/misc/interceptor/PatcherDetour.h
@@ -1325,18 +1325,18 @@ class WindowsDllDetourPatcher final : pu
         if (!origBytes.BackUpOneInstruction()) {
           return;
         }
 
         break;
       }
 
       // We need to load an absolute address into a particular register
-      tramp.WriteLoadLiteral(pcRelInfo.unwrap().mAbsAddress,
-                             pcRelInfo.unwrap().mDestReg);
+      tramp.WriteLoadLiteral(pcRelInfo.inspect().mAbsAddress,
+                             pcRelInfo.inspect().mDestReg);
     }
 
 #else
 #  error "Unknown processor type"
 #endif
 
     if (origBytes.GetOffset() > 100) {
       // printf ("Too big!");
--- a/toolkit/mozapps/extensions/AddonManagerStartup.cpp
+++ b/toolkit/mozapps/extensions/AddonManagerStartup.cpp
@@ -155,17 +155,17 @@ static Result<nsCString, nsresult> Decod
   if (!result.SetLength(size, fallible) ||
       !LZ4::decompress(data, dataLen, result.BeginWriting(), size,
                        &outputSize)) {
     return Err(NS_ERROR_UNEXPECTED);
   }
 
   MOZ_DIAGNOSTIC_ASSERT(size == outputSize);
 
-  return result;
+  return std::move(result);
 }
 
 // Our zlib headers redefine this to MOZ_Z_compress, which breaks LZ4::compress
 #undef compress
 
 template <typename T>
 static Result<nsCString, nsresult> EncodeLZ4(const nsACString& data,
                                              const T& magicNumber) {
@@ -189,17 +189,17 @@ static Result<nsCString, nsresult> Encod
   }
 
   size = LZ4::compress(data.BeginReading(), data.Length(),
                        result.BeginWriting() + off);
 
   if (!result.SetLength(off + size, fallible)) {
     return Err(NS_ERROR_OUT_OF_MEMORY);
   }
-  return result;
+  return std::move(result);
 }
 
 static_assert(sizeof STRUCTURED_CLONE_MAGIC % 8 == 0,
               "Magic number should be an array of uint64_t");
 
 /**
  * Reads the contents of a LZ4-compressed file, as stored by the OS.File
  * module, and returns the decompressed contents on success.
@@ -493,17 +493,17 @@ nsresult AddonManagerStartup::ReadStartu
 
   nsCOMPtr<nsIFile> file =
       CloneAndAppend(ProfileDir(), "addonStartup.json.lz4");
 
   nsCString data;
   auto res = ReadFileLZ4(file);
   if (res.isOk()) {
     data = res.unwrap();
-  } else if (res.unwrapErr() != NS_ERROR_FILE_NOT_FOUND) {
+  } else if (res.inspectErr() != NS_ERROR_FILE_NOT_FOUND) {
     return res.unwrapErr();
   }
 
   if (data.IsEmpty() || !ParseJSON(cx, data, locations)) {
     return NS_OK;
   }
 
   if (!locations.isObject()) {
--- a/toolkit/xre/LauncherRegistryInfo.cpp
+++ b/toolkit/xre/LauncherRegistryInfo.cpp
@@ -58,17 +58,17 @@ LauncherResult<LauncherRegistryInfo::Dis
 LauncherVoidResult LauncherRegistryInfo::ReflectPrefToRegistry(
     const bool aEnable) {
   LauncherResult<EnabledState> curEnabledState = IsEnabled();
   if (curEnabledState.isErr()) {
     return LAUNCHER_ERROR_FROM_RESULT(curEnabledState);
   }
 
   bool isCurrentlyEnabled =
-      curEnabledState.unwrap() != EnabledState::ForceDisabled;
+      curEnabledState.inspect() != EnabledState::ForceDisabled;
   if (isCurrentlyEnabled == aEnable) {
     // Don't reflect to the registry unless the new enabled state is actually
     // changing with respect to the current enabled state.
     return Ok();
   }
 
   // Always delete the launcher timestamp
   LauncherResult<bool> clearedLauncherTimestamp =
@@ -132,32 +132,32 @@ LauncherResult<LauncherRegistryInfo::Pro
       savedImageTimestamp.unwrapErr() !=
           WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
     return LAUNCHER_ERROR_FROM_RESULT(savedImageTimestamp);
   }
 
   // If we don't have a saved timestamp, or we do but it doesn't match with
   // our current timestamp, clear previous values.
   if (savedImageTimestamp.isErr() ||
-      savedImageTimestamp.unwrap() != ourImageTimestamp.unwrap()) {
+      savedImageTimestamp.inspect() != ourImageTimestamp.inspect()) {
     LauncherVoidResult clearResult = ClearStartTimestamps();
     if (clearResult.isErr()) {
       return LAUNCHER_ERROR_FROM_RESULT(clearResult);
     }
 
     LauncherVoidResult writeResult =
-        WriteImageTimestamp(ourImageTimestamp.unwrap());
+        WriteImageTimestamp(ourImageTimestamp.inspect());
     if (writeResult.isErr()) {
       return LAUNCHER_ERROR_FROM_RESULT(writeResult);
     }
   }
 
   ProcessType typeToRunAs = aDesiredType;
 
-  if (disposition.unwrap() == Disposition::CreatedNew ||
+  if (disposition.inspect() == Disposition::CreatedNew ||
       aDesiredType == ProcessType::Browser) {
     // No existing values to check, or we're going to be running as the browser
     // process: just write our timestamp and return
     LauncherVoidResult wroteTimestamp = WriteStartTimestamp(typeToRunAs);
     if (wroteTimestamp.isErr()) {
       return LAUNCHER_ERROR_FROM_RESULT(wroteTimestamp);
     }
 
@@ -177,17 +177,17 @@ LauncherResult<LauncherRegistryInfo::Pro
     // that's bad because it is indicating that the browser can't run with
     // the launcher process.
     typeToRunAs = ProcessType::Browser;
   } else if (haveLauncherTs) {
     // if we have both timestamps, we want to ensure that the launcher timestamp
     // is earlier than the browser timestamp.
     if (aDesiredType == ProcessType::Launcher) {
       bool areTimestampsOk =
-          lastLauncherTimestamp.unwrap() < lastBrowserTimestamp.unwrap();
+          lastLauncherTimestamp.inspect() < lastBrowserTimestamp.inspect();
       if (!areTimestampsOk) {
         typeToRunAs = ProcessType::Browser;
       }
     }
   } else {
     // If we have neither timestamp, then we should try running as suggested
     // by |aDesiredType|.
     // We shouldn't really have this scenario unless we're going to be running
@@ -240,24 +240,24 @@ LauncherRegistryInfo::IsEnabled() {
 
   LauncherResult<uint64_t> browserTimestamp =
       GetStartTimestamp(ProcessType::Browser);
 
   // In this function, we'll explictly search for the ForceDisabled and
   // Enabled conditions. Everything else is FailDisabled.
 
   bool isBrowserTimestampZero =
-      browserTimestamp.isOk() && browserTimestamp.unwrap() == 0ULL;
+      browserTimestamp.isOk() && browserTimestamp.inspect() == 0ULL;
 
   if (isBrowserTimestampZero && launcherTimestamp.isErr()) {
     return EnabledState::ForceDisabled;
   }
 
   if (launcherTimestamp.isOk() && browserTimestamp.isOk() &&
-      launcherTimestamp.unwrap() < browserTimestamp.unwrap()) {
+      launcherTimestamp.inspect() < browserTimestamp.inspect()) {
     return EnabledState::Enabled;
   }
 
   if (launcherTimestamp.isErr() && browserTimestamp.isErr()) {
     return EnabledState::Enabled;
   }
 
   return EnabledState::FailDisabled;
@@ -331,17 +331,17 @@ LauncherVoidResult LauncherRegistryInfo:
     if (!::QueryPerformanceCounter(
             reinterpret_cast<LARGE_INTEGER*>(&timestamp))) {
       return LAUNCHER_ERROR_FROM_LAST();
     }
   }
 
   DWORD len = sizeof(timestamp);
   LSTATUS result =
-      ::RegSetValueExW(mRegKey.get(), name.unwrap().c_str(), 0, REG_QWORD,
+      ::RegSetValueExW(mRegKey.get(), name.inspect().c_str(), 0, REG_QWORD,
                        reinterpret_cast<PBYTE>(&timestamp), len);
   if (result != ERROR_SUCCESS) {
     return LAUNCHER_ERROR_FROM_WIN32(result);
   }
 
   return Ok();
 }
 
@@ -376,31 +376,31 @@ LauncherVoidResult LauncherRegistryInfo:
 LauncherResult<bool> LauncherRegistryInfo::ClearStartTimestamp(
     LauncherRegistryInfo::ProcessType aProcessType) {
   LauncherResult<std::wstring> timestampName = ResolveValueName(aProcessType);
   if (timestampName.isErr()) {
     return LAUNCHER_ERROR_FROM_RESULT(timestampName);
   }
 
   LSTATUS result =
-      ::RegDeleteValueW(mRegKey.get(), timestampName.unwrap().c_str());
+      ::RegDeleteValueW(mRegKey.get(), timestampName.inspect().c_str());
   if (result == ERROR_SUCCESS) {
     return true;
   }
 
   if (result == ERROR_FILE_NOT_FOUND) {
     return false;
   }
 
   return LAUNCHER_ERROR_FROM_WIN32(result);
 }
 
 LauncherVoidResult LauncherRegistryInfo::ClearStartTimestamps() {
   LauncherResult<EnabledState> enabled = IsEnabled();
-  if (enabled.isOk() && enabled.unwrap() == EnabledState::ForceDisabled) {
+  if (enabled.isOk() && enabled.inspect() == EnabledState::ForceDisabled) {
     // We don't clear anything when we're force disabled - we need to maintain
     // the current registry state in this case.
     return Ok();
   }
 
   LauncherResult<bool> clearedLauncherTimestamp =
       ClearStartTimestamp(ProcessType::Launcher);
   if (clearedLauncherTimestamp.isErr()) {
@@ -468,17 +468,17 @@ LauncherResult<uint64_t> LauncherRegistr
   if (name.isErr()) {
     return LAUNCHER_ERROR_FROM_RESULT(name);
   }
 
   uint64_t value;
   DWORD valueLen = sizeof(value);
   DWORD type;
   LSTATUS result =
-      ::RegQueryValueExW(mRegKey.get(), name.unwrap().c_str(), nullptr, &type,
+      ::RegQueryValueExW(mRegKey.get(), name.inspect().c_str(), nullptr, &type,
                          reinterpret_cast<PBYTE>(&value), &valueLen);
   // NB: If the value does not exist, result == ERROR_FILE_NOT_FOUND
   if (result != ERROR_SUCCESS) {
     return LAUNCHER_ERROR_FROM_WIN32(result);
   }
 
   if (type != REG_QWORD) {
     return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -752,17 +752,16 @@ static void CutExtension(nsCString& aPat
   }
 }
 
 static void DoRegisterManifest(NSLocationType aType, FileLocation& aFile,
                                bool aChromeOnly) {
   auto result = URLPreloader::Read(aFile);
   if (result.isOk()) {
     nsCString buf(result.unwrap());
-
     ParseManifest(aType, aFile, buf.BeginWriting(), aChromeOnly);
   } else if (NS_BOOTSTRAPPED_LOCATION != aType) {
     nsCString uri;
     aFile.GetURIString(uri);
     LogMessage("Could not read chrome manifest '%s'.", uri.get());
   }
 }