What is the difference between indexmap::IndexSet::get_full and get for obtaining both index and value?

get_full returns a tuple containing the index, a reference to the value, and the hash, while get returns only an optional reference to the value—get_full provides complete insertion metadata for scenarios where you need to know where an element resides in the underlying order, whereas get is a simpler lookup when you only need the value itself. The index returned by get_full represents the element's position in the insertion order, enabling indexed access patterns alongside hash-based lookups.

The IndexSet Data Structure

use indexmap::IndexSet;
 
fn indexset_basics() {
    // IndexSet is a hash set that maintains insertion order
    // Elements can be accessed by index OR by value lookup
    
    let mut set = IndexSet::new();
    
    set.insert("apple");
    set.insert("banana");
    set.insert("cherry");
    
    // Order is preserved: ["apple", "banana", "cherry"]
    // Elements can be accessed by index (like Vec)
    assert_eq!(set[0], "apple");
    assert_eq!(set[1], "banana");
    assert_eq!(set[2], "cherry");
    
    // Elements can be looked up by value (like HashSet)
    assert!(set.contains(&"banana"));
    
    // IndexSet provides both hash-set semantics AND ordered indexing
}

IndexSet combines hash-based lookup with ordered storage, enabling both contains and indexed access.

The get Method

use indexmap::IndexSet;
 
fn get_method() {
    let mut set = IndexSet::new();
    set.insert("apple");
    set.insert("banana");
    set.insert("cherry");
    
    // get returns Option<&T> - just the value reference
    let result = set.get(&"banana");
    assert_eq!(result, Some(&"banana"));
    
    // Non-existent element returns None
    let result = set.get(&"grape");
    assert_eq!(result, None);
    
    // get only tells you if the element exists
    // It doesn't tell you WHERE it is in the order
    
    // Signature:
    // fn get<Q: ?Sized>(&self, value: &Q) -> Option<&T>
    // where Q: Hash + Equivalent<T>
}

get is the simple lookup: returns the value reference if present, None otherwise.

The get_full Method

use indexmap::IndexSet;
 
fn get_full_method() {
    let mut set = IndexSet::new();
    set.insert("apple");
    set.insert("banana");
    set.insert("cherry");
    
    // get_full returns Option<(usize, &T, &S::Hash)>
    // - index in insertion order
    // - reference to the value
    // - reference to the hash
    
    let result = set.get_full(&"banana");
    
    match result {
        Some((index, value, hash)) => {
            assert_eq!(index, 1);           // Position in order
            assert_eq!(*value, "banana");   // Value reference
            // hash is the computed hash value
            println!("Found at index {} with hash {:?}", index, hash);
        }
        None => println!("Not found"),
    }
    
    // Non-existent element returns None
    let result = set.get_full(&"grape");
    assert_eq!(result, None);
    
    // Signature:
    // fn get_full<Q: ?Sized>(&self, value: &Q) -> Option<(usize, &T, &S::Hash)>
    // where Q: Hash + Equivalent<T>
}

get_full returns the index, value reference, and hash—complete metadata about the element.

Return Type Comparison

use indexmap::IndexSet;
 
fn return_types() {
    let mut set = IndexSet::new();
    set.insert("apple");
    set.insert("banana");
    
    // get returns Option<&T>
    let get_result: Option<&&str> = set.get(&"apple");
    
    // get_full returns Option<(usize, &T, &S::Hash)>
    let get_full_result: Option<(usize, &str, &u64)> = set.get_full(&"apple");
    
    // When you only need the value:
    if let Some(value) = set.get(&"apple") {
        println!("Value: {}", value);
    }
    
    // When you need index:
    if let Some((index, value, _)) = set.get_full(&"apple") {
        println!("Value {} is at index {}", value, index);
        // Can use index for other operations:
        // - get_index(index)
        // - swap_remove_index(index)
        // - etc.
    }
}

The key difference: get returns just the value; get_full returns index, value, and hash.

Practical Use: Index-Based Operations

use indexmap::IndexSet;
 
fn index_operations() {
    let mut set = IndexSet::new();
    set.insert("apple");
    set.insert("banana");
    set.insert("cherry");
    
    // Scenario: Find an element, then remove by index
    
    // Using get: Can't remove efficiently
    // You'd need to iterate to find the index
    
    // Using get_full: Get index, then remove
    if let Some((index, value, _)) = set.get_full(&"banana") {
        println!("Removing {} at index {}", value, index);
        
        // Now we can use index-based operations
        let removed = set.swap_remove_index(index);
        assert_eq!(removed, Some("banana"));
    }
    
    // set now: ["apple", "cherry"]
    assert!(!set.contains(&"banana"));
}

get_full enables index-based operations after finding an element by value.

Practical Use: Iteration from Found Position

use indexmap::IndexSet;
 
fn iterate_from_position() {
    let mut set = IndexSet::new();
    set.insert("apple");
    set.insert("banana");
    set.insert("cherry");
    set.insert("date");
    set.insert("elderberry");
    
    // Find "cherry" and iterate from there
    if let Some((index, value, _)) = set.get_full(&"cherry") {
        println!("Found {} at index {}", value, index);
        
        // Iterate from this position forward
        for i in index..set.len() {
            println!("Index {}: {}", i, set[i]);
        }
    }
}

The index enables positional operations on the ordered collection.

Practical Use: Maintaining Parallel Data Structures

use indexmap::IndexSet;
 
fn parallel_structures() {
    // Maintain ordered set alongside another structure
    let mut set = IndexSet::new();
    let mut scores: Vec<i32> = Vec::new();  // Parallel to set indices
    
    set.insert("alice");
    scores.push(100);
    
    set.insert("bob");
    scores.push(85);
    
    set.insert("charlie");
    scores.push(92);
    
    // Now scores[i] corresponds to set[i]
    
    // Find "bob" and get corresponding score
    if let Some((index, name, _)) = set.get_full(&"bob") {
        let score = scores[index];
        println!("{} has score {}", name, score);
    }
    
    // get would require finding index separately
    // Less efficient:
    if let Some(_value) = set.get(&"charlie") {
        // Need to iterate to find index
        for (i, item) in set.iter().enumerate() {
            if item == "charlie" {
                println!("Score: {}", scores[i]);
                break;
            }
        }
    }
}

get_full avoids a second lookup when maintaining parallel structures based on indices.

Hash Access

use indexmap::IndexSet;
 
fn hash_access() {
    let mut set = IndexSet::new();
    set.insert("apple");
    set.insert("banana");
    
    // get_full also provides access to the hash
    if let Some((index, value, hash)) = set.get_full(&"banana") {
        // The hash is useful for:
        // - Custom hash table implementations
        // - Avoiding recomputing hash
        // - Debug/introspection
        
        println!("Element '{}' at index {} has hash {:?}", value, index, hash);
    }
    
    // In practice, the hash is rarely needed
    // The index and value are the commonly used parts
    let (index, value, _hash) = set.get_full(&"apple").unwrap();
}

The third tuple element is the hash value, useful in specialized scenarios.

Performance Implications

use indexmap::IndexSet;
 
fn performance() {
    let mut set = IndexSet::new();
    for i in 0..1000 {
        set.insert(i);
    }
    
    // Both get and get_full are O(1) average
    // get_full has slightly more overhead (tuple construction)
    // But both are essentially the same hash lookup
    
    // The difference is in what you'd do after:
    
    // Using get + finding index manually
    if set.get(&500).is_some() {
        // Need to find index? O(n) iteration
        for (i, &v) in set.iter().enumerate() {
            if v == 500 {
                // Found index i
                break;
            }
        }
    }
    
    // Using get_full
    if let Some((index, _value, _hash)) = set.get_full(&500) {
        // Already have index, O(1)
    }
    
    // get_full is O(1) for both lookup and getting index
    // get + manual index finding is O(1) + O(n)
}

get_full is more efficient than get followed by iteration to find the index.

Mutable Variants

use indexmap::IndexSet;
 
fn mutable_variants() {
    let mut set = IndexSet::new();
    set.insert(String::from("apple"));
    set.insert(String::from("banana"));
    
    // get_full has a mutable variant
    // Note: The hash reference type differs
    
    // get returns Option<&T>
    let value: Option<&String> = set.get(&"apple");
    
    // get_mut returns Option<&mut T>
    // (when you need to modify the stored value)
    if let Some(value) = set.get_mut(&"apple") {
        value.push_str("_modified");
    }
    
    // get_full_mut returns Option<(usize, &mut T, &S::Hash)>
    // Combines index + mutable reference
    if let Some((index, value, _hash)) = set.get_full_mut(&"banana") {
        *value = String::from("banana_updated");
        println!("Modified element at index {}", index);
    }
    
    // After mutations:
    assert!(set.contains(&"apple_modified"));
    assert!(set.contains(&"banana_updated"));
}

Mutable variants get_mut and get_full_mut allow modifying values.

Comparison Table

use indexmap::IndexSet;
 
fn comparison_table() {
    let mut set = IndexSet::new();
    set.insert("apple");
    set.insert("banana");
    
    // | Method       | Return Type                      | Information Provided |
    // |--------------|----------------------------------|----------------------|
    // | get          | Option<&T>                       | Value only           |
    // | get_mut      | Option<&mut T>                   | Mutable value        |
    // | get_full     | Option<(usize, &T, &S::Hash)>   | Index, value, hash   |
    // | get_full_mut | Option<(usize, &mut T, &S::Hash)>| Index, mut value, hash |
    
    // | Use Case                     | Preferred Method |
    // |------------------------------|------------------|
    // | Just check existence         | contains         |
    // | Get value reference           | get              |
    // | Modify value                  | get_mut          |
    // | Get value + index             | get_full         |
    // | Get index for removal         | get_full         |
    // | Iterate from found position   | get_full         |
    // | Access parallel array element | get_full         |
}

Choose based on what information you need: simple lookup vs complete metadata.

IndexMap vs IndexSet

use indexmap::{IndexSet, IndexMap};
 
fn indexmap_comparison() {
    // IndexSet: stores values, lookup by value
    let mut set = IndexSet::new();
    set.insert("value");
    
    // IndexMap: stores key-value pairs, lookup by key
    let mut map = IndexMap::new();
    map.insert("key", "value");
    
    // Both have get_full:
    
    // IndexSet::get_full returns Option<(usize, &V, &S::Hash)>
    let set_result: Option<(usize, &str, &u64)> = set.get_full(&"value");
    
    // IndexMap::get_full returns Option<(usize, &K, &V)>
    // Note: different return type (includes key)
    let map_result: Option<(usize, &str, &str)> = map.get_full(&"key");
    
    // For IndexMap, get_full provides key reference too
    // For IndexSet, the key IS the value, so value appears twice in concept
}

IndexMap::get_full also returns the key; IndexSet::get_full returns value and hash.

Pattern: Destructuring get_full

use indexmap::IndexSet;
 
fn destructure_pattern() {
    let mut set = IndexSet::new();
    set.insert("apple");
    set.insert("banana");
    set.insert("cherry");
    
    // Common pattern: ignore hash with _
    if let Some((index, value, _)) = set.get_full(&"banana") {
        println!("Found at index {}", index);
    }
    
    // Pattern: extract only index
    let index = set.get_full(&"apple").map(|(i, _, _)| i);
    
    // Pattern: extract only value
    let value = set.get_full(&"apple").map(|(_, v, _)| v);
    
    // But if you only need value, use get instead:
    let value = set.get(&"apple");
    
    // Pattern: multiple lookups
    fn find_multiple(set: &IndexSet<&str>, items: &[&str]) -> Vec<usize> {
        items.iter()
            .filter_map(|&item| set.get_full(&item).map(|(i, _, _)| i))
            .collect()
    }
}

Common patterns for using get_full with destructuring to extract only needed parts.

Complete Example: Ordered Processing with Index Tracking

use indexmap::IndexSet;
 
fn process_with_indices() {
    // Track processing order and enable lookup
    
    let mut pending = IndexSet::new();
    pending.insert("task-a");
    pending.insert("task-b");
    pending.insert("task-c");
    pending.insert("task-d");
    
    let mut completed: Vec<String> = Vec::new();
    
    // Process in order
    for (index, &task) in pending.iter().enumerate() {
        println!("Processing task {} at index {}", task, index);
        completed.push(format!("{}-done", task));
    }
    
    // Now we want to check if a task was processed
    // and find its result
    
    let task_to_check = "task-b";
    
    if let Some((index, _task, _)) = pending.get_full(task_to_check) {
        // Found at index, can access parallel structure
        let result = &completed[index];
        println!("Task {} result: {}", task_to_check, result);
    }
    
    // Alternative without get_full: would need separate tracking
    // or iteration to find index
}

get_full enables bridging between value lookup and index-based parallel data.

Summary

use indexmap::IndexSet;
 
fn summary() {
    // get: Simple value lookup
    let set = IndexSet::new();
    
    // Returns Option<&T>
    // Use when you just need to check existence or get a reference
    
    // get_full: Complete metadata lookup
    // Returns Option<(usize, &T, &S::Hash)>
    // Use when you need:
    // - Index for removal operations
    // - Index for parallel array access
    // - Position for iteration/processing
    // - Hash for custom operations
    
    // Performance: Both are O(1) average case
    // get_full has minimal overhead over get
    
    // Choose get_full when:
    // - You need the index for any reason
    // - You need to modify or remove based on value
    // - You maintain parallel data structures
    
    // Choose get when:
    // - You only need the value
    // - You don't need index information
    // - Code clarity (less tuple destructuring)
}

Key insight: get and get_full perform the same underlying hash lookup, but get_full returns the complete insertion metadata—index, value reference, and hash—while get returns only the value. The index enables all positional operations that make IndexSet unique: indexed access, removal by index, and correlation with parallel data structures. Use get when you only need the value; use get_full when the index matters for subsequent operations. The hash, while always returned, is primarily for internal use and rarely needed in application code.