Crate symm_impl[−][src]
Attribute macro that automatically implements a symmetric trait
Motivation
There is a class of binary operators known as symmetric operators.
Formally, let F: D x D -> V
be a binary operator, F
is symmetric if
F(a, b) = F(b, a)
for any (a, b)
.
Such pattern arises naturally in computational geometry. For example,
is_intersected(a: Shape1<Transform>, b: Shape2<Transform>) -> bool
decides whether two transformed shapes intersects. Or
distance(a: Shape1<Transform>, b: Shape2<Transform>) -> f32
computes
the distance between two transformed shapes. Both functions could be seen
as symmetric binary operators. More importantly, the types of the arguments
can be heterogeneous. We can have point.distance(circle)
for example.
It is very tempting to represent an operator with a trait:
trait Distance<Other> { fn distance(&self, other: &Other) -> f64; }
And given different shapes:
struct Point2D { x: f64, y: f64, } struct Disk { center: Point2D, radius: f64 }
We can have
impl Distance<Point2D> for Point2D { fn distance(&self, other: &Point2D) -> f64 { let dx = self.x - other.x; let dy = self.y - other.y; (dx * dx + dy * dy).sqrt() } } impl Distance<Disk> for Point2D { fn distance(&self, other: &Disk) -> f64 { let p_diff = self.distance(&other.center); if p_diff.le(&other.radius) { 0.0_f64 } else { p_diff - other.radius } } }
It is very helpful to also have impl Distance<Point2D> for Disk
, but we
cannot use generic implementation due to conflicting implementation.
// Conflicting implementation because this generic implementation makes // Disk: Distance<Point2D>, which in turn implements // Distance<Disk> for Point2D again. impl<T, U> Distance<U> for T where U: Distance<T>, { fn distance(&self, other: &U) -> f64 { other.distance(self) } }
So one has to manually implement:
impl Distance<Point2D> for Disk { fn distance(&self, other: &Point2D) -> f64 { other.distance(self) } }
This crates tries to address this problem by introducing an attribute macro to automatically implement the symmetric case.
Note
There are several constraints for a trait to be deemed symmetric:
-
The trait must be generic, with the first non-lifetime parameter being the type for the symmetry.
e.g.
trait SymmetricTrait<'a, Other, MoreType> { fn operator(&self, other: &Other) -> MoreType; } trait NotSymmetricTrait<'a, MoreType, Other> { fn operator(&self, other: &Other) -> MoreType; }
-
All the methods in the trait must take exactly 2 arguments, where the first argument is
self
and the other argument is of the type for the symmetry. The two arguments must have the same family in the sense that they should both or neither be reference or mutable.e.g.
trait SymmetricTrait<Other> { fn operator_1(&self, other: &Other) -> SomeType; fn operator_2(self, other: Other) -> SomeType; fn operator_3(&mut self, other: &mut Other) -> SomeType; } trait NotSymmetricTrait<Other> { // reference mismatch fn operator_1(&self, other: Other) -> SomeType; // mutability mismatch fn operator_2(&self, other: &mut Other) -> SomeType; // incorrect arguments order fn operator_3(other: &mut Other, this: &mut Self) -> SomeType; // incorrect number of arguments fn operator_4(&self, other: &Other, more_other: &Other) -> SomeType; }
Associated types in a trait are allowed, and they will be transformed as:
trait TraitWithType<Other> { type SomeType; } impl TraitWithType<B> for A { type SomeType = i32; } // #[symmetric] will expands to impl TraitWithType<A> for B { type SomeType = <A as TraitWithType<B>>::SomeType; }
Example
use symm_impl::symmetric; trait Distance<Other> { fn distance(&self, other: &Other) -> f64; } struct Point2D { x: f64, y: f64, } struct Disk { center: Point2D, radius: f64 } impl Distance<Point2D> for Point2D { fn distance(&self, other: &Point2D) -> f64 { let dx = self.x - other.x; let dy = self.y - other.y; (dx * dx + dy * dy).sqrt() } } #[symmetric] impl Distance<Disk> for Point2D { fn distance(&self, other: &Disk) -> f64 { let p_diff = self.distance(&other.center); if p_diff.le(&other.radius) { 0.0_f64 } else { p_diff - other.radius } } } /* Expands to impl Distance<Point2D> for Disk { #[allow(unused_mut)] #[inline] fn distance(&self, other: &Point2D) -> f64 { <Point2D as Distance<Disk>>::distance(other, self) } } */ let p = Point2D { x: 5.0, y: 4.0 }; let c = Disk { center: Point2D { x: 1.0, y: -2.0 }, radius: 3.0, }; assert_eq!(p.distance(&c), c.distance(&p));
Attribute Macros
symmetric | See module-level documentation |