#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Point {
Point { x: x, y: y }
}
}
fn main() {
let my_point = Point::new(1, 2);
println!("My point being: {:?}", my_point);
}
Rust offers the possibility to bind functions to types.
This sometimes looks like object-oriented programming, but it is not.
In particular, run-time polymorphism, messages, classes, subtypes, and method overload are missing.
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Point {
Point { x: x, y: y }
}
}
fn main() {
let my_point = Point::new(1, 2);
println!("My point being: {:?}", my_point);
}
new
here is purely convention.
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Point {
Point { x: x, y: y }
}
fn from_pair(pair: (i32, i32)) -> Point {
Point {
x: pair.0,
y: pair.1,
}
}
fn into_pair(self) -> (i32, i32) {
(self.x, self.y)
}
fn inspect(&self) {
println!("Current point value: {:?}", self);
}
fn move_to(&mut self, x: i32, y: i32) {
self.x = x;
self.y = y;
}
fn x(&self) -> &i32 {
&self.x
}
fn x_mut(&mut self) -> &mut i32 {
&mut self.x
}
fn y(&self) -> &i32 {
&self.y
}
fn y_mut(&mut self) -> &mut i32 {
&mut self.y
}
}
fn main() {
let mut my_point = Point::new(1, 2);
my_point.inspect();
my_point.move_to(2, 3);
my_point.inspect();
let x = my_point.x_mut();
*x = 5;
my_point.inspect();
}
self
It is like normal ownership and borrowing, but at the beginning somewhat unfamiliar.
Borrowing through one function simultaneously borrows self.
This is especially applicable for mutable borrows!
self
without &
takes ownership to the value from the calling context.
self
Owned | Borrowed | Mutably borrowed |
---|---|---|
self | &self | &mut self |
Values can be replaced when calling &mut
functions
Values, for example iterators and builders, can have methods that consume self
and are thus invalidated.
This solves the problem of invalidating iterators!
Implementations can occur multiple times. This is useful when multiple constraints are needed.
Traits are Rust’s particular way of abstracting over types.
We’ve already met a trait: Debug
.
Traits define functions types must implement. They can then be used generically.
struct Point {
x: i32,
y: i32
}
trait Distance {
fn distance(&self, other: &Self) -> f64;
}
impl Distance for Point {
fn distance(&self, other: &Point) -> f64 {
(((other.x - self.x).pow(2) + (other.y - self.y).pow(2)) as f64).sqrt()
}
}
fn main() {
let p1 = Point { x: 1, y: 1 };
let p2 = Point { x: 2, y: 2 };
println!("{}", p1.distance(&p2));
}
Self
is a special type: it is the type currently being implemented.
Traits can have type parameters.
struct Point {
x: i32,
y: i32
}
trait Distance<OtherShape> {
fn distance(&self, other: &OtherShape) -> f64;
}
impl Distance<Point> for Point {
fn distance(&self, other: &Point) -> f64 {
(((other.x - self.x).pow(2) + (other.y - self.y).pow(2)) as f64).sqrt()
}
}
fn main() {
let p1 = Point { x: 1, y: 1 };
let p2 = Point { x: 2, y: 2 };
println!("{}", p1.distance(&p2));
}
Working with generic traits is very common.
Type inference of traits is very advanced, but sometimes, undecidable situations can occur. In this case, the compiler needs help deciding.
There are multiple techniques.
struct Point {
x: i32,
y: i32
}
trait Distance<OtherShape> {
fn distance(&self, other: &OtherShape) -> f64;
}
impl Distance<Point> for Point {
fn distance(&self, other: &Point) -> f64 {
(((other.x - self.x).pow(2) + (other.y - self.y).pow(2)) as f64).sqrt()
}
}
fn main() {
let p1 = Point { x: 1, y: 1 };
let p2 = Point { x: 2, y: 2 };
println!("{}", <Point as Distance<Point>>::distance(&p1, &p2));
}
Any reachable function in Rust can be addressed with this syntax.
Associated types are generic parameters, but they are ignored during inference.
#[derive(Debug)]
struct Point {
x: i32,
y: i32
}
trait MyAddition<Other> {
type Output;
fn add(&self, other: &Other) -> Self::Output;
}
impl MyAddition<Point> for Point {
type Output = Point;
fn add(&self, other: &Point) -> Point {
Point { x: self.x + other.x, y: self.y + other.y }
}
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
println!("{:?}", p1.add(&p2))
}
impl Trait
impl Trait
is used when the type of a value does not need to be named.
fn main() {
let v = vec![1,2,3];
let i = make_iter(&v);
}
fn make_iter<'a>(v: &'a Vec<u8>) -> impl Iterator<Item=u8> + 'a {
v.iter().map(|v| (*v)*2)
}
fn main() {
let v = vec![1,2,3];
let i = v.iter();
let i2 = double(i);
}
fn double<'a>(i: impl Iterator<Item=&'a u8> + 'a) -> impl Iterator<Item=u8> + 'a {
i.map(|v| (*v)*2)
}
Limitations:
No impl Trait
in trait methods
trait Foo {}
trait Bar {
fn fooify(&self) -> impl Foo;
}