1
votes

I'm experimenting with Rust right now, and I'm getting really tripped up by all sorts of random compiler errors like this one:

error: cannot move out of dereference of `&`-pointer
    return list[idx];
           ^~~~~~~~~

The snippet in question is reproduced below. list is a word list, and the idea is to return a random word from list which is within a certain length (between minLength and maxLength). So, I generate a random uint and constrain it to the size of the list using a modulo. Then I want to check to see if that word is the right length, and if it is return it.

Obviously this isn't the most efficient way to do this, but I just wanted something easy that would fulfill the needs of the exercise.

fn get_a_word(list: Vec<String>, minLength: uint, maxLength: uint) -> String {
    loop {
        let idx = rand::random::<uint>() % list.len();
        if list[idx].len() > minLength && list[idx].len() < maxLength {
            return list[idx];
        }
    }
}

I've done some research into what this error message means, but everyone seems to say that this comes up when you're dealing with Option<T> retvals, not attempting to get the value out of a collection.

This is probably a really simple question, but why on earth doesn't Rust just let you return a value from a collection/vector/array?

2

2 Answers

3
votes

Only one thing can own an object at a time, and what you’re trying to do there would break that invariant, as you would have the string you are returning in both the vector and in the return position. list[idx] expands more or less to *list.index(idx) for the method Index::index (in truth it’s a little more complex than that as whether it should be Index::index or IndexMut::index_mut is determined based on context, &list[idx] would be just a straight list.index(idx) and list[idx].method() for a &self-taking method would be list.index(idx).method()).

You have a few options; here are three:

  • Remove the value from the list, returning it. The rest of the Vec<String> that you had will be dropped, and so all of the Strings except for the right one will in the end be freed.

    return list.swap_remove(idx).unwrap(); (compare swap_remove and remove, the former is more efficient but alters the order which is perfectly OK in this case) in place of return list[idx]; would do for this.

  • Clone the string. This is less efficient but may be considered acceptable.

    return list[idx].clone(); will do for this.

  • Use slices and references through the whole thing: this is the most efficient way and typically the most idiomatic. It’s efficient as it doesn’t involve any allocations or deallocations or moving of large values.

    fn get_a_word(list: &[String], min_length: uint, max_length: uint) -> &str {
        loop {
            let idx = rand::random::<uint>() % list.len();
            if list[idx].len() > min_length && list[idx].len() < max_length {
                return list[idx].as_slice();
            }
        }
    }
    
2
votes

list[idx] is a shorthand for *list.index(&idx). index() returns a borrowed pointer inside the value that is being indexed (here, the Vec). You cannot move a value (here, a String) by dereferencing a borrowed pointer; that would be like "stealing" a String from the Vec, which owns the string. A String owns an allocation on the heap; we can't have two String values that reference the same heap allocation, or they would both try to free it when they are dropped, which would lead to a double-free.

Since the function receives the Vec by value, it means the Vec is moved into the function, and therefore the function can do whatever we want with it, since it nows "owns" the Vec (the Vec will be destroyed when the function exits). For example, we can remove the String we want to return from the Vec, so that the Vec no longer has ownership of that String.

fn get_a_word(mut list: Vec<String>, minLength: uint, maxLength: uint) -> String {
    loop {
        let idx = rand::random::<uint>() % list.len();
        if list[idx].len() > minLength && list[idx].len() < maxLength {
            return list.remove(idx).unwrap();
        }
    }
}

If you didn't mean to have the Vec destroyed when get_a_word exits, then the function should receive a borrowed pointer to a Vec instead. If we do this, the function can simply return a string slice.

fn get_a_word(list: &Vec<String>, minLength: uint, maxLength: uint) -> &str {
    loop {
        let idx = rand::random::<uint>() % list.len();
        if list[idx].len() > minLength && list[idx].len() < maxLength {
            return list[idx].as_slice();
        }
    }
}

If you must absolutely return a String, then you can clone the value instead:

fn get_a_word(list: &Vec<String>, minLength: uint, maxLength: uint) -> String {
    loop {
        let idx = rand::random::<uint>() % list.len();
        if list[idx].len() > minLength && list[idx].len() < maxLength {
             return list[idx].clone();
        }
    }
}