Currently, trait composition is done using &, e.g. (from the docs): comptime DuckLike = Quackable & Flyable
At the same time, struct “composition” is done using std.Variant, e.g. comptime IntOrString = Variant[Int, String]. Notably, there is no shorthand such as comptime IntOrString = Int | String
Python is using Union and the shorthand | for this purpose. Interestingly, in the Mojo docs, the term union is used to describe Variant.
Variant - A union that can hold a runtime-variant value from a set of predefined types.
Variant is a discriminated union type
Unifying these (and aligning with Python) would make it easier to teach and use. Maybe in the future these would be valid (e.g. via dynamic dispatch or by synthetizing the union):
def foo(flying_quacker: Quackable | Flyable) or def foo(flying_quacker: Quackable & Flyable)
but also def bar(int_or_str: Int | String)
compare with the current: def foo[T: Quackable & Flyable](flying_quacker: T)
and def bar(int_or_str: Variant[Int, String])
Or at the very least, keeping trait composition as is and renaming Variant to Union and allowing the shorthand | would be a good change in my opinion.
We are saving the Int | String syntax for a proper anonymous tagged union type that can support pattern matching, since there is general agreement that something more like Rust’s enum is preferable to Variant. The reason that trait unions are treated separately is because they can’t exist on their own, they’re a way to name a trait which depends on all of the traits that make up the union.
Union is a name quasi-reserved for unsafe, C-like unions and variants of those (ex: using linear types and typestate to manage the active variant), which we need to have in Mojo to be able to do C interop, so I consider that name taken.
Hi, thanks for the reply! Shouldn’t the default be whatever the Python community is used to? For the C interop Mojo could very well use CUnion or UnsafeUnion or NativeUnion, or potentially just std.ffi.Union (which would be different from std.utils.Union)
The most common use-case will be reproducing Python functionality (i.e. rewrite Python code in Mojo), C interop is a corner case for which familiarity and simplicity is less valuable.
There was some discussion about that, but it tended to cause a lot of confusion both among systems programmers (who assumed that Union was a tagged union instead of a type union), and python programmers (“What are all of these extra kinds of Union?”). It would also result in a lot of python programmers accidentally using the extremely unsafe constructs and then getting confusing crashes.
While I’m sympathetic to the break with python here, my python programmers never even see the Union type name, instead using str | int, and placing that up against decades of expectations from another major group of Mojo programmers means I don’t think it’s worth the confusion.