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.
