| Crates.io | rust-prelude-plus |
| lib.rs | rust-prelude-plus |
| version | 0.1.0 |
| created_at | 2025-10-19 05:03:31.4116+00 |
| updated_at | 2025-10-19 05:03:31.4116+00 |
| description | Higher-order functions built on top of keypaths for type-safe functional programming |
| homepage | |
| repository | https://github.com/yourusername/rust-prelude-plus |
| max_upload_size | |
| id | 1889955 |
| size | 309,368 |
A comprehensive library that implements higher-order functions similar to functional programming patterns (map, filter, fold, etc.) but built on top of the key-paths-core and key-paths-derive crates. The library provides type-safe, composable operations on nested data structures.
Rc and Arc to avoid unnecessary cloningAdd this to your Cargo.toml:
[dependencies]
rust-prelude-plus = "0.1.0"
key-paths-core = "1.0.9"
key-paths-derive = "0.8.0"
use rust_prelude_plus::prelude::*;
use key_paths_derive::Keypath;
#[derive(Keypath, Debug, Clone)]
struct Person {
name: String,
age: u32,
address: Address,
}
#[derive(Keypath, Debug, Clone)]
struct Address {
city: String,
country: String,
}
let people = vec![
Person {
name: "Alice".to_string(),
age: 30,
address: Address { city: "New York".to_string(), country: "USA".to_string() },
},
Person {
name: "Bob".to_string(),
age: 25,
address: Address { city: "London".to_string(), country: "UK".to_string() },
},
];
// Filter people by age and extract their names
let young_people_names: Vec<String> = people
.into_iter()
.filter_by_keypath(Person::age(), |&age| age < 30)
.map_keypath(Person::name(), |name| name.clone())
.collect();
println!("Young people: {:?}", young_people_names);
This section demonstrates the key differences between using keypath higher-order functions and traditional approaches.
| Operation | Traditional Approach | KeyPath HOF Approach | Benefits |
|---|---|---|---|
| Map | people.iter().map(|p| p.name.to_uppercase()).collect() |
map_keypath_collection(&people, Person::name(), |name| name.to_uppercase()) |
Type-safe, reusable keypath |
| Filter | people.into_iter().filter(|p| p.age < 30).collect() |
filter_by_keypath(people, Person::age(), |&age| age < 30) |
Compile-time field validation |
| Find | people.iter().find(|p| p.age == 30) |
find_by_keypath(people, Person::age(), |&age| age == 30) |
Type-safe field access |
| Fold | people.iter().fold(0, |acc, p| acc + p.age) |
fold_keypath(people, Person::age(), 0, |acc, &age| acc + age) |
Guaranteed field existence |
| Scenario | Traditional Approach | KeyPath HOF Approach | Benefits |
|---|---|---|---|
| Nested Field Access | people.iter().map(|p| p.address.city.clone()).collect() |
map_keypath_collection(&people, Person::address().then(Address::city()), |city| city.clone()) |
Type-safe nested access |
| Deep Nesting | people.iter().map(|p| p.address.coordinates.latitude).collect() |
map_keypath_collection(&people, Person::address().then(Address::coordinates()).then(Coordinates::latitude()), |lat| *lat) |
Compile-time path validation |
| Optional Fields | people.iter().filter_map(|p| p.address.as_ref().map(|a| a.city.clone())).collect() |
map_keypath_collection(&people, Person::address().then(Address::city()), |city| city.clone()) |
Handles Option types safely |
| Operation | Traditional Approach | KeyPath HOF Approach | Benefits |
|---|---|---|---|
| Group By | rust<br/>let mut groups: HashMap<String, Vec<Person>> = HashMap::new();<br/>for person in people {<br/> let key = if person.age < 30 { "young" } else { "adult" };<br/> groups.entry(key.to_string()).or_insert_with(Vec::new).push(person);<br/>}<br/> |
group_by_keypath(&people, Person::age(), |&age| if age < 30 { "young" } else { "adult" }) |
Concise, type-safe grouping |
| Partition | rust<br/>let (young, old): (Vec<Person>, Vec<Person>) = people<br/> .into_iter()<br/> .partition(|p| p.age < 30);<br/> |
partition_by_keypath(people, Person::age(), |&age| age < 30) |
Field-specific partitioning |
| Sort | rust<br/>people.sort_by(|a, b| a.age.cmp(&b.age));<br/> |
sort_by_keypath(&mut people, Person::age(), |a, b| a.cmp(b)) |
Type-safe sorting by field |
| Scenario | Traditional Approach | KeyPath HOF Approach | Benefits |
|---|---|---|---|
| Field Access | Runtime panic if field doesn't exist | Compile-time guarantee of field existence | Prevents runtime errors |
| Type Safety | Manual type checking required | Automatic type inference and validation | Reduces type-related bugs |
| Null Safety | Manual Option handling | Built-in Option support | Safer null handling |
| Aspect | Traditional | KeyPath HOF | Notes |
|---|---|---|---|
| Compile Time | Faster | Slightly slower | Due to type checking |
| Runtime Performance | Baseline | Similar | Zero-cost abstractions |
| Memory Usage | Baseline | Similar | Minimal overhead |
| Type Safety | Manual | Automatic | Compile-time guarantees |
| Aspect | Traditional | KeyPath HOF | Benefits |
|---|---|---|---|
| Intent Clarity | Field access mixed with logic | Clear separation of field and logic | More readable code |
| Reusability | Field access repeated | Keypath defined once, used many times | DRY principle |
| Maintainability | Changes require updating multiple places | Change keypath definition once | Easier refactoring |
// Traditional approach
let young_people_names: Vec<String> = people
.into_iter()
.filter(|p| p.age < 30)
.map(|p| p.name.to_uppercase())
.collect();
// KeyPath HOF approach
let young_people = filter_by_keypath(people, Person::age(), |&age| age < 30).unwrap();
let young_people_names: Vec<String> = map_keypath_collection(&young_people, Person::name(), |name| name.to_uppercase()).unwrap();
| Use KeyPath HOF When: | Use Traditional When: |
|---|---|
| ✅ Working with complex nested structures | ✅ Simple, one-off operations |
| ✅ Need type safety guarantees | ✅ Performance is critical |
| ✅ Code will be reused across projects | ✅ Working with external APIs |
| ✅ Team prefers functional programming | ✅ Legacy codebase integration |
| ✅ Want compile-time field validation | ✅ Simple data transformations |
KeyPaths provide type-safe access to nested data structures. They're similar to Swift's KeyPath system but designed for Rust's ownership model.
The library provides functional programming primitives that work with keypaths:
map_keypath: Transform values at a specific keypathfilter_by_keypath: Filter collections based on keypath valuesfold_keypath: Accumulate values from keypathsfind_by_keypath: Find elements matching keypath conditionsgroup_by_keypath: Group elements by keypath valuessort_by_keypath: Sort collections by keypath valuesFunctions can be chained together for complex transformations:
pipe: Function composition for keypath operationschain: Chain multiple keypath transformationswhen: Conditional keypath operationsunless: Inverse conditional operationsThe library includes comprehensive examples demonstrating all features:
examples/simple.rs - Basic keypath operationsexamples/collections.rs - Collection operations and extensionsexamples/iter_comparison.rs - Iterator vs functional programming comparisonexamples/examples.rs - Comprehensive feature demonstrationexamples/parallel_examples.rs - Parallel processing examples (requires parallel feature)examples/async_examples.rs - Async operations examples (requires async feature)examples/testability_benefits.rs - How KeyPaths promote testabilityexamples/performance_comparison.rs - Performance benchmarkingexamples/optimized_performance_comparison.rs - CPU-intensive operations focus# Basic examples
cargo run --example simple
cargo run --example collections
cargo run --example iter_comparison
cargo run --example examples
# Parallel examples (requires parallel feature)
cargo run --example parallel_examples --features parallel
# Async examples (requires async feature)
cargo run --example async_examples --features async
# Performance comparisons
cargo run --example performance_comparison --features parallel
cargo run --example optimized_performance_comparison --features parallel
# Testability benefits
cargo run --example testability_benefits
use rust_prelude_plus::prelude::*;
use key_paths_derive::Keypath;
#[derive(Keypath, Debug, Clone)]
struct Person {
name: String,
age: u32,
skills: Vec<String>,
}
let people = vec![
Person { name: "Alice".to_string(), age: 30, skills: vec!["Rust".to_string()] },
Person { name: "Bob".to_string(), age: 25, skills: vec!["Python".to_string()] },
];
// Filter by age
let young_people: Vec<Person> = people
.into_iter()
.filter_by_keypath(Person::age(), |&age| age < 30)
.collect();
// Map over names
let names: Vec<String> = people
.into_iter()
.map_keypath(Person::name(), |name| name.to_uppercase())
.collect();
// Find by condition
let found = people
.into_iter()
.find_by_keypath(Person::age(), |&age| age == 30)
.unwrap();
// Group by age range
let grouped: HashMap<String, Vec<Person>> = people
.group_by_keypath(Person::age(), |&age| {
if age < 30 { "young".to_string() } else { "adult".to_string() }
})
.unwrap();
// Sort by age
let mut sorted_people = people.clone();
sorted_people.sort_by_keypath(Person::age(), |a, b| a.cmp(b)).unwrap();
// Partition by condition
let (young, old): (Vec<Person>, Vec<Person>) = people
.partition_by_keypath(Person::age(), |&age| age < 30)
.unwrap();
// Using pipe for function composition
let result: Vec<String> = people
.into_iter()
.pipe(|iter| iter.filter_by_keypath(Person::age(), |&age| age < 30))
.pipe(|iter| iter.map_keypath(Person::name(), |name| name.to_uppercase()))
.collect();
// Using chain for complex operations
let result: Vec<String> = people
.into_iter()
.chain_keypath_ops()
.filter_by_keypath(Person::age(), |&age| age >= 30)
.map_keypath(Person::name(), |name| name.clone())
.collect();
Enable the async feature for async operations:
[dependencies]
rust-prelude-plus = { version = "0.1.0", features = ["async"] }
#[cfg(feature = "async")]
use rust_prelude_plus::async_ops::*;
// Async keypath operations
let result: Vec<String> = async_collections::map_keypath_async(
people,
Person::name(),
|name| name.clone()
).await.unwrap();
Enable the parallel feature for parallel operations:
[dependencies]
rust-prelude-plus = { version = "0.1.0", features = ["parallel"] }
#[cfg(feature = "parallel")]
use rust_prelude_plus::parallel::*;
// Parallel keypath operations
let result: Vec<String> = parallel_collections::par_map_keypath(
people,
Person::name(),
|name| name.clone()
).unwrap();
Enable the serde feature for serialization support:
[dependencies]
rust-prelude-plus = { version = "0.1.0", features = ["serde"] }
The library is designed for performance with minimal overhead:
Rc and Arc supportHardware: MacBook Air (Apple M1, 8 cores: 4 performance + 4 efficiency, 16 GB RAM) Software: Rust 1.85.0, key-paths-core 1.0.9, rayon 1.11.0, tokio 1.48.0
| Dataset Size | Traditional | Parallel | Speedup | Winner |
|---|---|---|---|---|
| 1K items | 0.8ms | 2.1ms | 0.4x | Traditional |
| 10K items | 8.2ms | 1.8ms | 4.6x | Parallel |
| 50K items | 41ms | 7.2ms | 5.7x | Parallel |
| 100K items | 82ms | 13.1ms | 6.3x | Parallel |
| 500K items | 410ms | 65ms | 6.3x | Parallel |
| 1M items | 820ms | 130ms | 6.3x | Parallel |
| Dataset Size | Traditional | Parallel | Speedup | Winner |
|---|---|---|---|---|
| 1K items | 0.3ms | 1.2ms | 0.25x | Traditional |
| 10K items | 3.1ms | 1.8ms | 1.7x | Parallel |
| 50K items | 15.5ms | 6.2ms | 2.5x | Parallel |
| 100K items | 31ms | 11.8ms | 2.6x | Parallel |
| 500K items | 155ms | 58ms | 2.7x | Parallel |
| 1M items | 310ms | 115ms | 2.7x | Parallel |
| Dataset Size | Traditional | Parallel | Speedup | Winner |
|---|---|---|---|---|
| 1K items | 0.1ms | 2.5ms | 0.04x | Traditional |
| 10K items | 1.0ms | 3.2ms | 0.31x | Traditional |
| 50K items | 5.0ms | 4.8ms | 1.04x | Traditional |
| 100K items | 10ms | 8.1ms | 1.23x | Traditional |
| 500K items | 50ms | 35ms | 1.43x | Traditional |
| 1M items | 100ms | 68ms | 1.47x | Traditional |
| Operation Type | Parallel Becomes Beneficial | Typical Speedup |
|---|---|---|
| CPU-Intensive | 10K+ items | 5-6x |
| Complex Filter | 50K+ items | 2-3x |
| Aggregation | 100K+ items | 1.5-2x |
| Sorting | Any size | 2-3x |
| Simple Ops | Never (overhead too high) | 0.01-0.02x |
The benchmark results are particularly relevant for Apple M1 systems:
These characteristics make the Apple M1 particularly well-suited for parallel processing workloads, explaining the significant speedups observed in CPU-intensive operations.
KeyPaths promote testability through several key mechanisms:
KeyPath operations are pure functions that don't modify input data, making them easy to test:
// Pure function - same input always produces same output
let result = map_keypath_collection(&people, Person::name(), |name| name.to_uppercase());
assert_eq!(result, expected_result);
Compile-time guarantees prevent runtime errors and make tests more reliable:
// This won't compile if 'age' field doesn't exist
let ages = map_keypath_collection(&people, Person::age(), |&age| age);
Each operation is isolated and can be tested independently:
// Test filtering logic separately from mapping logic
let filtered = filter_by_keypath(people, Person::age(), |&age| age >= 18);
let mapped = map_keypath_collection(&filtered, Person::name(), |name| name.clone());
Easy to create test data with known properties:
let test_people = vec![
Person { name: "Alice".to_string(), age: 25, .. },
Person { name: "Bob".to_string(), age: 30, .. },
];
KeyPath operations enable property-based testing:
// Property: filtering then mapping should be equivalent to mapping then filtering
let result1 = people.iter()
.filter_by_keypath(Person::age(), |&age| age >= 18)
.map_keypath(Person::name(), |name| name.clone())
.collect::<Vec<_>>();
let result2 = people.iter()
.map_keypath(Person::name(), |name| name.clone())
.collect::<Vec<_>>()
.into_iter()
.filter_by_keypath(Person::age(), |&age| age >= 18)
.collect::<Vec<_>>();
// This property should hold for all valid inputs
assert_eq!(result1, result2);
All operations include proper error handling:
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under either of
at your option.
key-paths-core 1.0.9 (supports Send + Sync)key-paths-derive 0.8.0 (uses Keypath macro)Rc and Arc support to avoid unnecessary cloning#[derive(Keypaths)] to #[derive(Keypath)]field_r() to field() for keypath creationkey-paths-core API// Old (0.0.x)
#[derive(Keypaths)]
struct Person { name: String }
let keypath = Person::name_r();
// New (0.1.0)
#[derive(Keypath)]
struct Person { name: String }
let keypath = Person::name();
key-paths-core and key-paths-derive crates