Problem overview
The function add_if_missing receives a shopping list items, a vector of strings, and a word item. It adds item at the end only when the list doesn't already hold it, and returns the list. The list may start empty.
For ["milk", "eggs"] and "bread", the word is new, so the result is ["milk", "eggs", "bread"]. For ["milk", "eggs"] and "milk", the word is already there, so the list stays as it is. For an empty list and "apples", the result is ["apples"].
Approach
Ask the question first, then act. contains tells whether the vector holds a value, and push adds to the end. Put push inside an if that runs only when the answer from contains is false. That way the list can never get the same word twice.
Step by step
- Write
if !items.contains(&item). The!turns "is there" into "is missing".containswants a reference, so you pass&item. It only borrows the word, soitemis still yours afterwards. Write the closing brace of theifright away. - Inside the
if, callitems.push(item). This runs only for a new word, and the word moves into the vector. - After the
if, end the function withitems, without a semicolon. When the word was already there, the vector comes back unchanged.
The solution in Rust
impl Solution {
fn add_if_missing(mut items: Vec<String>, item: String) -> Vec<String> {
if !items.contains(&item) {
items.push(item);
}
items
}
}
The order of the two uses of item matters. contains(&item) borrows it for just that call, so push(item) can then take ownership. If you pushed first and checked afterwards, the compiler would reject the code with "borrow of moved value", because a pushed String is no longer yours. The mut in the signature is what lets push change the vector.
Why the check comes first
Calling push without asking would append "milk" a second time, giving ["milk", "eggs", "milk"], which the task forbids. A vector has no built-in rule against repeated values, so the program has to enforce it. The check reads the whole vector from the start until it finds a match, so for a long list it takes longer; for lists as short as these, that doesn't matter. If you ever need to check membership very often on a large collection, a set is the usual better fit, but this lesson works with a plain vector.
Strings and references
The vector holds owned String values, and the word arrives as a String too, so the types match: &item is a &String, which is what contains expects for a Vec<String>. Values are compared by their text, so two different String objects with the same letters count as equal.
Edge cases and common mistakes
- An empty list.
containsisfalsefor it, so the word is added and the result has one item. - Forgetting the
!. Without it, the word is pushed only when it is already there, which is the opposite of the task. - Forgetting the
&.items.contains(item)is a type error:containstakes a reference. - Putting
itemsinside theif. The return value must come after it, or a word that is already there would return nothing.
Testing
Results from running the function:
["milk", "eggs"],"bread"gives["milk", "eggs", "bread"].["milk", "eggs"],"milk"gives["milk", "eggs"].[],"apples"gives["apples"].
