1

In Rust (version 1.x) I want to use elements of a collection inside a loop such as the example below (which recors the characters it has seen and does something when it spots a repeated char) where the collection is defined inside the function and only used by the loop.

    fn do_something(word:&str) -> u32 {
            let mut seen_chars = HashMap::new();
            let mut answer : u32 = 0;
            for (i,c) in word.chars().enumerate() {
            let char_str = Box::new(c.to_string());
            match seen_chars.get(&char_str) {
                    Some(&index) => {
                    answer = answer + index;
                    },
                    None => {seen_chars.insert(char_str,i);}
            }; 

    }
    answer
    }

In order to store references to c in my hashmap (which I have declared outside the loop) I need to box c and allocate it on the heap. This feels inefficent and like I must be doing something wrong. I wondered if using explicit lifetimes would be a better way to do things, below is my best attempt but I can't get it to compile.

    fn do_something<'a>(word:&str) -> u32 {
            let mut seen_chars = : &'a HashMap<&str,usize> = &HashMap::new();
            let mut answer : u32 = 0;
            for (i,c) in word.chars().enumerate() {
            let char_str =  &'a str = &c.to_string();
            match seen_chars.get(&char_str) {
                    Some(&index) => {
                    answer = answer + index;
                    },
                    None => {seen_chars.insert(char_str,i);}
            }; 

    }
    answer
    }

When I try compiling I get "error: borrowed value does not live long enough" with an indication that "&HashMap::new()" is the problem. Can I use lifetime specification to solve this issue or am doing things the wrong way here?

1 Answers1

2

I don't think either of your approaches is the best solution. You can just use the char itself as a key for your HashMap, no need to convert it to a String:

fn do_something(word:&str) -> usize {
    let mut seen_chars = HashMap::new();
    let mut answer : usize = 0;
    for (i,c) in word.chars().enumerate() {
        match seen_chars.get(&c) {
            Some(&index) => {
                answer = answer + index;
            },
            None => {seen_chars.insert(c,i);}
        };
    }
    answer
}

(I also had to change the type of answer to get this to compile, since enumerate gives you usizes . Alternatively, you could cast i to u32 where necessary)

If, for some reason, you wanted to have string keys instead of char, you would have to use owned strings (i.e. String) instead of string slices (&str). You would end up with something like this:

fn do_something(word:&str) -> usize {
    let mut seen_chars : HashMap<String,usize> = HashMap::new();
    let mut answer : usize = 0;
    for (i,c) in word.chars().enumerate() {
        let char_str = c.to_string();
        match seen_chars.get(&char_str) {
            Some(&index) => {
                answer = answer + index;
            },
            None => {seen_chars.insert(char_str,i);}
        };
    }
    answer
}

But I strongly suspect that the first options is what you actually want.

fjh
  • 12,121
  • 4
  • 46
  • 46
  • Thank you. I see now I was so concentrated on trying to solve the bad borrow compile error that I didn't step back and look at if I even needed the string conversion in the first place. In the case where you did actually need to create a new reference to add to a collection (For example some struct based on the character) would explicit lifetimes be the way to go? – James Matheson Dec 13 '15 at 05:19
  • No, explicit lifetimes probably wouldn't help you. The problem is that you can't put a reference to a value in a collection if the collection outlives the value that the reference points to, because the reference would become invalid. Explicit lifetime annotations never extend the lifetime of a value. – fjh Dec 13 '15 at 14:19