Normally, our code needs to know the exact type of a struct to be able to work with it.
In this example, hopscotch only works for a Human.
If we want a hopscotch for DarkElf too, we'd have to copy and paste the hopscotch function and adjust it to use DarkElf instead. How tedious!
It would be great if we could make a function accept any kind of reference, as long as it has the right functions (such as hop and skip).
We can, using interfaces!
We can create an interface with the interface keyword, like Bipedal here.
Then, to tell the compiler that Human and DarkElf conform to that interface, we use the impl keyword. 0
Last, we change hopscotch to take in a &Bipedal, and now we can call it with a Human or DarkElf, since both implement the Bipedal interface.
When a struct implements an interface, we call it a substruct of that interface.
Future versions will have a much simpler syntax, see the bottom of the page for a preview!
We can downcast an interface to a struct using the as function:
Note that as<FireflyShip>() returns a Result<FireflyShip, Ship>, which will be either an Ok containing a FireflyShip, or an Err containing a Ship. In the above code, we're calling .expect() because we assume it is indeed a FireflyShip.
(Also known as "Anonymous Substructs")
When we make an open interface like the above Bipedal, the compiler makes an open interface constructor, a function which can define another substruct.
It takes functions (or lambdas) as arguments, to use for the interface's methods.
In this example, we're giving it a lambda for its launch method.
These are planned features for Vale. See the roadmap for plans!
The current syntax is closer to the way Vale internally thinks about interfaces. However, an easier syntax is planned:
By default, an interface is open, which means any struct can implement it.
A sealed interface can only be implemented by structs defined in the same file.
Because the compiler knows all the implementing structs up-front, it:
Locals and members can inline sealed interfaces. The compiler will figure out the size of the largest substruct, and reserve that much memory in the stack (if an inl local) or the containing struct (if an inl member), and use that memory for the substruct. (This is the equivalent of a C/C++ union or Rust enum)
A sealed interface constructor is a function with the same name of the interface, which constructs the correct subclass depending on the given arguments.
In this example, when we call MaybeInt(7), the compiler finds that the SomeInt substruct indeed has a constructor accepting one int, and calls that.
A sealed interface constructor also allows us to make substructs via shortcalling. 3
See Structs for more on shortcalling.
We normally call a function by name, such as the MaybeInt(7) above. However, if the code is expecting a certain type, it can automatically call the constructor of the expected type.
This saves a lot of typing when calling functions.
Next: Generics