Understanding Rust’s [ ].iter().cloned().collect() – A Beginner-Friendly Guide

Let’s look at some idiomatic Rust code, and see if we can explain what it’s doing and why!

.iter().cloned().collect() often pops up when converting simple arrays into more complex collections like HashMap.

Why Use This?

This approach shines when initializing complex data structures while keeping your code concise. It’s also flexible—start with a small array, and scale it later without changing the logic.

use std::collections::HashMap;

fn main(){
    let dat = [("moo".to_string(),10)];
    let hm:HashMap<String,i32> = dat.iter().cloned().collect();
    println!("{:?}", hm);

    let dat = [("moo".to_string(),10),("foo".to_string(),11)];
    let hm:HashMap<String,i32> = dat.iter().cloned().collect();
    println!("{:?}", hm);
}
{"moo": 10}
{"moo": 10, "foo": 11}

[Process exited 0]

tuples are converted into key-value pairs using collect()

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2bd9780fdca26557bd87f7b80d6e0b66

This code demonstrates how tuples are converted into key-value pairs using Rust’s std::collections::HashMap. Let’s break it down step by step:

Key Concepts – .iter().cloned().collect()

  1. Tuples as Key-Value Pairs:
    • Each tuple, such as ("moo".to_string(), 10), is treated as a (key, value) pair.
    • The first element ("moo".to_string()) becomes the key, and the second element (10) becomes the value.
  2. iter():
    • dat.iter() creates an iterator over the array of tuples.
    • This iterator yields references to the tuples.
  3. cloned():
    • cloned() takes each reference from the iterator and clones it, creating owned values (not references).
    • Without cloned(), the iterator would yield &(&String, &i32).
  4. collect():
    • The collect() function transforms an iterator into a collection (in this case, a HashMap).
    • The compiler infers the type of collection (HashMap<String, i32>) based on the declaration of hm.

Example Output

Your code will produce the following output:

{"moo": 10}
{"moo": 10, "foo": 11}

How it Works

  • In the first example: let dat = [("moo".to_string(), 10)]; let hm: HashMap<String, i32> = dat.iter().cloned().collect();
    • The dat array contains one tuple: [("moo".to_string(), 10)].
    • dat.iter() creates an iterator over [("moo".to_string(), 10)].
    • cloned() ensures that the iterator produces owned (String, i32) tuples.
    • collect() transforms these into a HashMap<String, i32>.
  • In the second example: let dat = [("moo".to_string(), 10), ("foo".to_string(), 11)]; let hm: HashMap<String, i32> = dat.iter().cloned().collect();
    • The process is identical, but this time the array contains two tuples, and both are inserted into the HashMap.

Internals of collect()

Under the hood, collect() uses the FromIterator trait, which HashMap implements. When you call collect():

  • Each (key, value) pair is inserted into the HashMap using its insert method.
  • If duplicate keys exist, the latest value overwrites the previous one.

This is a very idiomatic way to create a HashMap in Rust!

iter().cloned().collect()

Practical Rule of Thumb

  • Use cloned() with iterators when you need to clone all elements.
  • Use clone() when working with individual values or when manually transforming items.
You can use into_iter to avoid using cloned - and you would want to use cloned when you need to preserve the original data while creating a new collection or performing an operation that requires ownership of the items.
use std::collections::HashMap;

fn main() {
    let data = [("moo".to_string(), 1), ("voo".to_string(), 2)];
    let hm: HashMap<String, i32> = data.into_iter().collect();
    dbg!("{:?}", hm);
}