Implementing traits
Trait conformance is just a fancy way of saying that a specific smart contract implements the functions defined in the trait. Take the following example trait:
(define-trait multiplier
(
(multiply (uint uint) (response uint uint))
)
)
If we want to implement the trait, all we have to do is make sure that our
contract contains a function multiply
that takes two uint
parameters and
returns a response that is either a (ok uint)
or (err uint)
. The following
contract will do just that:
(define-read-only (multiply (a uint) (b uint))
(ok (* a b))
)
(define-read-only (divide (a uint) (b uint))
(ok (/ a b))
)
Notice how the contract has another function divide
that is not present in the
multiplier
trait. That is completely fine because any system that is looking
for contracts that implement multiplier
do not care what other functions those
contracts might implement. Reliance on the trait ensures that conforming
contracts have a compatible multiply
function implementation, nothing more.
Asserting trait implementations
In the opening paragraph of this chapter we talked about implicit and explicit conformity. Under normal circumstances you always want to explicitly assert that your contract implements a trait.
Imagine that the multiplier
trait is deployed in a contract called
multiplier-trait
by the same principal. To assert that the example contract
implements the trait, the impl-trait
function is used.
(impl-trait .multiplier-trait.multiplier)
By adding this expression, the analyser will check if the contract implements
the trait specified when the contract is deployed. It will reject the
transaction if the contract is not a full implementation. It is therefore
recommended to always use impl-trait
because it prevent accidental
non-conformity.
The impl-trait
function takes a single trait reference parameter. A trait
reference is a contract principal plus
the name of the trait. Trait references can be written in short form as seen
above, or as a fully qualified reference.
(impl-trait 'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.multiplier-trait.multiplier)
The introduction mentioned Clarity favours
composition over inheritance.
Smart contracts can implement multiple traits, leading to more complex composite
behaviour when required. For example, say that there also exists a divider
trait that describes the divide
function, deployed in another contract called
divider-trait
.
(define-trait divider
(
(divide (uint uint) (response uint uint))
)
)
A contract that implements both traits simply contains multiple impl-trait
expressions.
(impl-trait .multiplier-trait.multiplier)
(impl-trait .divider-trait.divider)