Traits

A trait is a collection of functions. Unlike in Rust, traits are not defined for a specific implementor type; they can contain methods for different types.

In practice, we often define a trait for a generic type T, and then implement that trait for specific types.

In the example below, we define Animal, a group of methods over a generic type T. The Animal trait is then implemented for the Sheep data type, allowing the use of methods from Animal with a Sheep.

The Sheep data type also has some methods. In Cairo, type methods are defined in traits, where the self type is the type itself. As such, we use the #[generate_trait] attribute to automatically generate a trait from the definition of an impl containing type methods.

What enables the method syntax is the self keyword, which can be used to refer to the implementor type. In that case, any type T that implements Animal can use the methods defined in Animal.

#[derive(Drop)]
struct Sheep {
    naked: bool,
    name: ByteArray,
}

trait Animal<T> {
    // Associated function signature; `T` refers to the implementor type
    fn new(name: ByteArray) -> T;

    // Method signatures; these will return a ByteArray
    fn name(self: @T) -> ByteArray;
    fn noise(self: @T) -> ByteArray;

    // Traits can provide default method definitions
    fn talk(self: @T) {
        println!("{} says {}", Self::name(self), Self::noise(self));
    }
}


// The `#[generate_trait]` attribute is used to automatically generate a trait from the definition
// of an impl.
// This pattern is often used to define methods on a type.
#[generate_trait]
impl SheepImpl of SheepTrait {
    fn is_naked(self: @Sheep) -> bool {
        *self.naked
    }

    fn shear(ref self: Sheep) {
        if self.is_naked() {
            // Implementor methods can use the implementor's trait methods
            println!("{} is already naked...", self.name());
        } else {
            println!("{} gets a haircut!", self.name);
            self.naked = true;
        }
    }
}

// Implement the `Animal` trait for `Sheep`
impl SheepAnimal of Animal<Sheep> {
    // `T` is the implementor type: `Sheep`
    fn new(name: ByteArray) -> Sheep {
        Sheep { name: name, naked: false }
    }

    fn name(self: @Sheep) -> ByteArray {
        self.name.clone()
    }

    fn noise(self: @Sheep) -> ByteArray {
        if self.is_naked() {
            "baaaaah?" // Questioning
        } else {
            "baaaaah!" // Confident
        }
    }

    // Default trait methods can be overridden
    fn talk(self: @Sheep) {
        // For example, we can add some quiet contemplation
        println!("{} pauses briefly... {}", self.name, self.noise());
    }
}

fn main() {
    // Type annotation is necessary in this case
    let mut dolly: Sheep = Animal::new("Dolly");
    // TODO ^ Try removing the type annotations

    dolly.talk();
    dolly.shear();
    dolly.talk();
}