In our application we would like to be able to do some computation. E.g. we would like to calculate the area of a rectangle.
We see 4 different implementations
Simple function
The most simple way is to implement a function that will receive the width and length of the rectangle and return the area.
Besides calling the function area
there is nothing special about this function. It only multiplies two numbers.
The two parameters can be any two numbers (of the given type). They might be named width and length, but nothing really indicates that are measures of a rectangle.
This is a very generic solution, but does not convey much meaning.
examples/area-function/src/lib.rs
pub fn area(width: u64, length: u64) -> u64 {
width * length
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = area(2, 3);
assert_eq!(result, 6);
}
}
Struct with external function
A better representation might be to define a struct
called Rectangle that has two attributes for width and length.
We could then call the area function from the previous implementation like this:
let r = Rectangle { width: 2, length: 3 };
let result = area(r.width, r.length);
It is probably cleared if the area function receives an instance of Rectangle
(or better yet a reference to an instance)
examples/area-struct-and-function/src/lib.rs
#![allow(dead_code)]
struct Rectangle {
width: u64,
length: u64,
}
fn area(rect: &Rectangle) -> u64 {
rect.width * rect.length
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let r = Rectangle { width: 2, length: 3 };
let result = area(&r);
assert_eq!(result, 6);
}
}
This brings us to a generic function floating in our namespace with a very specific application. It can only be used with a Rectangle.
Struct with method
Instead of defining a function outside of the struct we could define it as a method of the Rectangle
struct.
We need to wrap the function definition it with impl Rectangle { }
and replace the parameter that was rect: &Rectangle
by &self
which is the generic representation of an instance of the current struct.
Also in the test just as in general use-case, instead of passing in a reference to the function area(&r)
, we use the dot-notation to call it as a method: r.area()
.
examples/area-struct-and-method/src/lib.rs
#![allow(dead_code)]
struct Rectangle {
width: u64,
length: u64,
}
impl Rectangle {
fn area(&self) -> u64 {
self.width * self.length
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let r = Rectangle { width: 2, length: 3 };
let result = r.area();
assert_eq!(result, 6);
}
}
Struct and Trait
To allow for easy generalization of the area
method we could define a Trait
that defined the signature of the area function:
It receives a reference to an instance of the current struct and returns a u64
.
pub trait Area {
fn area(&self) -> u64;
}
Then we implement the trait using the impl Area for Rectangle
expression instead of the impl Rectangle
expression we had earlier.
The rest of the code does not need to change, but now we defined the idea of having a method called area
which we'll be able to implement
for any shape and convey the message that thy have something common.
examples/area-struct-and-trait/src/lib.rs
#![allow(dead_code)]
struct Rectangle {
width: u64,
length: u64,
}
pub trait Area {
fn area(&self) -> u64;
}
impl Area for Rectangle {
fn area(&self) -> u64 {
self.width * self.length
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let r = Rectangle { width: 2, length: 3 };
let result = r.area();
assert_eq!(result, 6);
}
}