Article by Ayman Alheraki on January 11 2026 10:38 AM
Rust is known for its unique safety model based on Ownership, Borrowing, and Mutable Borrowing, combined with strong guarantees against memory errors and data races in safe code.
C++, on the other hand, although powerful and flexible, does not provide these guarantees natively. However, because C++ is highly expressive and allows deep control over program design, we can build safety models through careful API design, without changing the language itself.
In this article, we present a practical, lightweight model for creating a mini library that brings a Rust-like safety style to C++. This model is small, understandable, and can be used immediately in modern C++ codebases.
We call the library:
safecpp
It is a simple header-only library that introduces clear rules for ownership, borrowing, and safe concurrency.
The goal is not to recreate Rust’s compiler-level guarantees—because Rust’s Borrow Checker is deeply integrated into the language.
Instead, the aim is to achieve:
More predictable memory management
Clear separation between ownership and borrowing
Safer mutation patterns
Thread-safe state without data races
Cleaner APIs that reveal intent
This approach dramatically reduces errors while keeping full compatibility with Modern C++ (C++17/20/23/26).
safecpp contains three essential parts:
owner<T> — Explicit and Unique OwnershipA thin wrapper over std::unique_ptr<T> with a clearer semantic name.
borrow<T> and borrow_mut<T> — Borrowing ModelsThey mimic Rust’s &T and &mut T at the API level.
ts_value<T> — Safe Concurrent ValueA synchronized wrapper that exposes data only through controlled read and write operations.
safecpp/memory.hppDefines ownership and borrowing:
namespace safecpp {
// Unique ownershiptemplate <typename T>class owner {public: owner() = default; explicit owner(T* ptr) noexcept : ptr_(ptr) {}
owner(owner&&) noexcept = default; owner& operator=(owner&&) noexcept = default;
owner(const owner&) = delete; owner& operator=(const owner&) = delete;
T* get() noexcept { return ptr_.get(); } const T* get() const noexcept { return ptr_.get(); }
T& operator*() noexcept { return *ptr_; } const T& operator*() const noexcept { return *ptr_; }
private: std::unique_ptr<T> ptr_;};
template <typename T, typename... Args>owner<T> make_owner(Args&&... args) { return owner<T>{new T(std::forward<Args>(args)...)};}
// Immutable borrowtemplate <typename T>class borrow {public: borrow(const T& ref) noexcept : ptr_(&ref) {} const T& get() const noexcept { return *ptr_; }
private: const T* ptr_;};
// Mutable borrowtemplate <typename T>class borrow_mut {public: borrow_mut(T& ref) noexcept : ptr_(&ref) {} T& get() const noexcept { return *ptr_; }
private: T* ptr_;};
} // namespace safecppsafecpp/concurrency.hppA simple thread-safe wrapper:
namespace safecpp {
template <typename T, typename Mutex = std::mutex>class ts_value {public: template <typename... Args> explicit ts_value(Args&&... args) : value_(std::forward<Args>(args)...) {}
template <typename F> auto read(F&& f) const { std::scoped_lock lock(m_); return std::invoke(std::forward<F>(f), value_); }
template <typename F> auto write(F&& f) { std::scoped_lock lock(m_); return std::invoke(std::forward<F>(f), value_); }
private: mutable Mutex m_; T value_;};
} // namespace safecppThis example combines ownership, borrowing, and thread-safe state:
using namespace safecpp;
struct Stats { int requests = 0; int errors = 0;};
void process_request(ts_value<Stats>& stats, borrow<const std::string> user) { stats.write([&](Stats& s) { ++s.requests; if (user.get() == "bad") { ++s.errors; } });}
int main() { auto user = make_owner<std::string>("Ayman"); ts_value<Stats> stats{};
std::vector<std::thread> workers;
for (int i = 0; i < 4; ++i) { workers.emplace_back([&] { for (int j = 0; j < 1000; ++j) { process_request(stats, borrow<const std::string>(*user)); } }); }
for (auto& t : workers) t.join();
stats.read([](const Stats& s) { std::cout << "requests = " << s.requests << ", errors = " << s.errors << "\n"; });}This mini library does not enforce Rust’s guarantees, but it achieves several strong goals:
Clear ownership semantics
owner<T> → sole resource owner
borrow<T> → read-only borrow
borrow_mut<T> → write-only borrow without ownership
Reduced pointer-related errors
Safe concurrent state
All shared data goes through ts_value<T> with strict access rules.
Excellent compatibility with Modern C++ Works naturally with:
C++20 Concepts
Ranges
Coroutines
Modules
Works with sanitizers ASan, MSan, TSan improve safety even further.
With this simple design, C++ developers can bring a Rust-like mindset to their projects:
safer memory management
intentional ownership and borrowing
protected shared state
fewer logic mistakes
clearer API contracts
All achieved without changing the C++ language, and without any compiler extensions.
This is an excellent foundation for building larger frameworks—or for including as part of a Modern C++ Encyclopedia or teaching material.