Associated Methods

Associated Methods are associated functions which are called on a particular instance of a type

Associated functions are functions that are defined on a type. Let us look at example below. Origin and new are Associated methods on Coordinate. Associated functions are called using double colons.

struct Coordinate {
    x: f64,
    y: f64,
}


impl Coordinate {
    fn origin() -> Coordinate {
        Coordinate { x: 0.0, y: 0.0 }
    }

    fn new(x: f64, y: f64) -> Coordinate {
        Coordinate { x: x, y: y }
    }
}

struct Square {
    p1: Coordinate,
    p2: Coordinate,
}

impl Square {
    fn area(&self) -> f64 {
        let Coordinate { x: x1, y: y1 } = self.p1;
        let Coordinate { x: x2, y: y2 } = self.p2;

        ((x1 - x2) * (y1 - y2)).abs()
    }

    fn perimeter(&self) -> f64 {
        let Coordinate { x: x1, y: y1 } = self.p1;
        let Coordinate { x: x2, y: y2 } = self.p2;

        2.0 * ((x1 - x2).abs() + (y1 - y2).abs())
    }

    fn translate(&mut self, x: f64, y: f64) {
        self.p1.x += x;
        self.p2.x += x;

        self.p1.y += y;
        self.p2.y += y;
    }
}

struct Pair(Box<i32>, Box<i32>);

impl Pair {
    fn destroy(self) {
        let Pair(first, second) = self;

        println!("Destroying Pair({}, {})", first, second);

    }
}

fn main() {
    let rectangle = Square {
        
        p1: Coordinate::origin(),
        p2: Coordinate::new(3.0, 4.0),
    };


    println!("Square perimeter: {}", rectangle.perimeter());
    println!("Square area: {}", rectangle.area());

    let mut square = Square {
        p1: Coordinate::origin(),
        p2: Coordinate::new(1.0, 1.0),
    };

    
    square.translate(1.0, 1.0);

    let pair = Pair(Box::new(1), Box::new(2));

    pair.destroy();


}

Iterators

There are actually different ways in Rust to create iterators from types. While the IntoIterator and its into_iter() method are mostly called implicitly when we use forloops, iter() and iter_mut() methods are often provided by collection types to create iterators explicitly. There’s no trait that provides iter() and iter_mut(), so it’s more of a convention that collection types may implement these methods. 

The example from above can then be written as follows:

let employees = vec!["John", "Elvis", "David", "Calvin"];

let mut iterator_emp = (employees).iter(); 

println!("{}", iterator_emp.next().unwrap());
println!("{}", iterator_emp.next().unwrap());
println!("{}", iterator_emp.next().unwrap());
println!("{}", iterator_emp.next().unwrap());

must_use Annotation

#must_use Annotation helps in lifting the code and make it readable to the developer.

Rust stdlib uses #must_use Annotation for warnings and catching errors during development and compilation of the code. If you annotate using this annotation, values of that type should be used. Otherwise, it will flag a warning. Futures is another example where it is created but not waited then it raises a warning.

Error Management in Rust

Java and other languages have error management. Rust has following unique features in Rust:

  • Recoverable/ “true” errors – Result enum is the key one which helps in recoverable error management. Errors can bubble up
  • Non-recoverable / “exceptions” – Panics and UnWraps support non recoverable error management. Stopping the whole program or gracefully exit the application is the only option