0

I have a struct which contains some stuff. I implement the Iterator trait for that struct, and return a tuple of references to internal data in the struct. That necessitates that I annotate at least some things with lifetimes. What I want is to minimize the lifetime annotation, especially when it comes to other structs which have the original struct as a member.

some code:

pub struct LogReader<'a> {
    data:String,
    next_fn:fn(&mut LogReader)->Option<(&'a str,&'a [ConvertedValue])>,
//...
}

pub struct LogstreamProcessor {
    reader: LogReader, // doesn't work without polluting LogstreamProcessor with lifetimes
//...
}

impl<'a> Iterator for LogReader<'a > {
    type Item = (&'a str,&'a[ConvertedValue]);

    fn next(&mut self) -> Option<(&'a str,&'a[ConvertedValue])>{(self.next_fn)(self)}

}

impl <'a> LogReader<'a> {
    pub fn new(textFile:Option<bool>) -> LogReader<'a> {
        LogReader {
            next_fn:if textFile.unwrap_or(false) { LogReader::readNextText }else{ LogReader::readNextRaw },
            data: "blah".to_string()
        }
    }

    fn readNextText(&mut self)->Option<(&str,&[ConvertedValue])>{unimplemented!();}
    fn  readNextRaw(&mut self)->Option<(&str,&[ConvertedValue])>{unimplemented!();}
}
Camden Narzt
  • 2,271
  • 1
  • 23
  • 42
  • "and return a tuple of references to internal data in the struct" Are you sure you don't want a method, rather than a function object in a field? Right now you're using `next_fn` as if it was a method. – E_net4 May 26 '17 at 21:46
  • The field simply holds a pointer to a method on the object. It's not nice, but I'm porting python so there's a lot that doesn't translate nicely. – Camden Narzt May 26 '17 at 21:48
  • Methods from Python do translate nicely... into [methods](https://doc.rust-lang.org/book/method-syntax.html). – E_net4 May 26 '17 at 21:51
  • yeah but the original python uses a pointer to a method to decide which method to call, so this does the same. Anyway I don't see how this is relevant to the question I asked. – Camden Narzt May 26 '17 at 21:54
  • It matters because you might end up with lifetime conflicts. Can you add a minimal example of a function that you would feed such an object with? – E_net4 May 26 '17 at 22:29
  • Ok so I added the general mechanism for choosing a function. I suppose I could just keep the bool and use a conditional in the `next` function, but that doesn't change the fact that the return type has references and so lifetime annotations come into it. – Camden Narzt May 26 '17 at 23:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/145246/discussion-between-camden-narzt-and-e-net4). – Camden Narzt May 26 '17 at 23:55

1 Answers1

1

Can I limit the lifetime pollution from a struct?

Generically, if you're using them in any of your struct's fields, then you can't. They are made explicit for very good reasons (see Why are explicit lifetimes needed in Rust?), and once you have a struct containing objects that require explicit lifetimes, then they must be propagated.

Note that usually this isn't a concern to consumers of the struct, since the concrete lifetimes are then imposed by the compiler:

struct NameRef<'a>(&'a str);

let name = NameRef("Jake"); // 'a is 'static

One could also slightly mitigate the "noise" on the implementation of next by using the definition of Self::Item.

impl<'a> Iterator for LogReader<'a > {
    type Item = (&'a str,&'a[ConvertedValue]);

    fn next(&mut self) -> Option<Self::Item> {
        (self.next_fn)(self)
    }
}

However, your concern actually hides a more serious issue: Unlike you've mentioned, the values returned from next are not necessarily internal data from the struct. They actually live for as long as the generic lifetime 'a, and nothing inside LogReader is actually bound by that lifetime.

This means two things:

(1) I could pass a function that gives something completely different, and it would work just fine:

static NO_DATA: &[()] = &[()];
fn my_next_fn<'a>(reader: &mut LogReader<'a>) -> Option<(&'a str, &'a[ConvertedValue])> {
    Some(("wat", NO_DATA))
}

(2) Even if I wanted my function to return something from the log reader's internal data, it wouldn't work because the lifetimes do not match at all. Let's try it out anyway to see what happens:

static DATA: &[()] = &[()];

fn my_next_fn<'a>(reader: &mut LogReader<'a>) -> Option<(&'a str, &'a[ConvertedValue])> {
    Some((&reader.data[0..4], DATA))
}

fn main() {
    let mut a = LogReader {
      data: "This is DATA!".to_owned(),
      next_fn: my_next_fn
    };

    println!("{:?}", a.next());
}

The compiler would throw you this:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/main.rs:26:12
   |
26 |     Some((&reader.data[0..4], DATA))
   |            ^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 25:88...
  --> src/main.rs:25:89
   |
25 |   fn my_next_fn<'a>(reader: &mut LogReader<'a>) -> Option<(&'a str, &'a[ConvertedValue])> {
   |  _________________________________________________________________________________________^ starting here...
26 | |     Some((&reader.data[0..4], DATA))
27 | | }
   | |_^ ...ending here
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:26:12
   |
26 |     Some((&reader.data[0..4], DATA))
   |            ^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the body at 25:88...
  --> src/main.rs:25:89
   |
25 |   fn my_next_fn<'a>(reader: &mut LogReader<'a>) -> Option<(&'a str, &'a[ConvertedValue])> {
   |  _________________________________________________________________________________________^ starting here...
26 | |     Some((&reader.data[0..4], DATA))
27 | | }
   | |_^ ...ending here
note: ...so that expression is assignable (expected std::option::Option<(&'a str, &'a [()])>, found std::option::Option<(&str, &[()])>)
  --> src/main.rs:26:5
   |
26 |     Some((&reader.data[0..4], DATA))
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

...where the anonymous lifetime #1 is the log reader's lifetime. Forcing &mut LogReader to also have a lifetime 'a (&'a mut LogReader<'a>) would lead to further lifetime issues when attempting to implement Iterator. This basically narrows down to the fact that 'a is incompatible with references to values of LogReader themselves.

So, how should we fix that?

but that doesn't change the fact that the return type has references and so lifetime annotations come into it

Although that is not accurate (since lifetime elision can occur in some cases), that gives a hint to the solution: either avoid returning references at all or delegate data to a separate object, so that 'a can be bound to that object's lifetime. The final part of the answer to your question is in Iterator returning items by reference, lifetime issue.

E_net4
  • 27,810
  • 13
  • 101
  • 139
  • `Note that usually this isn't a concern to consumers of the struct, since the concrete lifetimes are then imposed by the compiler` That is my question, how can i get that to work for the LogstreamProcessor. – Camden Narzt May 27 '17 at 00:15
  • @CamdenNarzt As I stated at the beginning of the answer, you would need an explicit lifetime on that struct as well. The compiler does not automatically resolve that. – E_net4 May 27 '17 at 00:34