Last active
June 26, 2023 04:15
-
-
Save tyqualters/282d335b27cdd2b5758e2b066ee4a589 to your computer and use it in GitHub Desktop.
Rust-based runtime ownership and borrowing implemented into C++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * This is a C++ example of implementing Rust Lifetimes for memory safety. | |
| * Note: This is more relevant to Runtime assurance, not so similar to Rust's compile time assurance. | |
| */ | |
| #include <iostream> | |
| #include "lifetime.hpp" | |
| auto add(Lifetime<int> b) /* Lifetime<int>& would be the same instance */ { | |
| b.set(b.get() + 5); | |
| std::cout << "Value of 'b' is: " << b.get() << std::endl; | |
| } | |
| auto main() -> int { | |
| auto a = Lifetime<int>::from(15); // Create a new Lifetime (int) with value: 15 | |
| // add(a); cannot be duplicated (will subsequently error - CT - deleted functions) | |
| add(a.clone()); // Clone the Lifetime | |
| // add(a.borrow_mutable()); // Borrow as a mutable reference | |
| // add(a.move()); // Move ownership (will subsequently error - RT - owner deleted) | |
| if(a.is_owner()) // verify ownership | |
| a.set(15); | |
| std::cout << "Value of 'a' is: " << a.get() << std::endl; | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * @file lifetime.hpp | |
| * @author Ty Qualters (contact@tyqualters.com) | |
| * @brief Rust ownership & borrowing implemented as Runtime checks for C++ | |
| * @version 0.1 | |
| * @date 2022-12-17 | |
| * | |
| * @copyright Copyright (c) 2022 | |
| * | |
| */ | |
| #pragma once | |
| #ifndef CPP_LIFETIME_H_ | |
| #define CPP_LIFETIME_H_ | |
| #include <sstream> | |
| #include <stdexcept> | |
| // #include <source_location> (since C++20) | |
| #include <set> | |
| #include <utility> | |
| #include <mutex> | |
| #include <cassert> | |
| // TODO: ... Waiting on further compiler support for C++20 & C++23 | |
| // auto inline get_source_position() -> std::string | |
| // { | |
| // const std::source_location location = std::source_location::current(); | |
| // std::stringstream ss; | |
| // ss << "File: \t" << location.filename() << "\n"; | |
| // ss << "Line/Col: \t" << location.line() << ", " << location.column() << "\n"; | |
| // ss << "Func: \t" << location.function_name() << "\n"; | |
| // return ss.str(); | |
| // } | |
| // Similar to a std::shared_ptr<T> | |
| template<class T> | |
| class Lifetime { | |
| public: | |
| class LifetimeMutator; | |
| // Constructor (a new Lifetime) | |
| Lifetime(T* child, Lifetime** ownership, LifetimeMutator** mutator, std::mutex* mut, std::set<Lifetime*>* set, bool force_take_ownership = false, bool force_take_mutability = false) noexcept | |
| { | |
| this->m_T = child; | |
| this->m_mutator = (mutator == nullptr) ? new LifetimeMutator*{nullptr} : mutator; | |
| if(force_take_mutability && *this->m_mutator == nullptr) | |
| *this->m_mutator = new LifetimeMutator(this, new LifetimeMutator*{*this->m_mutator}); | |
| this->m_owner = (ownership == nullptr ? new Lifetime*{this} : ownership); | |
| if(force_take_ownership && *this->m_owner != this) *this->m_owner = this; | |
| this->m_mutex = (mut == nullptr ? new std::mutex : mut); | |
| this->m_refs = (set == nullptr ? new std::set<Lifetime*> : set); | |
| this->m_refs->insert(this); | |
| } | |
| // Destructor (disable noexcept) | |
| ~Lifetime() noexcept(false) | |
| { | |
| assert(this->m_T != nullptr); | |
| assert(this->m_owner != nullptr); | |
| assert(this->m_refs != nullptr); | |
| assert(this->m_mutator != nullptr); | |
| // Remove mutability | |
| if(*this->m_mutator != nullptr && (*this->m_mutator)->m_mutator == this) delete *this->m_mutator; | |
| // Remove | |
| for(const auto& ptr : *this->m_refs) | |
| { | |
| if(ptr == this) | |
| { | |
| this->m_refs->erase(ptr); | |
| break; | |
| } | |
| } | |
| if(this == *this->m_owner) | |
| { | |
| if(this->m_refs->size() > 0U) | |
| throw std::runtime_error("Owner freed but references still exist."); | |
| this->m_refs->clear(); | |
| } | |
| // Delete | |
| if(this->m_refs->empty()) | |
| { | |
| delete this->m_T; | |
| delete this->m_owner; | |
| delete this->m_refs; | |
| std::cout << "Lifetime deleted." << std::endl; | |
| } | |
| // Reset | |
| this->m_T = nullptr; | |
| this->m_owner = nullptr; | |
| this->m_refs = nullptr; | |
| } | |
| // Disable copying | |
| Lifetime(Lifetime const&) = delete; | |
| void operator=(Lifetime const &x) = delete; | |
| // Create a new Lifetime | |
| auto static from(T&& value) noexcept -> Lifetime<T> | |
| { | |
| return Lifetime<T>(new T{value}, nullptr, nullptr, nullptr, nullptr); | |
| } | |
| // Get mutable | |
| auto get_mutable() -> T& | |
| { | |
| assert(this->m_T != nullptr); | |
| assert(this->m_owner != nullptr); | |
| assert(this->m_mutator != nullptr); | |
| assert(this->m_mutex != nullptr); | |
| if(*this->m_mutator != nullptr && (*this->m_mutator)->m_mutator == this); | |
| else if(this != *this->m_owner) throw std::runtime_error("Lifetime tried to get a mutable reference without maintaining object ownership or mutability."); | |
| std::scoped_lock<std::mutex> lock(*this->m_mutex); | |
| return *this->m_T; | |
| } | |
| // Set new value | |
| auto set(T&& value) | |
| { | |
| assert(this->m_T != nullptr); | |
| assert(this->m_owner != nullptr); | |
| assert(this->m_mutator != nullptr); | |
| assert(this->m_mutex != nullptr); | |
| if(*this->m_mutator != nullptr && (*this->m_mutator)->m_mutator == this); | |
| else if(this != *this->m_owner) throw std::runtime_error("Lifetime tried to write a new value without maintaining object ownership or mutability."); | |
| std::scoped_lock<std::mutex> lock(*this->m_mutex); | |
| *this->m_T = value; | |
| } | |
| // Get the value | |
| auto get() noexcept -> const T& | |
| { | |
| return *this->m_T; | |
| } | |
| // Borrow | |
| auto borrow() noexcept -> Lifetime<T> | |
| { | |
| return Lifetime<T>(this->m_T, this->m_owner, this->m_mutator, this->m_mutex, this->m_refs); | |
| } | |
| // Borrow mutable | |
| auto borrow_mutable() -> Lifetime<T> | |
| { | |
| assert(this->m_mutator != nullptr); | |
| if(*this->m_mutator != nullptr) throw std::runtime_error("Tried to borrow mutable access from a Lifetime for which mutable access already exists."); | |
| return Lifetime<T>(this->m_T, this->m_owner, this->m_mutator, this->m_mutex, this->m_refs, false, true); | |
| } | |
| // Clone | |
| auto clone() noexcept -> Lifetime<T> | |
| { | |
| T _T = *this->m_T; | |
| return Lifetime<T>::from(std::move(_T)); | |
| } | |
| // Get mutability | |
| auto is_mutator() noexcept -> bool | |
| { | |
| return this->m_mutator != nullptr && (*this->m_mutator)->m_mutator == this; | |
| } | |
| // Get ownership | |
| auto is_owner() noexcept -> bool | |
| { | |
| return this == *this->m_owner; | |
| } | |
| // Move to | |
| auto move(Lifetime& lifetime) -> void | |
| { | |
| assert(this->m_owner != nullptr); | |
| assert(this->m_refs != nullptr); | |
| assert(this->m_mutator != nullptr); | |
| if(this != this->m_owner) throw std::runtime_error("Lifetime tried to transfer ownership without maintaining object ownership."); | |
| if(this == &lifetime) throw std::runtime_error("Lifetime tried to transfer ownership to the same instance."); | |
| if(this->m_refs.contains(&lifetime)) | |
| { | |
| // Remove mutability | |
| if(*this->m_mutator != nullptr && *this->m_mutator->m_mutator == this) delete *this->m_mutator; | |
| // Transfer ownership | |
| this->m_owner = false; | |
| lifetime.m_owner = true; | |
| } else throw std::runtime_error("Lifetime tried to transfer ownership to a different Lifetime."); | |
| } | |
| // Move | |
| auto move() -> Lifetime<T> | |
| { | |
| assert(this->m_T != nullptr); | |
| assert(this->m_owner != nullptr); | |
| assert(this->m_refs != nullptr); | |
| assert(this->m_mutator != nullptr); | |
| if(this != *this->m_owner) throw std::runtime_error("Lifetime tried to transfer ownership without maintaining object ownership."); | |
| return Lifetime<T>(this->m_T, this->m_owner, this->m_mutator, this->m_mutex, this->m_refs, true, false); | |
| } | |
| class LifetimeMutator { | |
| public: | |
| friend class Lifetime; | |
| LifetimeMutator(Lifetime* mutator, LifetimeMutator** mutator_access) | |
| { | |
| this->m_mutator = mutator; | |
| this->m_mutatorAccess = mutator_access; | |
| *this->m_mutatorAccess = this; | |
| } | |
| ~LifetimeMutator() | |
| { | |
| this->m_mutator = nullptr; | |
| *this->m_mutatorAccess = nullptr; | |
| } | |
| protected: | |
| Lifetime* m_mutator = nullptr; | |
| LifetimeMutator** m_mutatorAccess = nullptr; | |
| }; | |
| protected: | |
| mutable T* m_T = nullptr; | |
| mutable Lifetime** m_owner = nullptr; | |
| mutable std::mutex* m_mutex; | |
| mutable LifetimeMutator** m_mutator; | |
| mutable std::set<Lifetime*>* m_refs; | |
| }; | |
| #endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment