Trait in 17_Cairo#
The version of the Cairo compiler used in this article is 2.0.0-rc0. Because Cairo is being updated rapidly, the syntax may vary slightly in different versions, and the content of the article will be updated to the stable version in the future.
We have already written a lot of code using traits before, and now let's summarize the usage of traits.
The literal meaning of Trait is "characteristic", which is equivalent to the interface in other programming languages. You can use traits to define a set of methods as the specific content of the characteristics possessed by this trait. Any type can implement all the methods in this trait, which means it has the characteristics of this trait.
Basic Usage#
When a struct implements a trait, it needs to implement all the methods in the trait, otherwise it cannot be considered as implementing the trait, and there will be a compilation error. Let's take a look at an example:
use debug::PrintTrait;
#[derive(Copy, Drop)]
struct Rectangle {
width: u64,
high: u64
}
trait ShapeGeometry {
fn new(width: u64, high: u64) -> Rectangle;
fn boundary(self: @Rectangle) -> u64;
fn area(self: @Rectangle) -> u64;
}
// Implement the logic of the three functions here
impl RectangleGeometryImpl of ShapeGeometry {
fn new(width: u64, high: u64) -> Rectangle {
Rectangle { width, high }
}
fn boundary(self: @Rectangle) -> u64 {
2 * (*self.high + *self.width)
}
fn area(self: @Rectangle) -> u64 {
*self.high * *self.width
}
}
fn main() {
// Call the new method directly using impl here
let r = RectangleGeometryImpl::new(10, 20);
// Call the boundary and area methods using the struct here
r.boundary().print();
r.area().print();
// Call the area method directly using impl
RectangleGeometryImpl::area(@r).print();
}
The above code defines a struct named Rectangle, and then defines a trait named ShapeGeometry, which includes the signatures of three methods: new
, boundary
, and area
. To implement the ShapeGeometry trait for the Rectangle struct, we need to write the logic of the three functions in the impl block.
In addition, in the main function, we can see that we can also call member functions directly using impl.
Generic Trait#
In the example above, the Rectangle type is specified in the trait, so the trait can only be implemented by Rectangle. If there are multiple types that have the same trait characteristics, a generic trait needs to be used.
use debug::PrintTrait;
#[derive(Copy, Drop)]
struct Rectangle {
height: u64,
width: u64,
}
#[derive(Copy, Drop)]
struct Circle {
radius: u64
}
// This generic trait can be implemented by multiple structs
trait ShapeGeometryTrait<T> {
fn boundary(self: T) -> u64;
fn area(self: T) -> u64;
}
// Implemented by the Rectangle type
impl RectangleGeometryImpl of ShapeGeometryTrait<Rectangle> {
fn boundary(self: Rectangle) -> u64 {
2 * (self.height + self.width)
}
fn area(self: Rectangle) -> u64 {
self.height * self.width
}
}
// Implemented by the Circle type
impl CircleGeometryImpl of ShapeGeometryTrait<Circle> {
fn boundary(self: Circle) -> u64 {
(2 * 314 * self.radius) / 100
}
fn area(self: Circle) -> u64 {
(314 * self.radius * self.radius) / 100
}
}
fn main() {
let rect = Rectangle { height: 5, width: 7 };
rect.area().print(); // 35
rect.boundary().print(); // 24
let circ = Circle { radius: 5 };
circ.area().print(); // 78
circ.boundary().print(); // 31
}
Above, we define the ShapeGeometryTrait<T>
trait, which is implemented by both the Rectangle and Circle structs, and the main function uses member methods with the same name.