Skip to content

nexosim.types

Type-related utilities.

This module provides facilities to conveniently build Python counterparts to Rust's struct and enum types that can be (de)serialized as events, requests or replies within a Simulation.

Struct-like classes

Rust distinguishes between regular struct types (with named fields), unit struct types and tuple struct types. Because these types are serialized differently, it is important to preserve this distinction on the Python side.

Regular Rust struct types can be directly represented in Python with dataclasses, using e.g. the standard @dataclasses.dataclass decorator or the @define decorator from the attrs package.

Unit struct types are in turn represented by empty classes deriving from the UnitType class. Likewise, tuple struct types are represented by deriving from one of the TupleType*Arg classes.

Here are example Rust struct types and their Python counterparts:

struct MyRegularStruct {
    foo: f64,
    bar: (u32, String),
}

struct MyUnitStruct;

struct MyTupleStruct(u32, String);
from dataclasses import dataclass

from nexosim.types import UnitType, TupleType2Arg

@dataclass
class MyRegularStruct:
    foo: int
    bar: (int, str)

class MyUnitStruct(UnitType): ... # no implementation needed

class MyTupleStruct(TupleType2Arg[int, str]): ... # no implementation needed

In the case of tuple struct types, if you don't use a type checker or if your type checker supports dynamically-generated base classes (as of the day of this writing, this is the case for Pyright but not Mypy), it is possible to save a bit of typing by using the convenience tuple_type type constructor instead of the TupleType*Arg classes. For instant, the above MyTupleStruct could be instead defined with:

from nexosim.types import tuple_type

class MyTupleStruct(tuple_type(int, str)): ...

Note that tuple struct types with arity greater than 16 must be defined manually with the tupleclass decorator. This is for instance how TupleType2Arg is defined:

from nexosim.types import variantclass

@tupleclass
class TupleType2Arg[Arg0, Arg1]:
    _0: Arg0
    _1: Arg1

Enum-like classes

Rust enum types are represented by classes decorated with @enumclass, defining the variants as nested classes.

Unit-like, tuple-like and struct-like variants are defined in exactly the same way as struct-like classes.

Warning

Classes decorated with @enumclass implicitly define an enum type but are not the enum type itself.

Rather, the enum type is represented by a Python Union of the nested variant types, which for convenience is automatically generated by the @enumclass decorator and exposed as the type class variable.

Here's an example Rust enum and its Python counterpart:

enum MyEnum {
    MyUnitVariant,
    MyTupleVariant(i32, String),
    MyStructVariant { foo: f64, bar: u64 },
}
from dataclasses import dataclass
from nexosim.types import enumclass, UnitType, TupleType2Arg

@enumclass
class MyEnum:
    class MyUnitVariant(UnitType): ...

    class MyTupleVariant(TupleType2Arg[int, str]): ...

    @dataclass
    class MyStructVariant:
        foo: float
        bar: int

    # The following class variable is automatically generated:
    #
    # type = MyUnitVariant | MyTupleVariant | MyStructVariant

If the above enum were in turn used within a Rust struct, it would be represented like this:

struct MyComposedStruct {
    s: String,
    e: MyEnum,
}
@dataclass
class MyComposedStruct:
    s: str
    e: MyEnum.type # <- the actual union type is given by the `type` field

Type checking enums

Because the type field is dynamically added to the enum class definition by the @enumclass decorator, it is invisible to type checkers. This would in particular cause type checkers to complain in the earlier declaration of MyComposedStruct, even though its definition is valid and usable for serialization and deserialization purposes.

To prevent such false positives, you may elect to explicitly set the type field, for instance:

@enumclass
class MyEnum:
    class MyUnitVariant(UnitType): ...

    class MyTupleVariant(TupleType2Arg[int, str]): ...

    @dataclass
    class MyStructVariant:
        foo: float
        bar: int

    type = MyUnitVariant | MyTupleVariant | MyStructVariant

In order to prevent mistakes, the @enumclass decorator raises an exception if an explicitly-set type field is not equivalent to the auto-generated one.

Pattern matching

Tuple- and struct-like enum variants are dataclasses, either because they are explicitly marked as such (struct-like variants) or because they derive from dataclasses (tuple-like variants). This implies that enums can be structurally matched.

This is an example of pattern matching for the previously-defined MyEnum:

def print_my_enum(my_enum):
    match my_enum:
        case MyEnum.MyUnitVariant():
            print("MyUnitVariant")
        case MyEnum.MyTupleVariant(number, string):
            print(f"MyTupleVariant({number}, {string})")
        case MyEnum.MyStructVariant(foo=x, bar=y):
            print(f"MyStructVariant(foo={x}, bar={y})")
        case _:
            raise RuntimeError("unexpected enum variant")

Explicitly setting the type field additionally enables exhaustiveness checking. For instance, if the type field is explicitly set in MyEnum's definition, the missing case in the following code can be caught by the type checker:

def my_enum_discriminant(my_enum: MyEnum.type) -> int:
    match my_enum: # ERROR: function does not return `int` on all code paths
        case MyEnum.MyUnitVariant():
            return 0
        case MyEnum.MyTupleVariant(_):
            return 1

UnitType

Base class for unit-like types.

TupleType0Arg

Base class for nullary tuple-like types.

TupleType1Arg

Bases: Generic[_Arg0]

Base class for unary tuple-like types.

TupleType2Arg

Bases: Generic[_Arg0, _Arg1]

Base class for binary tuple-like types.

TupleType3Arg

Bases: Generic[_Arg0, _Arg1, _Arg2]

Base class for ternary tuple-like types.

TupleType4Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3]

Base class for 4-ary tuple-like types.

TupleType5Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4]

Base class for 5-ary tuple-like types.

TupleType6Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5]

Base class for 6-ary tuple-like types.

TupleType7Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6]

Base class for 7-ary tuple-like types.

TupleType8Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7]

Base class for 8-ary tuple-like types.

TupleType9Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8]

Base class for 9-ary tuple-like types.

TupleType10Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9]

Base class for 10-ary tuple-like types.

TupleType11Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10]

Base class for 11-ary tuple-like types.

TupleType12Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11]

Base class for 12-ary tuple-like types.

TupleType13Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12]

Base class for 13-ary tuple-like types.

TupleType14Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13]

Base class for 14-ary tuple-like types.

TupleType15Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13, _Arg14]

Base class for 15-ary tuple-like types.

TupleType16Arg

Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13, _Arg14, _Arg15]

Base class for 16-ary tuple-like types.

enumclass(cls)

Class decorator that marks a class as the definition of a Rust-like enum.

Note that the decorated class is not the enum type itself: the Python Union of all variants that actually represents the enum is generated by this decorator and exposed as the type class variable.

tupleclass(cls)

Decorator that marks a class as a tuple-like type.

This automatically makes the class a dataclass.

tuple_type(*args)

tuple_type() -> type[TupleType0Arg]
tuple_type(_0: type[_Arg0]) -> type[TupleType1Arg[_Arg0]]
tuple_type(_0: type[_Arg0], _1: type[_Arg1]) -> type[TupleType2Arg[_Arg0, _Arg1]]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2]) -> TupleType3Arg[_Arg0, _Arg1, _Arg2]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3]) -> TupleType4Arg[_Arg0, _Arg1, _Arg2, _Arg3]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4]) -> TupleType5Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5]) -> TupleType6Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6]) -> TupleType7Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7]) -> TupleType8Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8]) -> TupleType9Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9]) -> TupleType10Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10]) -> TupleType11Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11]) -> TupleType12Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11], _12: type[_Arg12]) -> TupleType13Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11], _12: type[_Arg12], _13: type[_Arg13]) -> TupleType14Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11], _12: type[_Arg12], _13: type[_Arg13], _14: type[_Arg14]) -> TupleType15Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13, _Arg14]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11], _12: type[_Arg12], _13: type[_Arg13], _14: type[_Arg14], _15: type[_Arg15]) -> TupleType16Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13, _Arg14, _Arg15]

A convenient type constructor for tuple-like types.

This function can saves some typing and reduce import boilerplate compared to manually specifying the TupleType*Arg base classes. It takes the types of the components as arguments and is overloaded by arity to return the appropriate TupleType*Arg class.

Warning

Note that not all type checkers support dynamically generated base classes and it may be necessary to manally specify the appropriate TupleType*Arg base class in order to satisfy the type checker.