Traits

Rust traits are more akin to Perl roles; they describe "does-a" rather than "is-a" relationships.

If we define a role like this in Perl

package Area {
    use Role::Tiny;
    requires qw(area);
}

then anything that does the Area role must have a method called area.

Similary, if we define a trait like this in Rust


#![allow(unused_variables)]
fn main() {
trait Area {
    fn area(&self) -> f64;
}
}

then anything that implements the Area trait, must have a method called area with that exact function signature.

For example, in Perl we could create a Circle and a Rectangle that both do the Area role like so

#!/usr/bin/env perl

use v5.28;
use warnings;
use experimental qw(signatures);

package Area {
    use Role::Tiny;
    requires qw(area);
}

package Circle {
    use Role::Tiny::With;
    with 'Area';
        
    sub new($class, $radius) {
        my $self = {
            _radius => $radius,
        };
        bless $self, $class;
        return $self;
    }

    sub area($self) {
        3.14159265358979 * $self->{_radius} ** 2
    }
}

package Rectangle {
    use Role::Tiny::With;
    with 'Area';
        
    sub new($class, $length, $width) {
        my $self = {
            _length => $length,
            _width  => $width,
        };
        bless $self, $class;
        return $self;
    }

    sub area($self) {
        $self->{_length} * $self->{_width}
    }
}

my $c = Circle->new(1);
say "Area of circle is ", $c->area;

my $r = Rectangle->new(2, 3);
say "Area of rectangle is ", $r->area;

Alternatively, here it is again using Perl's Object::Pad.

Similarly, in Rust we could create a Circle and a Rectangle that both implement the Area trait like so

trait Area {
    fn area(&self) -> f64;
}

struct Circle {
    radius: f64,
}

impl Circle {
    fn new(radius: f64) -> Self {
        Self{radius}
    }
}

impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius.powi(2)
    }
}

struct Rectangle {
    length: f64,
    width: f64,
}

impl Rectangle {
    fn new(length: f64, width: f64) -> Self {
        Self{length, width}
    }
}

impl Area for Rectangle {
    fn area(&self) -> f64 {
        self.length * self.width
    }
}

fn main() {
    let c = Circle::new(1.0);
    println!("Area of circle is {}", c.area());

    let r = Rectangle::new(2.0, 3.0);
    println!("Area of rectangle is {}", r.area());
}

Running either of these produces

Area of circle is 3.14159265358979
Area of rectangle is 6

There's more to Rust's traits (they're sort of the key to all of the magic in Rust), but that'll do for now.