How does http::HeaderMap handle multiple values for the same header name compared to HashMap?

http::HeaderMap is specifically designed for HTTP headers where multiple values for the same header name are both valid and common, such as Set-Cookie headers or multiple Accept values. Unlike HashMap, which stores a single value per key and overwrites on insertion, HeaderMap maintains all values for a given header name in insertion order. When you insert a value for an existing header name, HeaderMap appends the value rather than replacing it. Retrieval methods like get() return the first value for backward compatibility, while get_all() returns an iterator over all values. This design reflects HTTP semantics where headers like Set-Cookie must each be sent on their own line, and order can be significant for headers like Accept-Encoding.

HashMap: Single Value Per Key

use std::collections::HashMap;
 
fn hashmap_single_value() {
    let mut headers: HashMap<&str, &str> = HashMap::new();
    
    // Insert first value
    headers.insert("content-type", "text/html");
    println!("{:?}", headers.get("content-type")); // Some("text/html")
    
    // Insert second value - REPLACES the first
    headers.insert("content-type", "application/json");
    println!("{:?}", headers.get("content-type")); // Some("application/json")
    
    // Only one value exists
    println!("Length: {}", headers.len()); // 1
    println!("Values: {:?}", headers.values().collect::<Vec<_>>()); // ["application/json"]
}

HashMap overwrites previous values when inserting with an existing key.

HeaderMap: Multiple Values Per Key

use http::HeaderMap;
 
fn headermap_multiple_values() {
    let mut headers = HeaderMap::new();
    
    // Insert first value
    headers.insert("content-type", "text/html".parse().unwrap());
    println!("{:?}", headers.get("content-type")); // Some("text/html")
    
    // Append second value - KEEPS BOTH
    headers.append("content-type", "application/json".parse().unwrap());
    println!("{:?}", headers.get("content-type")); // Some("text/html") - first value
    
    // Get all values
    let all: Vec<_> = headers.get_all("content-type").iter().collect();
    println!("All values: {:?}", all); // ["text/html", "application/json"]
    
    // Header name count is different from value count
    println!("Header names: {}", headers.keys_name_count()); // 1
    println!("Total values: {}", headers.values().count()); // 2
}

HeaderMap preserves all values; insert replaces, append adds.

Insert vs Append Behavior

use http::HeaderMap;
 
fn insert_vs_append() {
    let mut headers = HeaderMap::new();
    
    // insert() replaces all existing values
    headers.insert("x-custom", "first".parse().unwrap());
    headers.insert("x-custom", "second".parse().unwrap());
    
    let values: Vec<_> = headers.get_all("x-custom").iter().collect();
    println!("After inserts: {:?}", values); // ["second"] - first was replaced
    
    // append() adds to existing values
    headers.append("x-custom", "third".parse().unwrap());
    headers.append("x-custom", "fourth".parse().unwrap());
    
    let values: Vec<_> = headers.get_all("x-custom").iter().collect();
    println!("After appends: {:?}", values); // ["second", "third", "fourth"]
    
    // insert() clears and sets new value
    headers.insert("x-custom", "fifth".parse().unwrap());
    
    let values: Vec<_> = headers.get_all("x-custom").iter().collect();
    println!("After final insert: {:?}", values); // ["fifth"] - all previous cleared
}

insert replaces all values; append preserves and adds.

HTTP Headers That Require Multiple Values

use http::HeaderMap;
 
fn realistic_http_headers() {
    let mut response_headers = HeaderMap::new();
    
    // Set-Cookie must be sent as separate headers
    // Browsers won't accept multiple cookies on one Set-Cookie line
    response_headers.append("set-cookie", "session=abc123; HttpOnly".parse().unwrap());
    response_headers.append("set-cookie", "preferences=dark; Max-Age=86400".parse().unwrap());
    response_headers.append("set-cookie", "tracking=xyz; Secure".parse().unwrap());
    
    // All Set-Cookie values preserved
    for cookie in response_headers.get_all("set-cookie") {
        println!("Set-Cookie: {}", cookie.to_str().unwrap());
    }
    
    // Accept headers can have multiple values with different priorities
    let mut request_headers = HeaderMap::new();
    request_headers.append("accept", "text/html".parse().unwrap());
    request_headers.append("accept", "application/json".parse().unwrap());
    request_headers.append("accept", "*/*".parse().unwrap());
    
    // Order matters for content negotiation
    println!("Accept header order:");
    for (i, accept) in request_headers.get_all("accept").iter().enumerate() {
        println!("  {}. {}", i + 1, accept.to_str().unwrap());
    }
}

HTTP semantics require multiple values for headers like Set-Cookie.

Iteration Differences

use http::HeaderMap;
use std::collections::HashMap;
 
fn iteration_comparison() {
    // HashMap - each key-value pair appears once
    let mut hashmap: HashMap<&str, &str> = HashMap::new();
    hashmap.insert("a", "1");
    hashmap.insert("b", "2");
    
    println!("HashMap iteration:");
    for (key, value) in &hashmap {
        println!("  {} = {}", key, value);
    }
    // a = 1
    // b = 2
    
    // HeaderMap - each VALUE appears as separate entry
    let mut headermap = HeaderMap::new();
    headermap.append("a", "1".parse().unwrap());
    headermap.append("a", "1b".parse().unwrap()); // Second value for "a"
    headermap.append("b", "2".parse().unwrap());
    
    println!("\nHeaderMap iteration:");
    for (name, value) in &headermap {
        println!("  {} = {}", name, value.to_str().unwrap());
    }
    // a = 1
    // a = 1b
    // b = 2
    
    println!("\nHeaderMap keys_name_count: {}", headermap.keys_name_count()); // 2
    println!("HeaderMap iter count: {}", headermap.iter().count()); // 3
}

HeaderMap iteration yields each value separately, even for the same header name.

Key Types and Case Insensitivity

use http::HeaderMap;
use http::header::{CONTENT_TYPE, USER_AGENT};
 
fn case_insensitive_keys() {
    let mut headers = HeaderMap::new();
    
    // Header names are case-insensitive in HTTP
    headers.insert("Content-Type", "text/html".parse().unwrap());
    
    // These all access the same entry
    println!("{:?}", headers.get("content-type")); // Some("text/html")
    println!("{:?}", headers.get("Content-Type")); // Some("text/html")
    println!("{:?}", headers.get("CONTENT-TYPE")); // Some("text/html")
    
    // Using typed constants
    headers.insert(USER_AGENT, "MyApp/1.0".parse().unwrap());
    println!("{:?}", headers.get(USER_AGENT)); // Some("MyApp/1.0")
    
    // Appending works with any case
    headers.append("content-type", "charset=utf-8".parse().unwrap());
    
    let values: Vec<_> = headers.get_all("CONTENT-TYPE").iter().collect();
    println!("All content-type values: {:?}", values);
}

HeaderMap treats header names case-insensitively, matching HTTP semantics.

Retrieval Methods

use http::HeaderMap;
 
fn retrieval_methods() {
    let mut headers = HeaderMap::new();
    headers.append("accept", "text/html".parse().unwrap());
    headers.append("accept", "application/json".parse().unwrap());
    headers.append("accept", "*/*".parse().unwrap());
    
    // get() returns the FIRST value only
    let first = headers.get("accept");
    println!("First value: {:?}", first.unwrap().to_str().unwrap()); // "text/html"
    
    // get_all() returns an iterator over ALL values
    println!("All values:");
    for value in headers.get_all("accept") {
        println!("  - {}", value.to_str().unwrap());
    }
    
    // Convert to Vec if needed
    let all_values: Vec<_> = headers
        .get_all("accept")
        .iter()
        .map(|v| v.to_str().unwrap().to_string())
        .collect();
    println!("As vector: {:?}", all_values);
    
    // Checking if header exists
    println!("Has accept: {}", headers.contains_key("accept"));
    println!("Has content-type: {}", headers.contains_key("content-type"));
}

get returns the first value; get_all iterates over all values.

Removal Behavior

use http::HeaderMap;
 
fn removal_behavior() {
    let mut headers = HeaderMap::new();
    headers.append("x-custom", "first".parse().unwrap());
    headers.append("x-custom", "second".parse().unwrap());
    headers.append("x-custom", "third".parse().unwrap());
    
    // remove() removes ALL values for the key
    let removed = headers.remove("x-custom");
    println!("Removed first value: {:?}", removed.unwrap().to_str().unwrap()); // "first"
    
    // All values are gone
    println!("Remaining values: {:?}", headers.get("x-custom")); // None
    
    // remove_entry() also returns all values
    headers.append("x-other", "a".parse().unwrap());
    headers.append("x-other", "b".parse().unwrap());
    
    let (name, first_value) = headers.remove_entry("x-other").unwrap();
    println!("Removed entry: {} = {}", name, first_value.to_str().unwrap());
}

remove deletes all values for a header name.

Memory and Performance Characteristics

use http::HeaderMap;
use std::collections::HashMap;
 
fn performance_comparison() {
    // HashMap memory: ~48 bytes overhead per entry + key + value
    // HashMap lookup: O(1) average
    
    // HeaderMap memory: Optimized for small header counts
    // HeaderMap lookup: O(n) for small n, but very cache-friendly
    // HeaderMap stores values inline when possible
    
    // HashMap: each entry is independent
    let mut hashmap: HashMap<String, String> = HashMap::new();
    for i in 0..100 {
        hashmap.insert(format!("header-{}", i), format!("value-{}", i));
    }
    
    // HeaderMap: values for same key stored together
    let mut headermap = HeaderMap::with_capacity(100);
    for i in 0..50 {
        // Multiple values per key
        headermap.append(format!("header-{}", i), format!("value-{}a", i).parse().unwrap());
        headermap.append(format!("header-{}", i), format!("value-{}b", i).parse().unwrap());
    }
    
    // HeaderMap is optimized for typical HTTP header counts (5-50 headers)
    // HashMap is optimized for general-purpose key-value storage
    
    println!("HashMap len: {}", hashmap.len()); // 100
    println!("HeaderMap keys: {}", headermap.keys_name_count()); // 50
    println!("HeaderMap values: {}", headermap.len()); // 100
}

HeaderMap is optimized for HTTP header patterns; HashMap for general use.

Entry API Differences

use http::HeaderMap;
use std::collections::HashMap;
 
fn entry_api() {
    // HashMap entry API - returns single value entry
    let mut hashmap: HashMap<&str, i32> = HashMap::new();
    hashmap.entry("count").and_modify(|v| *v += 1).or_insert(1);
    hashmap.entry("count").and_modify(|v| *v += 1).or_insert(1);
    println!("HashMap count: {:?}", hashmap.get("count")); // Some(2)
    
    // HeaderMap entry API - different semantics
    let mut headermap = HeaderMap::new();
    
    // entry() returns Entry for the header name
    use http::header::Entry;
    
    // This replaces existing values
    if let Entry::Vacant(entry) = headermap.entry("count") {
        entry.insert("1".parse().unwrap());
    }
    
    // append is simpler for multiple values
    headermap.append("count", "2".parse().unwrap());
    
    println!("HeaderMap count values:");
    for v in headermap.get_all("count") {
        println!("  {}", v.to_str().unwrap());
    }
}

HashMap entries manage single values; HeaderMap has different entry semantics.

Converting Between Types

use http::HeaderMap;
use std::collections::HashMap;
 
fn conversions() {
    // HeaderMap to HashMap (loses multiple values)
    let mut headermap = HeaderMap::new();
    headermap.append("accept", "text/html".parse().unwrap());
    headermap.append("accept", "application/json".parse().unwrap());
    
    let hashmap: HashMap<_, _> = headermap
        .iter()
        .map(|(k, v)| (k.to_string(), v.to_str().unwrap().to_string()))
        .collect();
    
    println!("HashMap only has last value per key");
    // HashMap loses "text/html" if "accept" was the key
    
    // Better: HeaderMap to HashMap<String, Vec<String>>
    let multi_map: HashMap<String, Vec<String>> = {
        let mut map = HashMap::new();
        for (name, value) in &headermap {
            map.entry(name.to_string())
                .or_insert_with(Vec::new)
                .push(value.to_str().unwrap().to_string());
        }
        map
    };
    
    println!("Multi-map: {:?}", multi_map);
    // {"accept": ["text/html", "application/json"]}
}

Converting to HashMap requires deciding how to handle multiple values.

Summary Comparison

Feature HashMap HeaderMap
Multiple values per key No (overwrites) Yes (appends)
Key case sensitivity Sensitive Insensitive
insert() Replaces value Replaces all values
append() N/A Adds value
get() Returns value Returns first value
get_all() N/A Returns all values
Iteration Key-value pairs One entry per value
Key type Any hashable HeaderName
Typical use General KV storage HTTP headers

Synthesis

HeaderMap and HashMap serve fundamentally different purposes:

HashMap design:

  • One value per key, enforced by insert overwriting
  • Keys are case-sensitive and can be any hashable type
  • Optimized for general key-value storage
  • Simple get returns the only value

HeaderMap design:

  • Multiple values per key, reflecting HTTP header semantics
  • Case-insensitive header names matching HTTP specification
  • insert replaces all values, append adds values
  • get returns first value, get_all returns all values
  • Iteration yields one entry per value, not per key name

Key insight: The HTTP/1.1 specification allows multiple header fields with the same name (except for some headers like Content-Type), and the order of headers can be significant. HeaderMap is purpose-built for these semantics, while HashMap would silently drop values or require workarounds like storing Vec<String> values. When building HTTP servers or clients, HeaderMap correctly represents the wire format where multiple Set-Cookie headers each need their own line, and Accept values are considered in order for content negotiation. Use HashMap for general configuration or data storage; use HeaderMap when dealing with actual HTTP headers.