0

Augmenting Existing Struct APIs with Rust Traits

 1 year ago
source link: http://rtpg.co/2022/12/05/rust-trait-object-apis.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Augmenting Existing Struct APIs with Rust Traits

2022-12-05 • Raphael

What I'm going to describe is just derived from how traits themselvces work, but despite having now written a decent amount of Rust, I had not really fully absorbed this fact.

Imagine you are working with some cell towers owned by various people, in various locations:

struct CellTower {
    id: String;
    location: Location;
    owner_id: String;
}

You might want to do various things with these so you can add an impl block with some helper functions to help make your code legible.

impl CellTower {
  fn distance_from(&self, point: Location): Distance {
     return self.location.distance(point)
  }
}

This lets you do stuff like cell_tower.distance_from(my_house) and other "normal" programming with objects that we're all used to. It is not really different from something like

distance_from(cell_tower, my_house)

But aesthetics really do matter.

You heard about fluent interfaces and want that! You want nice method chaining.

In Python and friends, my strategy might be to create a wrapped container class and build an interface with that.

But here with Rust we actually can totally circumvent this and add extra functionality to existing data structures:

trait FilterableTowerContainer {
  fn owned_by(self, owner_id: String) -> Self
  fn close_to(self, location: Location) -> Self
}

impl FilterableTowerContainer for Vec<CellTower> {
  fn owned_by(self, owner_id: String) -> Self {
     ...
  }
  fn close_to(self, location: Location) -> Self {
    ...
  }
}

fn main() {
    let towers : Vec<CellTower> = ...;
    let my_home: Location = ...;
    let my_favorite_company = ...;
    let usable_towers = towers
      .close_to(my_home)
      .owned_by(my_favorite_company);
    println!(
      "Number of found usable towers: {}",
      usable_towers.len()
    );
}

The interesting bit here being:

let usable_towers = towers
    .close_to(my_home)
    .owned_by(my_favorite_company);

Despite me not having written Vec's implementation, I'm able to "add more methods" to it.

This is all just really how all traits work, but if you're coming from a "objects are instances of classes and have a specificied MRO" world it's very magical!

And it saves you from writing things like:

let usable_towers = owned_by(
  close_to(my_towers, my_home),
  my_favorite_company
)

I'll leave more cursed trait implementation ideas as an exercise to the reader.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK