How to specify lifetimes for trait with borrowed data

In the code below I define Analyzer trait which takes ownership of its input data. Ideally, the Analyzer-objects should just hold a reference to data. How do I achieve this?


trait Analyzer {
    fn new(data: Vec<usize>) -> Self;
    fn analyze(self, p: usize) -> usize;
}

struct BruteAnalyzer {
    data: Vec<usize>,
}

impl Analyzer for BruteAnalyzer {
    fn new(data: Vec<usize>) -> Self {
        Self { data }
    }
    fn analyze(self, p: usize) -> usize {
        self.data.iter().map(|v| v + p).sum()
    }
}

I have tried starting with the following, but I am clearly missing a deeper understanding of how the lifetimes are resolved when traits are involved.

trait Analyzer <'a> {
    fn new<'b:'a>(data: &'b Vec<usize>) -> Self;
    fn analyze(&self, p: usize) -> usize;
}

Intended use is along these lines:

fn run_analyzer<T>(p: usize) -> usize
where
    T: Analyzer,
{
    let data: Vec<usize> = (0..10).collect();
    let analyzer = T::new(data);
    analyzer.analyze(p)
}

fn main() {
    assert_eq!(run_analyzer::<BruteAnalyzer>(3), 45+30);
}

(Playground)

Would this work for you? Playground.

2 Likes

The difficulty is that the lifetime of the local borrow cannot be named by callers of the function. So if your trait is parameterized by the lifetime, a caller cannot satisfy the trait bound unless it implements the trait for all lifetimes. But your borrowing structs do not (that's the point of having the lifetime parameter in the first place).

What you need instead is the ability to create an analyzing type for any lifetime. That is, you can split the trait in two: a construction trait that some standin struct can implement, and the actual analyzing trait that your borrowing struct implements.

Alternatively, just take a reference to the data and analyze it in one go. Construction (of Self) methods aren't common on traits.

2 Likes

To some degree -- and it was very helpful for me to understand the idea of using a separate "Data" type. Thank you.

I was maybe too sparse on my use case: I am comparing different analyzers with various degrees of preprocessing (in the new-method). Some of these will need complex data structures instead of &[usize], so an approach as suggested by @quinedot with an explicit separation into Analyze and Analyzer traits seems the way ahead.

Mostly to help future me and others who are googling for this: The solution offered by quinedot can be thought of as an example of the "factory design pattern". Here is a solution with identifiers changed to clarify this:

Also, quinedot has a helpful write-up on the reason for the construction

fn new(data: &[usize]) -> Self::Holder<'_>
1 Like