sunjay / turtle

Create Animated Drawings in Rust
http://turtle.rs
Mozilla Public License 2.0
562 stars 53 forks source link

Consider using a Point struct instead of [f64; 2] #18

Closed sunjay closed 6 years ago

sunjay commented 7 years ago

The main benefit of using the current type alias is the simple syntax, however it creates a lot of confusion because it forces us to then explain things like arrays, indexing and the "orphan rule" (when talking about random values) just to use points. It also isn't particularly expressive. For example:

// We have to access components of the point like so:
let x = pt[0]; // requires explaining arrays and indexing to use
let y = pt[1];
// Instead of by name:
let x = pt.x;
let y = pt.y;

Implementing our own type gives us the freedom to implement any traits for it however we want. We can provide a better random implementation or even implement methods on it for different situations that come up. Overall, it's more forwards compatible to use our own type. We can implement different vector operations on it like multiplying/dividing by a constant (this is helpful in a number of situations), adding points, etc.

We can actually even keep the convenient syntax by changing the few functions that take Point to take Into<Point> instead. This is a small change that affects barely any code since points are used so infrequently.

Sample Implementation

The implementation below can be

use std::ops::{Add, Sub, Mul, Div, Index, IndexMut};

#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
    pub x: f64,
    pub y: f64,
}

impl Point {
    pub fn is_finite(&self) -> bool {
        self.x.is_finite() && self.y.is_finite()
    }
}

impl From<(f64, f64)> for Point {
    fn from(pt: (f64, f64)) -> Self {
        Point {x: pt.0, y: pt.1}
    }
}

impl From<[f64; 2]> for Point {
    fn from(pt: [f64; 2]) -> Self {
        Point {x: pt[0], y: pt[1]}
    }
}

impl From<Point> for [f64; 2] {
    fn from(pt: Point) -> Self {
        [pt.x, pt.y]
    }
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Self) -> Self::Output {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

impl Sub for Point {
    type Output = Point;

    fn sub(self, other: Self) -> Self::Output {
        Point {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}

impl Mul<f64> for Point {
    type Output = Point;

    fn mul(self, other: f64) -> Self::Output {
        Point {
            x: self.x * other,
            y: self.y * other,
        }
    }
}

impl Div<f64> for Point {
    type Output = Point;

    fn div(self, other: f64) -> Self::Output {
        Point {
            x: self.x / other,
            y: self.y / other,
        }
    }
}

impl Index<usize> for Point {
    type Output = f64;

    fn index(&self, index: usize) -> &Self::Output {
        match index {
            0 => &self.x,
            1 => &self.y,
            _ => panic!("Invalid coordinate for Point: {}", index),
        }
    }
}

impl IndexMut<usize> for Point {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        match index {
            0 => &mut self.x,
            1 => &mut self.y,
            _ => panic!("Invalid coordinate for Point: {}", index),
        }
    }
}