How to check whether a collection contains a value
Short answer: on a Vec or a slice, contains(&value) — it walks the elements and
compares them. Ask the same question about the same values more than a handful of times,
and a HashSet answers faster: build it once, then each contains is a hash and a
comparison. Maps answer with contains_key.
From a fresh project — cargo new contains-demo — replace the contents of src/main.rs
with each block below and run it with cargo run. The project name shows up only in
cargo’s own output; nothing else here depends on it.
contains on a Vec
Section titled “contains on a Vec”fn main() { let nums = vec![10, 20, 30];
println!("{}", nums.contains(&20)); println!("{}", nums.contains(&40));
let languages = vec!["Rust", "Go", "Python"];
println!("{}", languages.contains(&"Go")); println!("{}", languages.contains(&"Java"));}truefalsetruefalsecontains is a slice method; a Vec reaches it through Deref. It walks from the front
comparing each element with PartialEq, so a hit stops early and a miss looks at
everything — the standard library’s own documentation puts the operation at O(n).
Written by hand, the same search is nums.iter().any(|&n| n == 20).
The & is not optional
Section titled “The & is not optional”The method takes &T, so a Vec<String> wants a &String, and the obvious spelling
does not compile:
fn main() { let names = vec!["Ada".to_string(), "Grace".to_string()];
println!("{}", names.contains("Ada"));}error[E0308]: mismatched types --> src/main.rs:4:35 | 4 | println!("{}", names.contains("Ada")); | -------- ^^^^^ expected `&String`, found `&str` | | | arguments to this method are incorrect | = note: expected reference `&String` found reference `&'static str`note: method defined here --> /Users/rango/.rustup/toolchains/1.99.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/slice/mod.rs:2600:12 |2600 | pub fn contains(&self, x: &T) -> bool | ^^^^^^^^
For more information about this error, try `rustc --explain E0308`.The fix is to hand it an owned String — names.contains(&"Ada".to_string()) — or to
write the comparison by hand: the standard library’s own example for this case is
v.iter().any(|e| e == "hello"), which searches a slice of String with a &str
because String implements PartialEq<str>.
A HashSet for the questions that repeat
Section titled “A HashSet for the questions that repeat”use std::collections::HashSet;
fn main() { let names = vec!["Ada".to_string(), "Grace".to_string()]; let set: HashSet<String> = names.iter().cloned().collect();
println!("{}", set.contains("Ada")); println!("{}", set.contains(&"Ada".to_string())); println!("{}", set.contains("Lovelace"));}truetruefalseBuilding the set costs one pass over the values. After that each contains hashes the
question and compares — expected constant time, whatever the size. The call takes any
borrowed form of the set’s value type: a HashSet<String> accepts both spellings above
because its bound is T: Borrow<Q>, not the plain &T a Vec asks for. The values
themselves need Hash + Eq, which is why a float cannot go in the set at all — the
f64 key article has the details.
One question against one Vec still loses: building the set is a pass, and so is the scan. The set wins when the questions repeat:
use std::collections::HashSet;
fn main() { let stoplist = vec!["the", "quick", "brown", "fox"]; let stopwords: HashSet<&str> = stoplist.iter().copied().collect();
let text = "the quick brown fox jumps over the lazy dog the quick fox"; let mut hits = 0; for word in text.split_whitespace() { if stopwords.contains(word) { hits += 1; } } println!("{hits}");}8Measured on this machine (release build, Rust 1.99.0, worst case — the probe is never in the collection): the scan grew from tens of nanoseconds at a thousand values to around forty-five microseconds at a million, while the set stayed in the tens of nanoseconds the whole way. The numbers move with the machine; the shape does not.
Maps answer about keys
Section titled “Maps answer about keys”use std::collections::{BTreeMap, HashMap};
fn main() { let mut ages = HashMap::new(); ages.insert("Ada", 36); ages.insert("Grace", 45);
println!("{}", ages.contains_key("Ada")); println!("{}", ages.contains_key("Lovelace"));
let mut sorted = BTreeMap::new(); sorted.insert("Ada", 36); sorted.insert("Grace", 45);
println!("{}", sorted.contains_key("Grace")); println!("{}", sorted.contains_key("Lovelace"));}truefalsetruefalseBoth maps have contains_key, and both take the borrowed form the same way. A
HashMap has no contains at all — the question a map answers is about its keys —
and the compiler says so, with the right name attached:
fn main() { use std::collections::HashMap;
let mut ages = HashMap::new(); ages.insert("Ada", 36);
println!("{}", ages.contains("Ada"));}error[E0599]: no method named `contains` found for struct `HashMap<K, V, S, A>` in the current scope --> src/main.rs:7:25 |7 | println!("{}", ages.contains("Ada")); | ^^^^^^^^ |help: there is a method `contains_key` with a similar name |7 | println!("{}", ages.contains_key("Ada")); | ++++
For more information about this error, try `rustc --explain E0599`.Related
Section titled “Related”- Which collection should you use? — the decision table for the whole chapter
- How to find an element in a Vec —
iter().findwhen you need the element, not a yes-or-no - What is the difference between HashMap and BTreeMap? — the previous article, and the two maps above
Next in this chapter: why a HashMap’s order is different every run.