Skip to content
Select theme

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.

true
false
true
false

contains 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 method takes &T, so a Vec<String> wants a &String, and the obvious spelling does not compile:

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>.

true
true
false

Building 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:

8

Measured 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.

true
false
true
false

Both 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:

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`.

Next in this chapter: why a HashMap’s order is different every run.