# 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,
}```

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);
0.0_f64
} else {
}
}
}```

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,
}
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);
0.0_f64
} else {
}
}
}
/* 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 },