A trait opts a type into a certain type of behavior or functionality that can be shared among types. This allows for easy reuse of code and generic programming. If you have ever used a typeclass in Haskell, a trait in Rust, or even an interface in Java, these are similar concepts.
We have just declared a trait called Compare. After the name of the trait, there are two blocks of code (a block is code enclosed in { curly brackets }). The first block is the interface surface. The second block is the methods provided by the trait. If a type can provide the methods in the interface surface, then it gets access to the methods in the trait for free! What the above trait is saying is: if you can determine if two values are equal, then for free, you can determine that they are not equal. Note that trait methods have access to the methods defined in the interface surface.
The example below implements a Compare trait for u64 to check if two numbers are equal. Let's take a look at how that is done:
implCompareforu64 {fnequals(self, b:Self) -> bool {self== b }}
The above snippet declares all of the methods in the trait Compare for the type u64. Now, we have access to both the equals and not_equals methods for u64, as long as the trait Compare is in scope.
When using multiple traits, scenarios often come up where one trait may require functionality from another trait. This is where supertraits come in as they allow you to require a trait when implementing another trait, i.e., a trait with a trait.
A good example of this is the Ord trait of the core library of Sway. The Ord trait requires the Eq trait, so Eq is kept as a separate trait as one may decide to implement Eq
without implementing other parts of the Ord trait.
traitEq {fnequals(self, b:Self) -> bool;}traitOrd:Eq {fngte(self, b:Self) -> bool;}implOrdforu64 {fngte(self, b:Self) -> bool { // As `Eq` is a supertrait of `Ord`, `Ord` can access the equals methodself.equals(b) ||self.gt(b) }}
To require a supertrait, add a : after the trait name and then list the traits you would like to require and separate them with a +.
contract;structFoo {}implABIsupertraitforFoo {fnfoo() {}}traitABIsupertrait {fnfoo();}abiMyAbi:ABIsupertrait {fnbar();} {fnbaz() {Self::foo() // supertrait method usage }}implABIsupertraitforContract {fnfoo() {}}// The implementation of MyAbi for Contract must also implement ABIsupertraitimplMyAbiforContract {fnbar() {Self::foo() // supertrait method usage }}
The implementation of MyAbi for Contract must also implement the ABIsupertrait trait. Methods in ABIsupertrait are not available externally, i.e. they're not actually contract methods, but they can be used in the actual contract methods, as shown in the example above.
ABI supertraits are intended to make contract implementations compositional, allowing combining orthogonal contract features using, for instance, libraries.
In addition to supertraits, ABIs can have superABI annotations:
contract;abiMySuperAbi {fnfoo();}abiMyAbi:MySuperAbi {fnbar();}implMySuperAbiforContract {fnfoo() {}}// The implementation of MyAbi for Contract must also implement MySuperAbiimplMyAbiforContract {fnbar() {}}
The implementation of MyAbi for Contract must also implement the MySuperAbi superABI. Methods in MySuperAbi will be part of the MyAbi contract interface, i.e. will be available externally (and hence cannot be called from other MyAbi contract methods).
SuperABIs are intended to make contract implementations compositional, allowing combining orthogonal contract features using, for instance, libraries.
Associated functions in traits consist of just function signatures. This indicates that each implementation of the trait for a given type must define all the trait functions.
Associated constants are constants associated with a type.
traitTrait {constID:u32=0;}
The initializer expression of an associated constants in a trait definition may be omitted to indicate that each implementation of the trait for a given type must specify an initializer:
traitTrait {constID:u32;}
Check the associated consts section on constants page.
Associated types in Sway allow you to define placeholder types within a trait, which can be customized by concrete
implementations of that trait. These associated types are used to specify the return types of trait methods or to
define type relationships within the trait.
Often, libraries and APIs have interfaces that are abstracted over a type that implements a certain trait. It is up to the consumer of the interface to implement that trait for the type they wish to use with the interface. For example, let's take a look at a trait and an interface built off of it.
library;pubenumSuit {Hearts: (),Diamonds: (),Clubs: (),Spades: (),}pubtraitCard {fnsuit(self) -> Suit;fnvalue(self) -> u8;}fnplay_game_with_deck<T>(a:Vec<T>) whereT:Card { // insert some creative card game here}
Now, if you want to use the function play_game_with_deck with your struct, you must implement Card for your struct. Note that the following code example assumes a dependency games has been included in the Forc.toml file.
script;use games::*;structMyCard { suit:Suit, value:u8}implCardforMyCard {fnsuit(self) -> Suit {self.suit }fnvalue(self) -> u8 {self.value }}fnmain() {letmut i =52;letmut deck:Vec<MyCard> =Vec::with_capacity(50);while i >0 { i = i -1; deck.push(MyCard { suit:generate_random_suit(), value: i %4} }play_game_with_deck(deck);}fngenerate_random_suit() -> Suit { [ ... ]}