Returning values borrowed from multiple places


#1

I have a design problem where I want to keep some data structures around for the lifetime of my service but want to work in terms of references to the contents of those data structures. Here is a simplified example:

use std::collections::HashSet;

fn build_sets(words: &Vec<String>) -> (
        HashSet<&str>,
        HashSet<&str>) {
    let mut set1 = HashSet::new();
    let mut set2 = HashSet::new();

    for word in words.iter() {
        set1.insert(&word);
        set2.insert(&word)
    }
    (set1, set2)
}

fn calculate_something(set1 : &HashSet<&str>, set2 : &HashSet<&str>) 
        -> Vec<&str> {
    let mut result = Vec::new();

    for item in set1.iter() {
        result.push(item);
    }
    for item in set2.iter() {
        result.push(item);
    }
    result;
}

fn main() {
    let mut words = Vec::new();
    words.insert("Apple");
    words.insert("Bat");
    words.insert("Cat");

    let (set1, set2) = build_sets(&words);
    let result = calculate_something(set1, set2);

    for word in result {
        println!("\t{}", word);
    }
}

As expected, this fails to compile:

src/repro.rs:17:77: 17:81 error: missing lifetime specifier [E0106]
src/repro.rs:17 fn calculate_something(set1 : &HashSet<&str>, set2 : &HashSet<&str>) -> Vec<&str> {
                                                                                            ^~~~
src/repro.rs:17:77: 17:81 help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from one of `set1`'s 2 elided lifetimes or one of `set2`'s 2 elided lifetimes
error: aborting due to previous error

But I’m not sure how to make this pattern work - the string references used in “result” are logically owed by “words” but are borrowed from “set1” and “set2”. Can I somehow give the return of calculate_something a lifetime specifier of bother arguments?

I know of some possible workarounds:

  1. Store the “Strings” in a HashSet and always populate “result” from there - I don’t like having the unnecessary hash lookup.
  2. Use String instead of <&str> everywhere - I don’t like copying data when storing pointers is sufficient.

#2

Something like this should work:

fn calculate_something<'a>(set1 : &HashSet<&'a str>, set2 : &HashSet<&'a str>) 
        -> Vec<&'a str> {
    let mut result = Vec::new();

    for item in set1.iter() {
        result.push(item);
    }
    for item in set2.iter() {
        result.push(item);
    }
    result;
}

I have added the lifetime parameter 'a and assigned it to the content of both set1 and set2, as well as the output. That tells the compiler that both sets should live at least as long as the output vector.


#3

Awesome, thanks!

I suggested a documentation addition in issues/25417

Cheers,
Brian