Trait in Cairo#
The version of the Cairo compiler used in this article is 2.0.0-rc0. Since 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, 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. Traits can be used 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
let r = RectangleGeometryImpl::new(10, 20);
// Call the boundary and area methods using the struct
r.boundary().print();
r.area().print();
// Call the area method directly using impl
RectangleGeometryImpl::area(@r).print();
}
In the above code, a struct named Rectangle is defined, followed by a trait named ShapeGeometry, which includes the signatures of three methods: new
, boundary
, and area
. To implement the ShapeGeometry trait for the Rectangle struct, the logic of the three functions in the trait needs to be written 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 explicitly specified in the trait, so the trait can only be implemented by the Rectangle. If there is a scenario where multiple types 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
}
In the above code, we define the ShapeGeometryTrait<T>
trait, which is implemented by both the Rectangle and Circle structs. The main function uses member methods with the same name.
Translation: