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)