Traits
Declaring a Trait
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.
Let's take a look at some code:
trait Compare {
fn equals(self, b: Self) -> bool;
} {
fn not_equals(self, b: Self) -> bool {
!self.equals(b)
}
}
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.
Implementing a Trait
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:
impl Compare for u64 {
fn equals(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.
Supertraits
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.
trait Eq {
fn equals(self, b: Self) -> bool;
}
trait Ord: Eq {
fn gte(self, b: Self) -> bool;
}
impl Ord for u64 {
fn gte(self, b: Self) -> bool {
// As `Eq` is a supertrait of `Ord`, `Ord` can access the equals method
self.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 +
.
ABI supertraits
ABIs can also have supertrait annotations:
contract;
struct Foo {}
impl ABIsupertrait for Foo {
fn foo() {}
}
trait ABIsupertrait {
fn foo();
}
abi MyAbi : ABIsupertrait {
fn bar();
} {
fn baz() {
Self::foo() // supertrait method usage
}
}
impl ABIsupertrait for Contract {
fn foo() {}
}
// The implementation of MyAbi for Contract must also implement ABIsupertrait
impl MyAbi for Contract {
fn bar() {
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.
SuperABIs
In addition to supertraits, ABIs can have superABI annotations:
contract;
abi MySuperAbi {
fn foo();
}
abi MyAbi : MySuperAbi {
fn bar();
}
impl MySuperAbi for Contract {
fn foo() {}
}
// The implementation of MyAbi for Contract must also implement MySuperAbi
impl MyAbi for Contract {
fn bar() {}
}
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 Items
Traits can declare different kinds of associated items in their interface surface:
Associated functions
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.
trait Trait {
fn associated_fn(self, b: Self) -> bool;
}
Associated constants
Associated constants are constants associated with a type.
trait Trait {
const ID: 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:
trait Trait {
const ID: u32;
}
Check the associated consts
section on constants page.
Associated types
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.
trait MyTrait {
type AssociatedType;
}
Check the associated types
section on associated types page.
Trait Constraints
When writing generic code, you can constraint the choice of types for a generic argument by using the where
keyword. The where
keyword specifies which traits the concrete generic parameter must implement. In the below example, the function expects_some_trait
can be called only if the parameter t
is of a type that has SomeTrait
implemented. To call the expects_both_traits
, parameter t
must be of a type that implements both SomeTrait
and SomeOtherTrait
.
trait SomeTrait { }
trait SomeOtherTrait { }
fn expects_some_trait<T>(t: T) where T: SomeTrait {
// ...
}
fn expects_some_other_trait<T>(t: T) where T: SomeOtherTrait {
// ...
}
fn expects_both_traits<T>(t: T) where T: SomeTrait + SomeOtherTrait {
// ...
}
Marker Traits
Sway types can be classified in various ways according to their intrinsic properties. These classifications are represented as marker traits. Marker traits are implemented by the compiler and cannot be explicitly implemented in code.
E.g., all types whose instances can be used in the panic
expression automatically implement the Error
marker trait. We can use that trait, e.g., to specify that a generic argument must be compatible with the panic
expression:
fn panic_with_error<E>(err: E) where E: Error {
panic err;
}
Note
panic
expression and error types have not yet been implemented
All marker traits are defined in the core::marker
module.
Use Cases
Custom Types (structs, enums)
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;
pub enum Suit {
Hearts: (),
Diamonds: (),
Clubs: (),
Spades: (),
}
pub trait Card {
fn suit(self) -> Suit;
fn value(self) -> u8;
}
fn play_game_with_deck<T>(a: Vec<T>) where T: 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::*;
struct MyCard {
suit: Suit,
value: u8
}
impl Card for MyCard {
fn suit(self) -> Suit {
self.suit
}
fn value(self) -> u8 {
self.value
}
}
fn main() {
let mut i = 52;
let mut 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);
}
fn generate_random_suit() -> Suit {
[ ... ]
}