Rahn
Rahn

Reputation: 5405

Default constructor implementation in a trait

Point and Vec2 are defined with the same variable and exactly the same constructor function:

pub struct Point {
    pub x: f32,
    pub y: f32,
}

pub struct Vec2 {
    pub x: f32,
    pub y: f32,
}

impl Point {
    pub fn new(x: f32, y: f32) -> Self {
        Self { x, y }
    }
}

impl Vec2 {
    pub fn new(x: f32, y: f32) -> Self {
        Self { x, y }
    }
}

Is it possible to define a trait to implement the constructor function?

So far I found it only possible to define the interface as the internal variables are not known:

pub trait TwoDimensional {
    fn new(x: f32, y: f32) -> Self;
}

Upvotes: 1

Views: 1667

Answers (1)

cameron1024
cameron1024

Reputation: 10156

You can certainly define such a trait, and implement it for your 2 structs, but you will have to write the implementation twice. Even though traits can provide default implementations for functions, the following won't work:

trait TwoDimensional {
  fn new(x: f32, y: f32) -> Self {
    Self {
      x,
      y,
    }
  }
}

The reason why is fairly simple. What happens if you implement this trait for i32 or () or an enum?

Traits fundamentally don't have information about the underlying data structure that implements them. Rust does not support OOP, and trying to force it often leads to ugly, unidiomatic and less performant code.

If however, you have a bunch of structs and want to essentially "write the same impl multiple times without copy/pasting", a macro might be useful. This pattern is common in the standard library, where, for example, there are certain functions that are implemented for all integer types. For example:

macro_rules! impl_constructor {
  ($name:ty) => {
    impl $name {
      pub fn new(x: f32, y: f32) -> Self {
        Self {
          x, y
        }
      }
    }
  }
}

impl_constructor!(Point);
impl_constructor!(Vec2);

These macros expand at compile time, so if you do something invalid (e.g. impl_constructor!(i32), you'll get a compilation error, since the macro expansion woudl contain i32 { x, y }.

Personally I only use a macro when there is really a large number of types that need an implementation. This is just personal preference however, there is no runtime difference between a hand-written and a macro-generated impl block.

Upvotes: 6

Related Questions