97
@sdcard_sid ·

Returning an iterator from a method fights me over lifetimes every single time Idiomatic?

I have a struct holding a Vec<Record> and I want a method that returns an iterator over the records matching a predicate, without collecting into a new Vec. Every attempt ends in either a lifetime error about the closure, or the compiler telling me the returned type doesn't live long enough. I've made it work once with Box<dyn Iterator> and it felt like giving up. What's the current idiomatic signature for this?

7 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @borrowck_ben · 6mo ago · 3 replies

    fn matching(&self, kind: Kind) -> impl Iterator<Item = &Record> + '_ is what you want. The '_ ties the returned iterator to the borrow of self, which is the piece that's usually missing when you get 'doesn't live long enough'. Inside, self.records.iter().filter(move |r| r.kind == kind) and the move on the closure captures kind by value so the closure doesn't borrow a local. Box<dyn Iterator> isn't wrong but you pay an allocation and lose the concrete type for no reason here.

    94
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @borrowck_ben · 6mo ago

      It can see it, it just won't assume it, because for impl Trait in return position the captured lifetimes have to be spelled out or elided explicitly. Newer editions loosened this, which is why half the examples you find online don't have it.

      43
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @sdcard_sid · 6mo ago

      The + '_ was the missing piece. I'd been writing impl Iterator<Item = &Record> and wondering why it wanted a lifetime it could clearly see.

      26
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @yaml_yusuf · 6mo ago

    One thing that trips people: if the predicate itself borrows something with a shorter life than self, no signature will save you and you genuinely do need to restructure. Passing the filter criteria in by value, as above, is what makes the simple signature possible.

    62
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @grepwitch · 6mo ago · 2 replies

    Box<dyn Iterator + '_> is a perfectly reasonable escape hatch when you have several branches returning different iterator types, and one heap allocation for an iterator you'll poll thousands of times is noise. I wouldn't feel like you gave up, I'd feel like you shipped.

    51
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @rackmount_rina · 6mo ago

      Agreed, and if the branches are the problem, itertools::Either gets you back to a concrete type without the box. Worth knowing before you reach for dyn.

      33
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @attic_server · 6mo ago

    Also fine to return std::slice::Iter<'_, Record> directly when you're not filtering. Naming the concrete type is unfashionable but it documents exactly what you get and it never surprises you later.

    29
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report