Understanding Generics
#
Understanding GenericsGenerics are essential to Rogue, they are what makes this language so unique to the blockchain world, and they are the source of Rogue's flexibility.
To start We'll quote Rust Book: Generics are abstract stand-ins for concrete types or other properties. Practically speaking, they are the way of writing a single function, which can then be used for any type, they can also be called templates as this function can be used as a template handler for any type.
In Rogue generics can be applied to signatures of struct
and function
.
#
In struct definitionFirst, we'll create a Box which will hold u64
value. We've already been through this, so no need for comments.
This box can only contain value of u64
type - this is clear. But what if we wanted to make the same box for u8
type or a bool
? Would we create type Box1
and then Box2
? Or would we publish another method for that? The answer is no - use generics instead.
Next to the struct name we've placed <T>
. Where angle brackets <..>
is a place to define generic types, and T
is a type we've templated in this struct. Inside the struct body definition we've used T
as a regular type. Type T does not exist, it is a placeholder for any type.
#
In function signatureNow let's create a constructor for this struct which will first use type u64
for value.
Generics have a bit more complicated definitions - since they need to have type parameters specified, and regular struct Box
becomes Box<u64>
. There are no restrictions in what types you can pass into generic's angle brackets. Let's make our create_box
method more generic and let users specify any type. How do we do that? By using another generic, now in function signature!
#
In function callsWhat we did is we added angle brackets into function signature right after function name. Just the same way as we did with struct. Now how would we use this function? By specifying type in function call.
Here we have used Box struct with 3 types: bool
, u64
and with Box<u64>
- last one may seem way too complicated but once you've gotten used to it and understood how it works, it becomes part of your routine.
Before we go any further, let's take a step back. By adding generics to Box
struct we've made this box abstract - its definition is fairly simple compared to capacity it gave us. Now we can create Box
with any type - be it u64
or address
, or even another box, or another struct.
#
Constraints to check AbilitiesWe've learned about abilities. They can be "checked" or constrained in generics; constraints are named after their abilities:
...or with structs:
Try to remember this syntax:
+
(plus) sign may not be intuitive first time; it is the only place in Rogue where+
is used in keyword list.
Here's an example of a system with constraints:
It is also important to mention that inner types (or generic types) MUST have abilities of the container (for all abilities except key
). If you think about it, everything is logical and intuitive: struct with copy ability must have contents that also have copy ability otherwise container object cannot be considered copyable. Rogue compiler will let you compile code that doesn't follow this logic but you won't be able to use these abilities. See this example:
This code compiles and publishes successfully. But if you try to use it...
You will get an error saying that Box is not droppable:
That happens because inner value doesn't have drop ability. Container abilities automatically limited by its contents, so, for example if you have a container struct that has copy, drop and store, and inner struct has only drop, it will be impossible to copy or store this container. Another way to look at this is that container doesn't have to have constraints for inner types and can be flexible - used for any type inside.
But to avoid mistakes always check and, if needed, specify generic constraints in functions and structs.
In this example safer struct definition could be:
#
Multiple types in genericsJust like you can use a single type, you can use many. Generic types are put into angle brackets and separated by comma. Let's add a new type Shelf
which will hold two boxes of two different types.
Type parameters for Shelf
are listed and matched inside struct's fields definition. Also, as you can see, name of the type parameter inside generics does not matter - it's up to you to choose a proper one. And each type parameter is only valid within definition so no need to match T1
or T2
with T
.
Using multiple generic type parameters is similar to using single:
You can have up to 18,446,744,073,709,551,615 (u64 size) generics in one definition. You definitely will never reach this limit, so feel free to use as many as you need without worrying about limits.
#
Unused type paramsNot every type specified in generic must be used. Look at this example:
Sometimes it is nice to have generic as a constraint or constant for some action. See how it can be used in script:
Here we use generics to mark type, but we don't actually use it. You'll soon learn why this definition matters when you get to know resources concept. For now it's just another way to use them.