17_Cairo 中的 Trait#
This article uses the Cairo compiler version: 2.0.0-rc0. Because Cairo is being updated rapidly, the syntax may vary slightly between different versions, and the content of this article will be updated to the stable version in the future.
We have written a lot of code that uses 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 that this trait possesses. 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 for 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 here
RectangleGeometryImpl::area(@r).print();
}
In the above code, we define a struct named Rectangle, and then define 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 for 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 Traits#
In the example above, the trait specifies the Rectangle type, so the trait can only be implemented by the Rectangle. If there is a scenario where multiple types have the same trait characteristics, we need to use generic traits.
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, and the main function uses member methods with the same name.