ruk·si

🦀 Rust
Traits

Updated at 2024-01-04 06:26

Use structs over creating new traits. Traits are for library code you expose. Like interfaces in many languages; if you have three or more structs that all need to implement an interface, you might consider introducing custom traits. You wouldn't want your Rust to feel like Java, would you?

Implementing traits is essential, introducing new traits is not.

Traits are used to signify something that multiple types have in common.

trait Signed {
    fn is_positive(self) -> bool;
}

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

impl Signed for Vec2 {
    fn is_positive(self) -> bool {
        self.x > 0.0 && self.y > 0.0
    }
}

impl Signed for i32 {
    fn is_positive(self) -> bool {
        self > 0
    }
}

fn main() {
    let v1 = Vec2 { x: 1.0, y: 3.0 };
    let v2 = Vec2 { x: -2.0, y: 1.5 };
    let i1: i32 = 10;
    assert_eq!(v1.is_positive(), true);
    assert_eq!(v2.is_positive(), false);
    assert_eq!(i1.is_positive(), true);
}

You can implement foreign traits. impl block is always for a specific type, so Self means the type itself.

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

impl std::ops::Neg for Vec2 {
    type Output = Self;

    fn neg(self) -> Self {
        Self {
            x: -self.x,
            y: -self.y,
        }
    }
}

fn main() {
    let v1 = Vec2 { x: 1.0, y: 3.0 };
    let v2 = -v1;
    assert_eq!(v2.x, -1.0);
    assert_eq!(v2.y, -3.0);
}

Trait methods can take self by reference or mutable reference.

impl std::clone::Clone for Vec2 {
    fn clone(&self) -> Self {
        Self { ..*self }
    }
}

Marker traits don't define implementation but say that something is allowed.

// "copying the value is allowed" (also requires `Clone` to be implemented)
impl std::marker::Copy for Vec2 {}

derive attribute can be used to automatically implement traits. If a derive macro has been implemented for that trait, that is.

#[derive(Clone, Copy)]
struct Vec2 {
    x: f64,
    y: f64,
}

Some common traits in the standard library:

  • Copy: can be duplicated with an always efficient bitwise copy
  • Clone: can be duplicated with a potentially expensive deep copy
  • Default: the type has a default value
  • Hash: can be mapped to a value of sized size by a hash function
  • Deref: make the struct behave like a reference to something else that it actually is
  • Drop: what to do when the instance goes out-of-scope; e.g., close file or connection

Ordering

  • Ord: can always be ordered
  • PartialOrd: can be ordered, but there might be corner cases

When derived on structs, it will produce a lexicographic ordering based on the top-to-bottom declaration order of the struct’s members.

Equality

  • Eq: can be compared and x==x is always true
  • PartialEq: can be compared but x==x might not always be true

For example, floating points are only PartialEq because NaN != NaN.

Sources