diff --git a/ctl/unique_ptr.cc b/ctl/unique_ptr.cc
new file mode 100644
index 000000000..daeda3b1b
--- /dev/null
+++ b/ctl/unique_ptr.cc
@@ -0,0 +1,19 @@
+// -*- mode:c++; indent-tabs-mode:nil; c-basic-offset:4; coding:utf-8 -*-
+// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
+//
+// Copyright 2024 Justine Alexandra Roberts Tunney
+//
+// Permission to use, copy, modify, and/or distribute this software for
+// any purpose with or without fee is hereby granted, provided that the
+// above copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "unique_ptr.h"
diff --git a/ctl/unique_ptr.h b/ctl/unique_ptr.h
new file mode 100644
index 000000000..179beda12
--- /dev/null
+++ b/ctl/unique_ptr.h
@@ -0,0 +1,156 @@
+// -*-mode:c++;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8-*-
+// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
+#ifndef COSMOPOLITAN_CTL_UNIQUE_PTR_H_
+#define COSMOPOLITAN_CTL_UNIQUE_PTR_H_
+#include <__utility/forward.h>
+#include <__utility/move.h>
+#include <__utility/swap.h>
+
+namespace ctl {
+
+template<typename T>
+struct default_delete
+{
+    constexpr void operator()(T* p) const noexcept
+    {
+        delete p;
+    }
+};
+
+template<typename T, typename D = default_delete<T>>
+struct unique_ptr
+{
+    using pointer = T*;
+    using element_type = T;
+    using deleter_type = D;
+
+    pointer p;
+    [[no_unique_address]] deleter_type d;
+
+    constexpr unique_ptr(nullptr_t = nullptr) noexcept : p(nullptr)
+    {
+    }
+
+    constexpr unique_ptr(pointer p) noexcept : p(p)
+    {
+    }
+
+    constexpr unique_ptr(pointer p, auto&& d) noexcept
+      : p(p), d(std::forward<decltype(d)>(d))
+    {
+    }
+
+    constexpr unique_ptr(unique_ptr&& u) noexcept : p(u.p), d(std::move(u.d))
+    {
+        u.p = nullptr;
+    }
+
+    // TODO(mrdomino):
+    // template <typename U, typename E>
+    // unique_ptr(unique_ptr<U, E>&& u) noexcept;
+
+    unique_ptr(const unique_ptr&) = delete;
+
+    inline ~unique_ptr() /* noexcept */
+    {
+        reset();
+    }
+
+    inline unique_ptr& operator=(unique_ptr r) noexcept
+    {
+        swap(r);
+        return *this;
+    }
+
+    inline pointer release() noexcept
+    {
+        pointer r = p;
+        p = nullptr;
+        return r;
+    }
+
+    inline void reset(nullptr_t = nullptr) noexcept
+    {
+        if (p)
+            d(p);
+        p = nullptr;
+    }
+
+    template<typename U>
+    // TODO(mrdomino):
+    /* requires is_convertible_v<U, T> */
+    inline void reset(U* p2)
+    {
+        if (p) {
+            d(p);
+        }
+        p = static_cast<pointer>(p2);
+    }
+
+    inline void swap(unique_ptr& r) noexcept
+    {
+        using std::swap;
+        swap(p, r.p);
+        swap(d, r.d);
+    }
+
+    inline pointer get() const noexcept
+    {
+        return p;
+    }
+
+    inline deleter_type& get_deleter() noexcept
+    {
+        return d;
+    }
+
+    inline const deleter_type& get_deleter() const noexcept
+    {
+        return d;
+    }
+
+    inline explicit operator bool() const noexcept
+    {
+        return p;
+    }
+
+    inline element_type& operator*() const
+      noexcept(noexcept(*std::declval<pointer>()))
+    {
+        if (!p)
+            __builtin_trap();
+        return *p;
+    }
+
+    inline pointer operator->() const noexcept
+    {
+        if (!p)
+            __builtin_trap();
+        return p;
+    }
+};
+
+template<typename T, typename... Args>
+inline unique_ptr<T>
+make_unique(Args&&... args)
+{
+    return unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+template<typename T>
+inline unique_ptr<T>
+make_unique_for_overwrite()
+{
+#if 0
+    // You'd think that it'd work like this, but std::unique_ptr does not.
+    return unique_ptr<T>(
+      static_cast<T*>(::operator new(sizeof(T), align_val_t(alignof(T)))));
+#else
+    return unique_ptr<T>(new T);
+#endif
+}
+
+// TODO(mrdomino): specializations for T[]
+
+} // namespace ctl
+#endif // COSMOPOLITAN_CTL_UNIQUE_PTR_H_
diff --git a/test/ctl/unique_ptr_test.cc b/test/ctl/unique_ptr_test.cc
new file mode 100644
index 000000000..4d6699b25
--- /dev/null
+++ b/test/ctl/unique_ptr_test.cc
@@ -0,0 +1,217 @@
+// -*- mode:c++; indent-tabs-mode:nil; c-basic-offset:4; coding:utf-8 -*-
+// vi: set et ft=cpp ts=4 sts=4 sw=4 fenc=utf-8 :vi
+//
+// Copyright 2024 Justine Alexandra Roberts Tunney
+//
+// Permission to use, copy, modify, and/or distribute this software for
+// any purpose with or without fee is hereby granted, provided that the
+// above copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "ctl/unique_ptr.h"
+
+#include <type_traits>
+
+#include "libc/runtime/runtime.h"
+
+// #include <memory>
+// #define ctl std
+
+template<typename T, typename D = ctl::default_delete<T>>
+using Ptr = ctl::unique_ptr<T, D>;
+
+template<typename T, typename... Args>
+Ptr<T>
+Mk(Args&&... args)
+{
+    return ctl::make_unique<T, Args...>(std::forward<Args>(args)...);
+}
+
+template<typename T>
+Ptr<T>
+MkRaw()
+{
+    return ctl::make_unique_for_overwrite<T>();
+}
+
+#undef ctl
+
+static int g = 0;
+
+struct SetsGDeleter
+{
+    void operator()(auto*) const noexcept
+    {
+        ++g;
+    }
+};
+
+struct StatefulDeleter
+{
+    char state;
+    void operator()(auto*) const noexcept
+    {
+    }
+};
+
+struct FinalDeleter final
+{
+    void operator()(auto*) const noexcept
+    {
+    }
+};
+
+static_assert(sizeof(Ptr<int, SetsGDeleter>) == sizeof(int*));
+
+// not everyone uses [[no_unique_address]]...
+static_assert(!std::is_same_v<Ptr<int>, ctl::unique_ptr<int>> ||
+              sizeof(Ptr<int, FinalDeleter>) == sizeof(int*));
+
+struct SetsGCtor
+{
+    SetsGCtor()
+    {
+        ++g;
+    }
+};
+
+struct SetsGDtor
+{
+    ~SetsGDtor()
+    {
+        ++g;
+    }
+};
+
+int
+main()
+{
+    {
+        Ptr<int> x(new int(5));
+    }
+
+    {
+        Ptr<int, SetsGDeleter> x(new int());
+        x.reset();
+        if (g != 1)
+            return 1;
+    }
+
+    {
+        g = 0;
+        Ptr<int, SetsGDeleter> x(new int());
+        delete x.release();
+        x.reset();
+        if (g)
+            return 17;
+    }
+
+    {
+        Ptr<int> x(new int(5)), y(new int(6));
+        x.swap(y);
+        if (*x != 6 || *y != 5)
+            return 2;
+    }
+
+    {
+        Ptr<int> x;
+        if (x)
+            return 3;
+        x.reset(new int(5));
+        if (!x)
+            return 4;
+    }
+
+    {
+        g = 0;
+        Ptr<SetsGCtor> x;
+        if (g)
+            return 5;
+        x = Mk<SetsGCtor>();
+        if (g != 1)
+            return 6;
+    }
+
+    {
+        g = 0;
+        auto x = Mk<SetsGDtor>();
+        if (g)
+            return 7;
+        x.reset();
+        if (g != 1)
+            return 8;
+        if (x)
+            return 9;
+    }
+
+    {
+        g = 0;
+        Ptr<SetsGDtor> x, y;
+        x = Mk<SetsGDtor>();
+        y = Mk<SetsGDtor>();
+#if 0
+        // shouldn't compile
+        x = y;
+#endif
+        x = std::move(y);
+        if (g != 1)
+            return 10;
+        if (y)
+            return 11;
+    }
+
+    {
+        g = 0;
+        {
+            auto x = Mk<SetsGDtor>();
+        }
+        if (g != 1)
+            return 12;
+    }
+
+    {
+        g = 0;
+        {
+            auto x = Mk<SetsGDtor>();
+            x.release();
+        }
+        if (g)
+            return 13;
+    }
+
+#if 0
+    // I could not figure out how to test make_unique_for_overwrite. The only
+    // side effects it has are illegal to detect?
+    {
+        g = 0;
+        auto x = MkRaw<DefaultInitialized>();
+        if (g)
+            return 14;
+        x.reset();
+        if (g)
+            return 15;
+        x = Mk<DefaultInitialized>();
+        if (g != 1)
+            return 16;
+    }
+#endif
+
+    {
+        int a;
+        // Should compile.
+        Ptr<int, FinalDeleter> x(&a);
+        Ptr<int, StatefulDeleter> y(&a);
+    }
+
+    // next is 18
+
+    return 0;
+}