5

I have a function that reads in a file, and for each line adds it to a HashSet of type &str, but I can't work out how to tell the borrow checker to increase the lifetime.

Here's my function so far:

fn build_collection_set(reader: &mut BufReader<File>) -> HashSet<&str> {
    let mut collection_set: HashSet<&str> = HashSet::new();

    for line in reader.lines() {
        let line = line.unwrap();
        if line.len() > 0 {
            collection_set.insert(&*line);
        }
    }

    return collection_set;
}

How do I let Rust know I want to keep it around longer?

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
Cetra
  • 2,593
  • 1
  • 21
  • 27
  • Duplicate of http://stackoverflow.com/q/27570978/155423 or http://stackoverflow.com/q/28820781/155423. **Please** try searching for existing questions before asking new ones. – Shepmaster Feb 28 '16 at 22:36
  • I did search, but no question is able to tell me how to extend a lifetime of a reference, which is really what I'm asking. – Cetra Feb 29 '16 at 04:27
  • One of those answers starts with "you can't" which is the same answer you'll get here: "it's impossible". – Shepmaster Feb 29 '16 at 04:28

2 Answers2

7

but I can't work out how to tell the borrow checker to increase the lifetime.

It's impossible.


The lifetime of a value, in C, C++ or Rust, is defined either:

  • by its lexical scope, if it is bound to an automatic variable
  • by its dynamic scope, if it is allocated on the heap

You can create variables which reference this value, and if your reference lives longer than the value, then you have a dangling reference:

  • in C and C++, you better do nothing with it
  • in Rust, the compiler will refuse to compile your code

In order to validate your program, the Rust compiler will require that you annotate the lifetime of your references; you will use lifetime annotations such as 'a in &'a T which allow naming a lifetime in order to document the relationship between the lifetime of multiple values.

The operative word is document here: a lifetime is intangible and cannot be influenced, the lifetime annotation 'a is just a name to allow referring to it.


So?

Whenever you find yourself wanting to extend the lifetime of a reference, what you should be looking at instead is extending the lifetime of the referred... or simply not use a reference but a value instead.

In this case, a simple solution is to return String instead of &str:

fn build_collection_set(reader: &mut BufReader<File>) -> HashSet<String> {
    let mut collection_set = HashSet::new();

    for line in reader.lines() {
        let line = line.unwrap();
        if line.len() > 0 {
            collection_set.insert(line);
        }
    }

    collection_set
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • So shouldn't I then be able to annotate the reference like so: let line: 'a String = line.unwrap(); let a_ref : &'a str – Cetra Feb 29 '16 at 04:43
  • @Cetra: You can use `'a:` (a label), however only in front of a `while`, `for`, `loop` in which case it is used to be able to break from an inner loop directly to the outer loop. Otherwise, you can only introduce a lifetime name in a function signature; as I said most of the time they are intangible. – Matthieu M. Feb 29 '16 at 07:19
3

reader.lines() returns an iterator over owned Strings. But then in your for loop you cast these to borrowed references to &str. So when the iterator goes out of scope all your borrowed references become invalid. Consider using a HashSet<String> instead, which also is zero cost, because the Strings get moved into the HashSet and therefore aren't copied.

Working example

fn build_collection_set(reader: &mut BufReader<File>) -> HashSet<String> {
    let mut collection_set: HashSet<String> = HashSet::new();

    for line in reader.lines() {
        let line = line.unwrap();
        if line.len() > 0 {
            collection_set.insert(line);
        }
    }
    collection_set
}
Yasammez
  • 1,383
  • 12
  • 15