collect_failable 0.12.3

A trait for collecting values into a container which has an invariant to uphold and whose construction may fail
Documentation

Build Docs Crates.io dependency status codecov GitHub License

collect_failable

A set of traits for collecting values into containers that must uphold invariants during construction or extension. These traits let you propagate structured errors instead of panicking or silently discarding data. Examples include preventing duplicate keys in a HashMap or respecting capacity limits in types like ArrayVec.

Features

This crate provides several complementary traits for failable collection:

  • TryFromIterator – build a new container from an iterator, returning an error when invariants can't be satisfied.
  • TryCollectEx – ergonomic collect-style extension for iterator consumers, forwarding a call to TryFromIterator.
  • TryExtend – fallible extend operations with strong and basic error guarantees variants.
  • TryExtendOne – extend with a single item, providing cleaner error types and strong guarantees.
  • TryUnzipunzip an iterator of pairs into two fallible containers.
  • FoldMutfold-style extension building a collection via mutation rather than move.

Additionally, several implementations are provided for common and popular containers. See the implementations section for more details.

Installation

It's on crates.io.

Usage

TryFromIterator and TryCollectEx

Construct a container from an iterator, with errors for invalid input. This behaves like FromIterator but returns Result<Self, E> instead of panicking or ignoring failures.

use std::collections::HashMap;
use collect_failable::{TryFromIterator, TryCollectEx};

// can be called on any type that implements TryFromIterator
let err = HashMap::try_from_iter([(1, 2), (2, 3), (1, 4), (3, 5)]).expect_err("should be Err");
assert_eq!(err.item.0, 1); // err.item is the colliding (K, V) tuple

// For `HashMap` the error contains all the data necessary to reconstruct the consumed iterator
let all_items: Vec<_> = err.into_iter().collect();
assert_eq!(all_items.len(), 4); // all 4 original items are present, though order is not guaranteed

// or collected via the TryCollectEx trait a turbofish may be necessary to disambiguate
let map = [(1, 2), (2, 3)].into_iter().try_collect_ex::<HashMap<_, _>>().expect("should be Ok");
assert_eq!(map, HashMap::from([(1, 2), (2, 3)]));

// or type ascription. Note the Result type can be inferred, just not the collection type.
let map: HashMap<_, _> = [(1, 2), (2, 3)].into_iter().try_collect_ex().expect("should be Ok");
assert_eq!(map, HashMap::from([(1, 2), (2, 3)]));

TryExtend and TryExtendSafe

Extend an existing container with items that may violate its invariants. Two different trait exposes two styles of error behavior:

  • TryExtendSafestrong guarantee on an error, the container must remain unchanged.
  • TryExtendbasic guarantee the container may have partially ingested items, but must remain valid.

Use TryExtendSafe if you must avoid mutation on failure; otherwise, prefer the faster TryExtend.

use std::collections::HashMap;
use collect_failable::TryExtendSafe;

let mut map = HashMap::new();
map.try_extend_safe([(1, 2), (2, 3)]).expect("should be Ok");
assert_eq!(map, HashMap::from([(1, 2), (2, 3)]));

// on a failure, the container is not modified
map.try_extend_safe([(1, 3)]).expect_err("should be Err");
assert_eq!(map, HashMap::from([(1, 2), (2, 3)]));

TryExtendOne

Extend a collection with a single item. This trait always provides a strong guarantee: on failure, the collection remains unchanged. Implemented as a seperate trait with no default implementation due to limitations imposed by the trait definition.

TryUnzip

Fallible equivalent of Iterator::unzip. Given an iterator of (A, B) items, produce two collections that implement Default + TryExtend, stopping on the first failure.

Allows unzipping an iterator of pairs into two collections that implement Default and TryExtend.

This is analogous to Iterator::unzip, except allows for failable construction.

use std::collections::{BTreeSet, HashSet};
use collect_failable::TryUnzip;

// Unzip into two different container types
let data = vec![(1, 'a'), (2, 'b'), (3, 'c')];
let (nums, chars): (BTreeSet<i32>, HashSet<char>) = data.into_iter().try_unzip().expect("should be ok");

assert_eq!(nums, BTreeSet::from([1, 2, 3]));
assert_eq!(chars, HashSet::from(['a', 'b', 'c']));

Implementations

Implementations for various containers are provided.

Tuple Implementations

Tuples of size 2 implement both TryFromIterator and TryExtend when their inner types do. Errors respect the guarantee of each component, mirroring the behavior of the std tuple implementations—but with fallibility. Implementing TryExtendSafe or TryExtendOne is not possible since they cannot provide atomic single-item guarantees within the trait definition.

Array Implementation

Arrays implement TryFromIterator for iterators that yield exactly the right number of elements. This uses unsafe internally and is gated behind the unsafe feature (enabled by default).

Result Implementation

TryFromIterator is implemented for Result<C, E>, where C implements TryFromIterator<T>, similar to the FromIterator implementation for Result. This allows short-circuiting collection of failable values into a container whose construction is also failable.