How does indexmap::IndexSet::insert_full return both insertion status and index for new elements?

insert_full returns a tuple (bool, usize) where the boolean indicates whether the element was newly inserted (true) or already existed (false), and the usize provides the index of that element in the insertion order, enabling callers to know both whether an insertion occurred and where the element lives in the ordered collection. This is more informative than the plain insert method, which only returns a boolean for insertion status without providing the element's position.

Basic IndexSet Insertion

use indexmap::IndexSet;
 
fn basic_insertion() {
    let mut set: IndexSet<&str> = IndexSet::new();
    
    // Plain insert returns only a bool
    let inserted = set.insert("apple");  // true - newly inserted
    assert!(inserted);
    
    let inserted = set.insert("apple");  // false - already exists
    assert!(!inserted);
    
    // But we don't know the index!
    // Where is "apple" in the set?
}

The basic insert method only tells you whether the element was new.

Using insert_full

use indexmap::IndexSet;
 
fn insert_full_example() {
    let mut set: IndexSet<&str> = IndexSet::new();
    
    // insert_full returns (inserted, index)
    let (inserted, index) = set.insert_full("apple");
    
    assert!(inserted);      // true: element was newly inserted
    assert_eq!(index, 0);   // apple is at index 0
    
    let (inserted, index) = set.insert_full("banana");
    assert!(inserted);      // true: newly inserted
    assert_eq!(index, 1);   // banana is at index 1
    
    // Inserting duplicate returns the existing index
    let (inserted, index) = set.insert_full("apple");
    assert!(!inserted);     // false: element already existed
    assert_eq!(index, 0);   // apple is still at index 0
}

insert_full provides both insertion status and element position.

Return Type Breakdown

use indexmap::IndexSet;
 
fn return_type_breakdown() {
    let mut set: IndexSet<i32> = IndexSet::new();
    
    // Return type: (bool, usize)
    // - bool: true if element was newly inserted
    // - usize: index of element in insertion order
    
    // Case 1: New element
    let (was_new, index) = set.insert_full(10);
    // was_new: true (element didn't exist before)
    // index: 0 (first element)
    
    // Case 2: Another new element
    let (was_new, index) = set.insert_full(20);
    // was_new: true
    // index: 1 (second element)
    
    // Case 3: Duplicate element
    let (was_new, index) = set.insert_full(10);
    // was_new: false (element already existed)
    // index: 0 (position of existing element)
    
    // In all cases, you know the element's position
}

The tuple provides complete information about the insertion outcome.

Index Preservation for Duplicates

use indexmap::IndexSet;
 
fn duplicate_behavior() {
    let mut set: IndexSet<&str> = IndexSet::new();
    
    set.insert_full("first");   // (true, 0)
    set.insert_full("second");  // (true, 1)
    set.insert_full("third");   // (true, 2)
    
    // Inserting duplicate returns original index
    let (inserted, index) = set.insert_full("second");
    
    assert!(!inserted);     // Not newly inserted
    assert_eq!(index, 1);   // Original position preserved
    
    // The set order is unchanged: [first, second, third]
    // "second" remains at index 1, not moved or duplicated
}

Duplicate insertions return the original index; the element isn't moved.

Practical Use Case: Tracking Positions

use indexmap::IndexSet;
 
fn tracking_positions() {
    let mut seen: IndexSet<String> = IndexSet::new();
    let mut positions: Vec<usize> = Vec::new();
    
    // Process items and track their positions
    let items = ["apple", "banana", "apple", "cherry", "banana"];
    
    for item in items {
        let (_, index) = seen.insert_full(item.to_string());
        positions.push(index);
    }
    
    // positions: [0, 1, 0, 2, 1]
    // "apple" -> 0, "banana" -> 1, "apple" -> 0, "cherry" -> 2, "banana" -> 1
    
    assert_eq!(&positions, &[0, 1, 0, 2, 1]);
    assert_eq!(seen.len(), 3);  // Only 3 unique items
}

Use insert_full when you need to track element positions across operations.

Comparison with Plain insert

use indexmap::IndexSet;
 
fn insert_comparison() {
    let mut set: IndexSet<i32> = IndexSet::new();
    
    // Plain insert: returns bool only
    let inserted: bool = set.insert(1);
    // No index information
    
    // To get index after insert, need separate call:
    set.insert(2);
    let index = set.get_index_of(&2);  // O(1) lookup
    // But this requires two operations
    
    // insert_full: returns both in one operation
    let (inserted, index) = set.insert_full(3);
    // Both information in single call
    
    // When you need both, insert_full is more efficient
    // Single hash table lookup instead of two
}

insert_full is more efficient than insert + get_index_of.

Getting Element by Index

use indexmap::IndexSet;
 
fn get_by_index() {
    let mut set: IndexSet<&str> = IndexSet::new();
    
    let (_, idx1) = set.insert_full("first");
    let (_, idx2) = set.insert_full("second");
    let (_, idx3) = set.insert_full("third");
    
    // Use index to retrieve element
    let element = set.get_index(idx2);
    assert_eq!(element, Some(&"second"));
    
    // Iterate by index order
    for i in 0..set.len() {
        if let Some(&elem) = set.get_index(i) {
            println!("Index {}: {}", i, elem);
        }
    }
    // Output:
    // Index 0: first
    // Index 1: second
    // Index 2: third
    
    // Indices are stable - they don't change on duplicate insert
}

Use the returned index to retrieve elements by position later.

Building a Symbol Table

use indexmap::IndexSet;
 
struct SymbolTable {
    symbols: IndexSet<String>,
}
 
impl SymbolTable {
    fn new() -> Self {
        SymbolTable {
            symbols: IndexSet::new(),
        }
    }
    
    // Intern a symbol, returning its index
    fn intern(&mut self, symbol: &str) -> usize {
        let (_, index) = self.symbols.insert_full(symbol.to_string());
        index
    }
    
    // Look up symbol by index
    fn get(&self, index: usize) -> Option<&str> {
        self.symbols.get_index(index).map(|s| s.as_str())
    }
    
    // Look up index by symbol
    fn lookup(&self, symbol: &str) -> Option<usize> {
        self.symbols.get_index_of(symbol)
    }
}
 
fn symbol_table_example() {
    let mut table = SymbolTable::new();
    
    let idx1 = table.intern("foo");  // 0
    let idx2 = table.intern("bar");  // 1
    let idx3 = table.intern("foo");  // 0 again (same symbol)
    
    assert_eq!(idx1, 0);
    assert_eq!(idx2, 1);
    assert_eq!(idx3, 0);  // Same as idx1
    
    assert_eq!(table.get(0), Some("foo"));
    assert_eq!(table.get(1), Some("bar"));
    assert_eq!(table.lookup("foo"), Some(0));
}

insert_full is ideal for interning patterns where you need stable indices.

Tracking First Occurrence

use indexmap::IndexSet;
 
fn first_occurrence() {
    let mut first_seen: IndexSet<String> = IndexSet::new();
    let mut occurrence_order: Vec<usize> = Vec::new();
    
    let items = ["apple", "banana", "apple", "cherry", "banana", "date"];
    
    for item in items {
        let (is_new, index) = first_seen.insert_full(item.to_string());
        
        if is_new {
            println!("First occurrence of '{}' at index {}", item, index);
        } else {
            println!("'{}' seen before at index {}", item, index);
        }
        
        occurrence_order.push(index);
    }
    
    // Output:
    // First occurrence of 'apple' at index 0
    // First occurrence of 'banana' at index 1
    // 'apple' seen before at index 0
    // First occurrence of 'cherry' at index 2
    // 'banana' seen before at index 1
    // First occurrence of 'date' at index 3
    
    assert_eq!(first_seen.len(), 4);  // 4 unique items
}

Use the boolean to detect first occurrences while tracking positions.

Building Reverse Lookup

use indexmap::IndexSet;
 
fn reverse_lookup() {
    let mut set: IndexSet<&str> = IndexSet::new();
    
    // Build set and track indices
    let indices: Vec<usize> = ["apple", "banana", "cherry", "apple"]
        .iter()
        .map(|&item| {
            let (_, index) = set.insert_full(item);
            index
        })
        .collect();
    
    // indices: [0, 1, 2, 0]
    
    // Use indices to reference items compactly
    // Instead of storing strings, store indices
    let compact: Vec<usize> = indices.clone();
    
    // Reconstruct from indices
    let reconstructed: Vec<&str> = compact
        .iter()
        .map(|&idx| *set.get_index(idx).unwrap())
        .collect();
    
    assert_eq!(reconstructed, vec!["apple", "banana", "cherry", "apple"]);
}

Store indices instead of strings for compact representation.

Difference from IndexMap

use indexmap::{IndexMap, IndexSet};
 
fn set_vs_map() {
    // IndexSet stores values only
    let mut set: IndexSet<&str> = IndexSet::new();
    let (inserted, index) = set.insert_full("value");
    
    // IndexMap stores key-value pairs
    let mut map: IndexMap<&str, i32> = IndexMap::new();
    let (inserted, index) = map.insert_full("key", 42);
    // Returns (was_new, index) for the KEY
    
    // Both preserve insertion order
    // Both return (bool, usize) from insert_full
    
    // Set: only values, no keys
    // Map: key-value pairs, index for key
}

Both IndexSet and IndexMap provide insert_full with similar semantics.

Performance Characteristics

use indexmap::IndexSet;
 
fn performance() {
    let mut set: IndexSet<i32> = IndexSet::new();
    
    // insert_full is O(1) average case
    // - Hash computation for element
    // - Hash table lookup
    // - Index determination
    
    // Equivalent to:
    // set.insert(element);           // O(1)
    // set.get_index_of(&element);    // O(1)
    // But combined into single operation
    
    // Single hash table lookup for both operations
    // More efficient than separate calls
    
    // Inserting 1 million elements:
    for i in 0..1_000_000 {
        let (is_new, index) = set.insert_full(i);
        // is_new is always true (unique elements)
        // index is the insertion order
    }
    
    // Memory: O(n) for hash table + order array
}

insert_full is O(1) amortized, same as insert, but returns more information.

Complete Example: Deduplication with Indices

use indexmap::IndexSet;
 
fn deduplicate_with_indices() {
    let data = vec![
        "apple", "banana", "apple", "cherry", 
        "banana", "date", "apple"
    ];
    
    let mut unique: IndexSet<&str> = IndexSet::new();
    let mut original_indices: Vec<usize> = Vec::new();
    
    for item in &data {
        let (is_new, index) = unique.insert_full(*item);
        original_indices.push(index);
        
        if is_new {
            println!("New unique item: '{}' at index {}", item, index);
        }
    }
    
    // unique: {"apple", "banana", "cherry", "date"}
    // original_indices: [0, 1, 0, 2, 1, 3, 0]
    
    // Reconstruct unique items in order
    println!("Unique items in order:");
    for (index, item) in unique.iter().enumerate() {
        println!("  {}: {}", index, item);
    }
    
    // Map original data to indices
    println!("Original data as indices:");
    println!("  {:?}", original_indices);
    
    // Useful for:
    // - Compression (store indices instead of strings)
    // - Canonical representation (unique items)
    // - Tracking (original position for each item)
}

Combine uniqueness and position tracking in a single operation.

Synthesis

Quick reference:

Method Return Type Information
insert bool Was element newly inserted?
insert_full (bool, usize) Inserted status + element index

Return tuple meaning:

(bool, usize) Meaning
(true, N) Element newly inserted at index N
(false, N) Element existed at index N

Common patterns:

use indexmap::IndexSet;
 
fn patterns() {
    let mut set: IndexSet<String> = IndexSet::new();
    
    // Pattern 1: Get index after insertion
    let (_, index) = set.insert_full("item".to_string());
    // Use index for lookup or reference
    
    // Pattern 2: Check if new and get index
    let (is_new, index) = set.insert_full("item".to_string());
    if is_new {
        println!("First occurrence at index {}", index);
    } else {
        println!("Already seen at index {}", index);
    }
    
    // Pattern 3: Ignore insertion status, just get index
    let (_, index) = set.insert_full("item".to_string());
    // Useful when you always need the index
    
    // Pattern 4: Track all occurrences
    let indices: Vec<usize> = items
        .iter()
        .map(|item| {
            let (_, idx) = set.insert_full(item.clone());
            idx
        })
        .collect();
}

Key insight: IndexSet::insert_full returns a (bool, usize) tuple that tells you two things simultaneously: whether the element was newly inserted (the boolean) and the element's position in the insertion order (the index). When inserting a new element, the boolean is true and the index is the next available position. When inserting a duplicate, the boolean is false and the index is the position of the existing element. This is more efficient than calling insert followed by get_index_of because both operations require hash table lookups, and insert_full performs just one lookup. The returned index is always stable—it doesn't change on duplicate insertions because IndexSet preserves insertion order and doesn't move existing elements. Use insert_full when you need to track element positions, build compact representations using indices instead of values, implement symbol tables or interning, or detect first occurrences while recording positions. Use plain insert when you only need to know whether the element was new and don't care about its position.