Struct mozjs::jsapi::mozilla::Variant

source ·
#[repr(C)]
pub struct Variant { pub _address: u8, }
Expand description

mozilla::Variant

A variant / tagged union / heterogenous disjoint union / sum-type template class. Similar in concept to (but not derived from) boost::variant.

Sometimes, you may wish to use a C union with non-POD types. However, this is forbidden in C++ because it is not clear which type in the union should have its constructor and destructor run on creation and deletion respectively. This is the problem that mozilla::Variant solves.

Usage

A mozilla::Variant instance is constructed (via move or copy) from one of its variant types (ignoring const and references). It does not support construction from subclasses of variant types or types that coerce to one of the variant types.

Variant<char, uint32_t> v1('a');
Variant<UniquePtr<A>, B, C> v2(MakeUnique<A>());
Variant<bool, char> v3(VariantType<char>, 0); // disambiguation needed
Variant<int, int> v4(VariantIndex<1>, 0); // 2nd int

Because specifying the full type of a Variant value is often verbose, there are two easier ways to construct values:

A. AsVariant() can be used to construct a Variant value using type inference in contexts such as expressions or when returning values from functions. Because AsVariant() must copy or move the value into a temporary and this cannot necessarily be elided by the compiler, it’s mostly appropriate only for use with primitive or very small types.

Variant<char, uint32_t> Foo() { return AsVariant('x'); }
// ...
Variant<char, uint32_t> v1 = Foo();  // v1 holds char('x').

B. Brace-construction with VariantType or VariantIndex; this also allows in-place construction with any number of arguments.

struct AB { AB(int, int){...} };
static Variant<AB, bool> foo()
{
  return {VariantIndex<0>{}, 1, 2};
}
// ...
Variant<AB, bool> v0 = Foo();  // v0 holds AB(1,2).

All access to the contained value goes through type-safe accessors. Either the stored type, or the type index may be provided.

void
Foo(Variant<A, B, C> v)
{
  if (v.is<A>()) {
    A& ref = v.as<A>();
    ...
  } else (v.is<1>()) { // Instead of v.is<B>.
    ...
  } else {
    ...
  }
}

In some situation, a Variant may be constructed from templated types, in which case it is possible that the same type could be given multiple times by an external developer. Or seemingly-different types could be aliases. In this case, repeated types can only be accessed through their index, to prevent ambiguous access by type.

// Bad! template struct ResultOrError { Variant<T, int> m; ResultOrError() : m(int(0)) {} // Error ‘0’ by default ResultOrError(const T& r) : m(r) {} bool IsResult() const { return m.is(); } bool IsError() const { return m.is(); } }; // Now instantiante with the result being an int too: ResultOrError myResult(123); // Fail! // In Variant<int, int>, which ‘int’ are we refering to, from inside // ResultOrError functions?

// Good! template struct ResultOrError { Variant<T, int> m; ResultOrError() : m(VariantIndex<1>{}, 0) {} // Error ‘0’ by default ResultOrError(const T& r) : m(VariantIndex<0>{}, r) {} bool IsResult() const { return m.is<0>(); } // 0 -> T bool IsError() const { return m.is<1>(); } // 1 -> int }; // Now instantiante with the result being an int too: ResultOrError myResult(123); // It now works!

Attempting to use the contained value as type T1 when the Variant instance contains a value of type T2 causes an assertion failure.

A a;
Variant<A, B, C> v(a);
v.as<B>(); // <--- Assertion failure!

Trying to use a Variant<Ts...> instance as some type U that is not a member of the set of Ts... is a compiler error.

A a;
Variant<A, B, C> v(a);
v.as<SomeRandomType>(); // <--- Compiler error!

Additionally, you can turn a Variant that is<T> into a T by moving it out of the containing Variant instance with the extract<T> method:

Variant<UniquePtr<A>, B, C> v(MakeUnique<A>());
auto ptr = v.extract<UniquePtr<A>>();

Finally, you can exhaustively match on the contained variant and branch into different code paths depending on which type is contained. This is preferred to manually checking every variant type T with is() because it provides compile-time checking that you handled every type, rather than runtime assertion failures.

// Bad!
char* foo(Variant<A, B, C, D>& v) {
  if (v.is<A>()) {
    return ...;
  } else if (v.is<B>()) {
    return ...;
  } else {
    return doSomething(v.as<C>()); // Forgot about case D!
  }
}

// Instead, a single function object (that can deal with all possible
// options) may be provided:
struct FooMatcher
{
  // The return type of all matchers must be identical.
  char* operator()(A& a) { ... }
  char* operator()(B& b) { ... }
  char* operator()(C& c) { ... }
  char* operator()(D& d) { ... } // Compile-time error to forget D!
}
char* foo(Variant<A, B, C, D>& v) {
  return v.match(FooMatcher());
}

// In some situations, a single generic lambda may also be appropriate:
char* foo(Variant<A, B, C, D>& v) {
  return v.match([](auto&) {...});
}

// Alternatively, multiple function objects may be provided, each one
// corresponding to an option, in the same order:
char* foo(Variant<A, B, C, D>& v) {
  return v.match([](A&) { ... },
                 [](B&) { ... },
                 [](C&) { ... },
                 [](D&) { ... });
}

// In rare cases, the index of the currently-active alternative is
// needed, it may be obtained by adding a first parameter in the matcner
// callback, which will receive the index in its most compact type (just
// use `size_t` if the exact type is not important), e.g.:
char* foo(Variant<A, B, C, D>& v) {
  return v.match([](auto aIndex, auto& aAlternative) {...});
  // --OR--
  return v.match([](size_t aIndex, auto& aAlternative) {...});
}

Examples

A tree is either an empty leaf, or a node with a value and two children:

struct Leaf { };

template<typename T>
struct Node
{
  T value;
  Tree<T>* left;
  Tree<T>* right;
};

template<typename T>
using Tree = Variant<Leaf, Node<T>>;

A copy-on-write string is either a non-owning reference to some existing string, or an owning reference to our copy:

class CopyOnWriteString
{
  Variant<const char*, UniquePtr<char[]>> string;

  ...
};

Because Variant must be aligned suitable to hold any value stored within it, and because |alignas| requirements don’t affect platform ABI with respect to how parameters are laid out in memory, Variant can’t be used as the type of a function parameter. Pass Variant to functions by pointer or reference instead.

Fields§

§_address: u8

Trait Implementations§

source§

impl Clone for Variant

source§

fn clone(&self) -> Variant

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Variant

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
source§

impl PartialEq<Variant> for Variant

source§

fn eq(&self, other: &Variant) -> bool

This method tests for self and other values to be equal, and is used by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl Copy for Variant

source§

impl StructuralPartialEq for Variant

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> ToOwned for Twhere T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.