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

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
}
}

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
}
}

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.

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.

Related

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;
}
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.

Is there a memory efficient way to change the behavior of an inherent implementation?

Is there a memory efficient way to change the behavior on an inherent implementation? At the moment, I can accomplish the change of behavior by storing a number of function pointers, which are then called by the inherent implementation. My difficulty is that there could potentially be a large number of such functions and a large number of objects that depend on these functions, so I'd like to reduce the amount of memory used. As an example, consider the code:
// Holds the data for some process
struct MyData {
x: f64,
y: f64,
fns: MyFns,
}
impl MyData {
// Create a new object
fn new(x: f64, y: f64) -> MyData {
MyData {
x,
y,
fns: CONFIG1,
}
}
// One of our functions
fn foo(&self) -> f64 {
(self.fns.f)(self.x, self.y)
}
// Other function
fn bar(&self) -> f64 {
(self.fns.g)(self.x, self.y)
}
}
// Holds the functions
struct MyFns {
f: fn(x: f64, y: f64) -> f64,
g: fn(x: f64, y: f64) -> f64,
}
// Some functions to use
fn add(x: f64, y: f64) -> f64 {
x + y
}
fn sub(x: f64, y: f64) -> f64 {
x - y
}
fn mul(x: f64, y: f64) -> f64 {
x * y
}
fn div(x: f64, y: f64) -> f64 {
x / y
}
// Create some configurations
const CONFIG1: MyFns = MyFns {
f: add,
g: mul,
};
const CONFIG2: MyFns = MyFns {
f: sub,
g: div,
};
fn main() {
// Create our structure
let mut data = MyData::new(1., 2.);
// Check our functions
println!(
"1: x={}, y={}, foo={}, bar={}",
data.x,
data.y,
data.foo(),
data.bar()
);
// Change the functions
data.fns = CONFIG2;
// Print the functions again
println!(
"2: x={}, y={}, foo={}, bar={}",
data.x,
data.y,
data.foo(),
data.bar()
);
// Change a single function
data.fns.f = add;
// Print the functions again
println!(
"3: x={}, y={}, foo={}, bar={}",
data.x,
data.y,
data.foo(),
data.bar()
);
}
This code allows the behavior of foo and bar to be changed by editing f and g. However, it also not flexible. I'd rather use a boxed trait object Box<dyn Fn(f64,f64)->f64, but then I can't create some default configurations like CONFIG1 and CONFIG2 because Box can not be used to create a constant object. In addition, if we have a large number of functions and objects, I'd like to share the memory for their implementation. For function pointers, this isn't a big deal, but for closures it is. Here, we can't create a constant Rc for the configuration to share the memory. Finally, we could have a static reference to a configuration, which would save memory, but then we could not change the individual functions. I'd rather we have a situation where most of the time we share memory for the functions, but have the ability hold its own memory and change the functions if desired.
I'm open to a better design if one is available. Ultimately, I'd like to change the behavior of foo and bar at runtime based on a function held, in some form or another, inside of MyData. Further, I'd like a way to do so where the memory is shared when possible and we have the ability to change an individual function and not just the entire configuration.
A plain dyn reference will work here - it allows references to objects that have a certain trait but with type known only at runtime.
(This is exactly what you want for function pointers. Think of it as each function having its own special type, but falling under a trait like Fn(f64,f64)->f64.)
So your struct could be defined as:
struct MyData<'a> {
x: f64,
y: f64,
f: &'a dyn Fn(f64, f64) -> f64,
g: &'a dyn Fn(f64, f64) -> f64,
}
(Notice, you need the lifetime specifier 'a to ensure the the lifetime of that references is not shorter than the struct itself.)
Then your impl could be like:
impl<'a> MyData<'a> {
// Create a new object
fn new(x: f64, y: f64) -> Self {
MyData {
x,
y,
f: &add, // f and g as in CONFIG1
g: &mul,
}
}
fn foo(&self) -> f64 {
(self.f)(self.x, self.y)
}
// etc...
}
Depending on how you want the default configurations to work, you could either make them as more inherent functions such as fn to_config2(&mut self); or you could make a separate struct just with the function pointers and then have a function to copy those function pointers into the MyData struct.

i need to push (Vec) of a member of struct contained, he is a struct as well

I am trying to add a member of a structure that is itself a structure. I have the classic mistake "can not move out of borrowed content".
How can I get around the problem
thanks in advance.
use std::cell::RefCell;
pub struct Sprite {
pub x: f32,
pub y: f32,
}
impl Sprite {
pub fn new(x: f32, y: f32) -> Sprite {
let sprite: Sprite = Sprite { x: x, y: y };
sprite
}
}
pub struct Human {
pub x: f32,
pub y: f32,
pub sprite: Sprite,
}
impl Human {
pub fn new() -> Human {
Human {
x: 400.0,
y: 300.0,
sprite: Sprite::new(1.0, 1.0),
}
}
}
pub struct Game {
pub human: Human,
sprites: Vec<RefCell<Sprite>>,
}
impl Game {
pub fn new() -> Game {
let human = Human::new();
Game {
human: human,
sprites: vec![],
}
}
pub fn init(&mut self) {
let sprite = self.human.sprite; //error : can not move out of borrowed content
self.create_sprite(sprite);
}
fn create_sprite(&mut self, sprite: Sprite) {
self.sprites.push(RefCell::new(sprite));
}
}
fn main() {}
I made the change proposed by RLS, which only displaced the problem.
I also tried to change the "lifetime" with annotations, it did not work either, but maybe I am wrong. I do not know this feature well.
REM : The code is purge for shows the error and compiled
Right, so your:
let sprite = human.sprite
Attempts to take ownership of the sprite field away from the human it is defined in.
This is prohibited in rust since it would leave a dangling reference in the struct if the original reference is destroyed, or double references if copied. Both unsafe.
Using a borrow allows simpler code semantics but borrows are supposed to have a specific lifetime, i.e. in general not stick around since the thing you are borrowing from might outlive the borrow reference otherwise.
The final option that's typically used is just copying the data, but since it seems you want to track the full state of your sprites from several places that wouldn't work here. Copying data would not leave a reference to the original.
There are ways around this in Rust.
So, since it seems to me you want to be able to reference the same Sprite-struct from two locations you need a lot of wrapping here.
I´ve added reference counting to your RefCell, this Rc wrapper can then be cloned and kept as a reference to the original Struct in several places.
The RefCell then provides the actual read-write "lock" to allow the data to be mutated from several places.
Have a look below and see if this brings you closer to your usecase:
use std::rc::Rc;
use std::cell::RefCell;
pub struct Sprite {
pub x: f32,
pub y: f32,
}
impl Sprite {
pub fn new(x: f32, y: f32) -> Sprite {
let sprite: Sprite = Sprite { x: x, y: y };
sprite
}
}
pub struct Human {
pub x: f32,
pub y: f32,
pub sprite: Rc<RefCell<Sprite>>,
}
impl Human {
pub fn new() -> Human {
Human {
x: 400.0,
y: 300.0,
sprite: Rc::new(RefCell::new(Sprite::new(1.0, 1.0))),
}
}
}
pub struct Game {
pub human: Human,
sprites: Vec<Rc<RefCell<Sprite>>>,
}
impl Game {
pub fn new() -> Game {
let human = Human::new();
Game {
human: human,
sprites: vec![],
}
}
pub fn init(&mut self) {
let sprite = self.human.sprite.clone(); //error : can not move out of borrowed content
self.create_sprite(sprite);
}
fn create_sprite(&mut self, sprite: Rc<RefCell<Sprite>>) {
self.sprites.push(sprite);
}
}
fn main() {}

Most idiomatic way to create a default struct

To create a default struct, I used to see fn new() -> Self in Rust, but today, I discovered Default. So there are two ways to create a default struct:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new() -> Self {
Point {
x: 0,
y: 0,
}
}
}
impl Default for Point {
fn default() -> Self {
Point {
x: 0,
y: 0,
}
}
}
fn main() {
let _p1 = Point::new();
let _p2: Point = Default::default();
}
What is the better / the most idiomatic way to do so?
If you had to pick one, implementing the Default trait is the better choice to allow your type to be used generically in more places while the new method is probably what a human trying to use your code directly would look for.
However, your question is a false dichotomy: you can do both, and I encourage you to do so! Of course, repeating yourself is silly, so I'd call one from the other (it doesn't really matter which way):
impl Point {
fn new() -> Self {
Default::default()
}
}
Clippy even has a lint for this exact case!
I use Default::default() in structs that have member data structures where I might change out the implementation. For example, I might be currently using a HashMap but want to switch to a BTreeMap. Using Default::default gives me one less place to change.
In this particular case, you can even derive Default, making it very succinct:
#[derive(Default)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new() -> Self {
Default::default()
}
}
fn main() {
let _p1 = Point::new();
let _p2: Point = Default::default();
}

How to share functionality in Rust?

struct Vector {
data: [f32; 2]
}
impl Vector {
//many methods
}
Now I want to create a Normal which will almost behave exactly like a Vector but I need to differentiate the type. Because for example transforming a normal is different than transforming a vector. You need to transform it with the tranposed(inverse) matrix for example.
Now I could do it like this:
struct Normal {
v: Vector
}
And then reimplement all the functionality
impl Normal {
fn dot(self, other: Normal) -> f32 {
Vector::dot(self.v, other.v)
}
....
}
I think I could also do it with PhantomData
struct VectorType;
struct NormalType;
struct PointType;
struct Vector<T = VectorType> {
data: [f32; 2],
_type: PhantomData<T>,
}
type Normal = Vector<NormalType>;
But then I also need a way to implement functionality for specific types.
It should be easy to implement for example add for everything so that it is possible to add point + vector.
Or functionality specific to some type
impl Vector<NormalType> {..} // Normal specific stuff
Not sure how I would implement functionality for a subrange. For example maybe the dot product only makes sense for normals and vectors but not points.
Is it possible to express boolean expression for trait bounds?
trait NormalTrait;
trait VectorTrait;
impl NormalTrait for NormalType {}
impl VectorTrait for VectorType {}
impl<T: PointTrait or VectorTrait> for Vector<T> {
fn dot(self, other: Vector<T>) -> f32 {..}
}
What are my alternatives?
Your question is pretty broad and touches many topics. But your PhantomData idea could be a good solution. You can write different impl blocks for different generic types. I added a few things to your code:
struct VectorType;
struct NormalType;
struct PointType;
struct Vector<T = VectorType> {
data: [f32; 2],
_type: PhantomData<T>,
}
type Normal = Vector<NormalType>;
type Point = Vector<PointType>;
// --- above this line is old code --------------------
trait Pointy {}
impl Pointy for VectorType {}
impl Pointy for PointType {}
// implement for all vector types
impl<T> Vector<T> {
fn new() -> Self {
Vector {
data: [0.0, 0.0],
_type: PhantomData,
}
}
}
// impl for 'pointy' vector types
impl<T: Pointy> Vector<T> {
fn add<R>(&mut self, other: Vector<R>) {}
fn transform(&mut self) { /* standard matrix multiplication */ }
}
// impl for normals
impl Vector<NormalType> {
fn transform(&mut self) { /* tranposed inversed matrix stuff */ }
}
fn main() {
let mut n = Normal::new();
let mut p = Point::new();
n.transform();
p.transform();
// n.add(Normal::new()); // ERROR:
// no method named `add` found for type `Vector<NormalType>` in the current scope
p.add(Point::new());
}
Is it possible to express boolean expression for trait bounds?
No (not yet). But you can solve it in this case as shown above: you create a new trait (Pointy) and implement it for the types in your "or"-condition. Then you bound with that trait.

Resources