Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement multiple traits for a struct without repeating methods?

Tags:

rust

traits

The code below works just fine, but I'm repeating myself a lot and I don't think this is really Rustic. For example I'm implementing two traits for Square and this doesn’t feel right! Also the function coordinate() is repeated in the trait and in the implementation.

Is there a way to implement this code without repeating myself so often? Is it possible to implement the two traits like:

impl BasicInfo && Sides for Square {
    ....
} 

the above code does not work, it is just an idea. When a function can be applied to multiple structs, is it possible to define it just once in the trait BasicInfo and access it.

fn main() {
    let x_1: f64 = 2.5;
    let y_1: f64 = 5.2;
    let radius_1: f64 = 5.5;
    let width_01 = 10.54;

    let circle_01 = Circle { x: x_1, y: y_1, radius: radius_1 };
    let square_01 = Square { x: x_1, y: y_1, width: width_01, sides: 4 };

    println!("circle_01 has an area of {:.3}.", 
        circle_01.area().round());
    println!("{:?}", circle_01);
    println!("The coordinate of circle_01 is {:?}.\n", circle_01.coordinate());
    println!("coordinate of square_01: {:?} has an area of: {} m2 and also has {} sides.", 
        square_01.coordinate(), 
        (square_01.area() * 100.0).round() / 100.0,
        square_01.has_sides() );
}

#[derive(Debug)]
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

struct Square {
    x: f64,
    y: f64,
    width: f64,
    sides: i32,
}

trait BasicInfo {
    fn area(&self) -> f64;
    // coordinate() is declared here, but not defined. Is it possible to define it here and still be able to access it when I want it.
    fn coordinate(&self) -> (f64, f64);
}

trait Sides {
    fn has_sides(&self) -> i32;
}

impl BasicInfo for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
    // coordinate() gets defined again, and looks like repeating code
    fn coordinate(&self) -> (f64, f64) {
        (self.x, self.y)
    }
}

impl BasicInfo for Square {
    fn area(&self) -> f64 {
        self.width.powf(2.0)
    }
    // coordinate() gets defined again, and looks like repeating code
    fn coordinate(&self) -> (f64, f64) {
        (self.x, self.y)
    }
}

impl Sides for Square {
    fn has_sides(&self) -> i32 {
        self.sides
    }
}
like image 986
user3419211 Avatar asked Dec 14 '15 14:12

user3419211


People also ask

How do you implement traits?

Implementing a trait on a type is similar to implementing regular methods. The difference is that after impl , we put the trait name we want to implement, then use the for keyword, and then specify the name of the type we want to implement the trait for.

How do traits work rust?

A trait tells the Rust compiler about functionality a particular type has and can share with other types. Traits are an abstract definition of shared behavior amongst different types. So, we can say that traits are to Rust what interfaces are to Java or abstract classes are to C++.

What is impl in Rust?

The impl keyword is primarily used to define implementations on types. Inherent implementations are standalone, while trait implementations are used to implement traits for types, or other traits. Functions and consts can both be defined in an implementation.


3 Answers

for your second question (avoid repeating the identical implementation of coordinate) I wanted to show you the macro-based solution.

Funnily enough, it leaves you with 3 traits instead of 2, so it goes in the exact opposite direction of your first question. I guess you can't have everything! :)

// factoring out the Coordinates trait from BasicInfo
trait Coordinates {
    fn coordinate(&self) -> (f64, f64);
}

// but we can require implementors of BasicInfo to also impl Coordinates
trait BasicInfo: Coordinates {
    fn area(&self) -> f64;
}

// helper macro to avoid repetition of "basic" impl Coordinates
macro_rules! impl_Coordinates { 
    ($T:ident) => {
        impl Coordinates for $T {
            fn coordinate(&self) -> (f64, f64) { (self.x, self.y) }
        }
    }
}

#[derive(Debug)]
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

#[derive(Debug)]
struct Square {
    x: f64,
    y: f64,
    width: f64,
    sides: i32,
}

// the macro here will expand to identical implementations
// for Circle and Square. There are also more clever (but a bit
// harder to understand) ways to write the macro, so you can
// just do impl_Coordinates!(Circle, Square, Triangle, OtherShape)
// instead of repeating impl_Coordinates!
impl_Coordinates!(Circle);
impl_Coordinates!(Square);


trait Sides {
    fn has_sides(&self) -> i32;
}

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

impl BasicInfo for Square {
    fn area(&self) -> f64 {
        self.width.powf(2.0)
    }
}

impl Sides for Square {
    fn has_sides(&self) -> i32 {
        self.sides
    }
}
like image 113
Paolo Falabella Avatar answered Oct 19 '22 09:10

Paolo Falabella


Disclaimer: at the time of writing, there are two questions bundled in one, and the latter does not match the title. So going by the title...

Is it possible to implement multiple traits at once?

impl BasicInfo && Sides for Square {
    ....
}

No.

The overhead of implementing them separately is relatively low, and for more complex situations, when constraints are necessary, it might not be possible as you would want different types of constraints for each trait.

That being said, you could potentially open an RFC suggesting this be implemented, and then let the community/developers decide whether they find it worth implementing or not.

like image 24
Matthieu M. Avatar answered Oct 19 '22 08:10

Matthieu M.


There's nothing wrong with implementing multiple traits for one type; it's very common in fact.

I also don't understand what you mean by repeating coordinate in the trait and the impl. The function is declared in the trait and implemented in the impl, just like every other trait function. Did you mean that the function's implementation is identical in the Square and Circle impls? Macros would help you there, although there may be a better way.

like image 31
Sebastian Redl Avatar answered Oct 19 '22 09:10

Sebastian Redl