// Copyright (C) 2020-2022 Free Software Foundation, Inc. // This file is part of GCC. // GCC is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3, or (at your option) any later // version. // GCC is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // You should have received a copy of the GNU General Public License // along with GCC; see the file COPYING3. If not see // . #ifndef RUST_OPTIONAL_H #define RUST_OPTIONAL_H #include "config.h" #include "rust-system.h" #include "selftest.h" namespace Rust { /** * Tagged union to try and simulate a sum type. This is safer and more ergonomic * than one of the two alternatives we're currently using in the compiler: * * 1. Storing a raw pointer, which can be `nullptr` or valid * * This is wildly unsafe, and usable in conjunction with local references, stack * variables, or pointers managed elsewhere, which can cause crashes, hard to * debug issues or undefined behavior. Likewise, if you do not check for the * pointer's validity, this will cause a crash. * * 2. Storing an extra boolean alongside the object * * This causes implementors to use a "dummy object": Either an empty version or * an error version. But what happens if what you really wanted to store was * the empty or error version? You can also easily incorporate logic bugs if you * forget to check for the associated boolean. * * The `Optional` type has the same "ergonomic" cost: You need to check * whether your option is valid or not. However, the main advantage is that it * is more restrictive: You can only acess the member it contains "safely". * It is similar to storing a value + an associated boolean, but has the * advantage of making up only one member in your class. * You also benefit from some helper methods such as `map()`. * * You also get helper functions and operator overloading to "seamlessly" * replace raw pointer alternatives. * * ```c++ * MyType *raw_pointer = something_that_can_fail(); * if (raw_pointer) * raw_pointer->method(); * * // or * * Optional opt = something_that_can_fail2(); * if (opt) * opt->method(); * * // equivalent to * * if (opt.is_some()) * opt.get().method(); * ``` */ template class Optional { private: struct Empty { }; enum Kind { Some, None } kind; union Content { Empty empty; T value; Content () = default; } content; Optional (Kind kind, Content content) : kind (kind), content (content) {} public: Optional (const Optional &other) = default; Optional (Optional &&other) = default; static Optional some (T value) { Content content; content.value = value; return Optional (Kind::Some, content); } static Optional none () { Content content; content.empty = Empty (); return Optional (Kind::None, content); } bool is_some () const { return kind == Kind::Some; } bool is_none () const { return !is_some (); } /** * Enable boolean-like comparisons. */ operator bool () { return is_some (); } /** * Enables dereferencing to access the contained value */ T &operator* () { return get (); } const T &operator* () const { return get (); } T *operator-> () { return &get (); } const T *operator-> () const { return &get (); } const T &get () const { rust_assert (is_some ()); return content.value; } T &get () { rust_assert (is_some ()); return content.value; } T take () { rust_assert (is_some ()); auto to_return = std::move (content.value); content.empty = Empty (); kind = Kind::None; return to_return; } template Optional map (std::function functor) { if (is_none ()) return Optional::none (); auto value = functor (take ()); return Optional::some (value); } }; } // namespace Rust #ifdef CHECKING_P static void rust_optional_create () { auto opt = Rust::Optional::some (15); ASSERT_TRUE (opt.is_some ()); ASSERT_EQ (opt.get (), 15); Rust::Optional const_opt = Rust::Optional::some (15); const int &value = const_opt.get (); ASSERT_EQ (value, 15); } static void rust_optional_operators () { auto opt = Rust::Optional::some (15); // as bool ASSERT_TRUE (opt); // deref ASSERT_EQ (*opt, 15); class Methodable { public: int method () { return 15; } }; auto m_opt = Rust::Optional::some (Methodable ()); ASSERT_EQ (m_opt->method (), 15); } static void rust_optional_take () { auto opt = Rust::Optional::some (15); auto value = opt.take (); ASSERT_EQ (value, 15); ASSERT_TRUE (opt.is_none ()); } static void rust_optional_map () { auto opt = Rust::Optional::some (15); auto twice = opt.map ([] (int value) { return value * 2; }); ASSERT_FALSE (opt); ASSERT_TRUE (twice); ASSERT_EQ (*twice, 30); } static void rust_optional_test () { rust_optional_create (); rust_optional_operators (); rust_optional_take (); rust_optional_map (); } #endif // !CHECKING_P #endif // !RUST_OPTIONAL_H