Example

  1. #[derive(Debug)]
  2. struct Race {
  3. name: String,
  4. laps: Vec<i32>,
  5. }
  6. impl Race {
  7. fn new(name: &str) -> Race { // No receiver, a static method
  8. Race { name: String::from(name), laps: Vec::new() }
  9. }
  10. fn add_lap(&mut self, lap: i32) { // Exclusive borrowed read-write access to self
  11. self.laps.push(lap);
  12. }
  13. fn print_laps(&self) { // Shared and read-only borrowed access to self
  14. println!("Recorded {} laps for {}:", self.laps.len(), self.name);
  15. for (idx, lap) in self.laps.iter().enumerate() {
  16. println!("Lap {idx}: {lap} sec");
  17. }
  18. }
  19. fn finish(self) { // Exclusive ownership of self
  20. let total = self.laps.iter().sum::<i32>();
  21. println!("Race {} is finished, total lap time: {}", self.name, total);
  22. }
  23. }
  24. fn main() {
  25. let mut race = Race::new("Monaco Grand Prix");
  26. race.add_lap(70);
  27. race.add_lap(68);
  28. race.print_laps();
  29. race.add_lap(71);
  30. race.print_laps();
  31. race.finish();
  32. // race.add_lap(42);
  33. }

Key Points:

  • All four methods here use a different method receiver.
    • You can point out how that changes what the function can do with the variable values and if/how it can be used again in main.
    • You can showcase the error that appears when trying to call finish twice.
  • Note that although the method receivers are different, the non-static functions are called the same way in the main body. Rust enables automatic referencing and dereferencing when calling methods. Rust automatically adds in the &, *, muts so that that object matches the method signature.
  • You might point out that print_laps is using a vector that is iterated over. We describe vectors in more detail in the afternoon.