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.
