0

I am trying to define a trait for algorithm that returns a struct that reference multiple internal fields after each step in the algorithm. Then the calling code can optionally choose to serialize the state information or maybe display it. I want to return this as references so that if the calling code doesn't use it, there is not hit to performance (the internal state could be very large).

Below is what I think I want, but instead I want it to be generic of an Algorithm trait and then implementing the Algorithm trait for FooAlgorithm, or others.

use serde::{Deserialize, Serialize}; // 1.0.125

struct FooStateRef<'a> {
    field_0: &'a usize,
    field_1: &'a usize,
}

impl<'a> Clone for FooStateRef<'a> {
    fn clone(&self) -> Self {
        FooStateRef {
            field_0: self.field_0,
            field_1: self.field_1,
        }
    }
}

impl<'a> Copy for FooStateRef<'a> {}

#[derive(Debug, Serialize, Deserialize)]
struct FooState {
    field_0: usize,
    field_1: usize,
}

struct FooAlgorithm {
    field_0: usize, // Don't want to clone unless the calling code wants to serialize
    field_1: usize, // Don't want to clone unless the calling code wants to serialize
    field_2: usize, // No need to serialize
}

impl<'a> From<FooStateRef<'a>> for FooState {
    fn from(state: FooStateRef) -> FooState {
        FooState {
            field_0: state.field_0.clone(),
            field_1: state.field_1.clone(),
        }
    }
}

impl FooAlgorithm {
    fn step(&mut self) -> Option<FooStateRef> {
        self.field_1 += self.field_0;
        self.field_2 += self.field_1;
        self.field_0 += self.field_2;

        if self.field_2 > 100 {
            Some(FooStateRef {
                field_0: &self.field_0,
                field_1: &self.field_1,
            })
        } else {
            None
        }
    }
}

fn main() {
    let mut algo = FooAlgorithm {
        field_0: 1,
        field_1: 1,
        field_2: 1,
    };

    let mut count = 0;
    loop {
        match algo.step() {
            Some(state_ref) => {
                if count % 10 == 0 {
                    // Save state to file
                }
            }
            None => break,
        }
        count += 1;
    }
}

My attempt at making the Algorithm trait is here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5a36c6eb121f0724284bf7d5ec5a8cad

The issue I'm running into is with lifetimes. How do I write the function signature in the algorithm trait definition so that I can return a FooStateRef<'a> when I implement Algorithm for FooAlgorithm, but also let me return BarStateRef<'a> when I implement Algorithm for BarAlgorithm?

Another thing I have looked into is associated types, but from what I can tell, I would need Generic Associated Traits to add something like type AlgoStateRef<'a>: StateRef<'a>; to the Algorithm trait definition.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • Does this answer your question? [When is it appropriate to use an associated type versus a generic type?](https://stackoverflow.com/questions/32059370/when-is-it-appropriate-to-use-an-associated-type-versus-a-generic-type) – Jmb Apr 29 '21 at 06:56
  • Not quite, those answers do help me understand the different between associated types and generic types better, but I'm mainly looking for help on how to describe lifetimes correctly for this case. In my question I show the interface I want implemented on the one struct, but now I just want to abstract that interface into an Algorithm trait. – Dylan Staatz Apr 29 '21 at 16:29
  • My point was that the return type for `Algorithm::step` should almost certainly be an associated type, not a type parameter, and that should make the lifetimes much easier to determine. – Jmb Apr 30 '21 at 16:51

0 Answers0