Loading page…
Rust walkthroughs
Loading page…
The predicates crate provides a fluent, composable API for creating reusable test assertions and conditions. Instead of writing repetitive assertion logic, you build predicates that can be combined with boolean operators, applied to different types, and used for both testing and runtime filtering. This leads to more readable tests and reduces boilerplate code.
Key concepts:
and(), or(), not() for combining predicates# Cargo.toml
[dev-dependencies]
predicates = "3.0"use predicates::prelude::*;
fn main() {
let predicate = predicate::eq(5);
assert!(predicate.eval(&5));
assert!(!predicate.eval(&10));
let combined = predicate::gt(3).and(predicate::lt(10));
assert!(combined.eval(&5));
println!("All tests passed!");
}use predicates::prelude::*;
fn main() {
// Equality
let equals_five = predicate::eq(5);
println!("5 equals 5: {}", equals_five.eval(&5));
println!("10 equals 5: {}", equals_five.eval(&10));
// Comparison
let greater_than_three = predicate::gt(3);
println!("5 > 3: {}", greater_than_three.eval(&5));
println!("2 > 3: {}", greater_than_three.eval(&2));
let less_than_ten = predicate::lt(10);
let between = predicate::gt(3).and(predicate::lt(10));
println!("5 is between 3 and 10: {}", between.eval(&5));
// Inequality
let not_five = predicate::ne(5);
println!("7 != 5: {}", not_five.eval(&7));
// Greater or equal, less or equal
let at_least_five = predicate::ge(5);
println!("5 >= 5: {}", at_least_five.eval(&5));
let at_most_ten = predicate::le(10);
println!("10 <= 10: {}", at_most_ten.eval(&10));
}use predicates::prelude::*;
fn main() {
let text = "Hello, World!";
// Exact match
let is_hello = predicate::eq("Hello");
println!("'Hello' matches: {}", is_hello.eval(&"Hello"));
println!("'Hello, World!' matches: {}", is_hello.eval(&text));
// Contains substring
let contains_world = predicate::str::contains("World");
println!("Contains 'World': {}", contains_world.eval(text));
println!("Contains 'Rust': {}", contains_world.eval("Hello, Rust!"));
// Starts with / ends with
let starts_hello = predicate::str::starts_with("Hello");
println!("Starts with 'Hello': {}", starts_hello.eval(text));
let ends_world = predicate::str::ends_with("World!");
println!("Ends with 'World!': {}", ends_world.eval(text));
// Regex matching
let is_email = predicate::str::is_match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
println!("Is email (valid): {}", is_email.eval("user@example.com"));
println!("Is email (invalid): {}", is_email.eval("not-an-email"));
// Case-insensitive contains
let contains_case_insensitive = predicate::str::contains("rust").trim();
let contains_normalized = predicate::str::similar("rust");
}use predicates::prelude::*;
fn main() {
// AND
let between_5_and_10 = predicate::ge(5).and(predicate::le(10));
println!("5 is between 5-10: {}", between_5_and_10.eval(&5));
println!("7 is between 5-10: {}", between_5_and_10.eval(&7));
println!("15 is between 5-10: {}", between_5_and_10.eval(&15));
// OR
let less_than_5_or_greater_than_10 = predicate::lt(5).or(predicate::gt(10));
println!("3 is <5 or >10: {}", less_than_5_or_greater_than_10.eval(&3));
println!("7 is <5 or >10: {}", less_than_5_or_greater_than_10.eval(&7));
println!("15 is <5 or >10: {}", less_than_5_or_greater_than_10.eval(&15));
// NOT
let not_zero = predicate::ne(0).not();
println!("0 is not zero: {}", not_zero.eval(&0));
println!("5 is not zero: {}", not_zero.eval(&5));
// Complex combinations
let valid_age = predicate::ge(0).and(predicate::le(120));
let is_working_age = predicate::ge(18).and(predicate::le(65));
let is_senior = predicate::gt(65).and(predicate::le(120));
let is_minor = predicate::lt(18);
println!("\nAge classification:");
for age in [10, 25, 70, 130, -5] {
println!(" Age {}:", age);
println!(" Valid: {}", valid_age.eval(&age));
println!(" Working age: {}", is_working_age.eval(&age));
println!(" Senior: {}", is_senior.eval(&age));
println!(" Minor: {}", is_minor.eval(&age));
}
}use predicates::prelude::*;
use std::path::Path;
fn main() {
// Create test paths
let existing_file = Path::new("src/main.rs");
let non_existent = Path::new("nonexistent.txt");
// Exists
let exists = predicate::path::exists();
println!("src/main.rs exists: {}", exists.eval(existing_file));
println!("nonexistent.txt exists: {}", exists.eval(non_existent));
// Is file / is directory
let is_file = predicate::path::is_file();
let is_dir = predicate::path::is_dir();
println!("src/main.rs is file: {}", is_file.eval(existing_file));
println!("src/main.rs is dir: {}", is_dir.eval(existing_file));
println!("src is dir: {}", is_dir.eval(Path::new("src")));
// File extension
let is_rust = predicate::path::ext().eq("rs");
println!("src/main.rs is Rust: {}", is_rust.eval(existing_file));
// File name pattern
let has_main = predicate::path::file_name().contains("main");
println!("Contains 'main' in filename: {}", has_main.eval(existing_file));
}use predicates::prelude::*;
fn main() {
// Approximate equality for floats
let near_pi = predicate::float::is_close(3.14159, 0.001);
println!("3.14159 is near π: {}", near_pi.eval(&3.14159));
println!("3.141 is near π: {}", near_pi.eval(&3.141));
println!("3.14 is near π: {}", near_pi.eval(&3.14));
println!("3.0 is near π: {}", near_pi.eval(&3.0));
// Greater than with tolerance
let greater_than_10 = predicate::gt(10.0);
println!("10.001 > 10.0: {}", greater_than_10.eval(&10.001));
println!("9.999 > 10.0: {}", greater_than_10.eval(&9.999));
}use predicates::prelude::*;
fn process_number(n: i32) -> i32 {
n * 2 + 1
}
#[cfg(test)]
mod tests {
use super::*;
use predicates::prelude::*;
#[test]
fn test_process_number() {
let result = process_number(5);
// Using predicate with assert!
assert!(predicate::eq(11).eval(&result));
// Chained predicates
let is_odd_and_positive = predicate::gt(0).and(|n: &i32| n % 2 == 1);
assert!(is_odd_and_positive.eval(&result));
}
#[test]
fn test_result_range() {
let result = process_number(10);
// Result should be between 0 and 100
let in_range = predicate::ge(0).and(predicate::le(100));
assert!(in_range.eval(&result));
}
}
fn main() {
println!("Run with: cargo test");
}use predicates::prelude::*;
use std::fmt;
// Method 1: Closure-based predicate
fn is_even() -> impl Predicate<i32> {
predicate::function(|n: &i32| n % 2 == 0)
}
// Method 2: Struct implementing Predicate
struct IsPrime;
impl Predicate<i32> for IsPrime {
fn eval(&self, n: &i32) -> bool {
if *n <= 1 {
return false;
}
if *n <= 3 {
return true;
}
if *n % 2 == 0 || *n % 3 == 0 {
return false;
}
let mut i = 5;
while i * i <= *n {
if *n % i == 0 || *n % (i + 2) == 0 {
return false;
}
i += 6;
}
true
}
fn find_case<'a>(&'a self, expected: bool, variable: &i32) -> Option<Case<'a, i32>> {
let result = self.eval(variable);
if result == expected {
None
} else {
Some(Case::new(Some(variable), expected))
}
}
}
impl fmt::Display for IsPrime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "var.is_prime()")
}
}
// Method 3: Using predicate::function with description
fn is_positive() -> impl Predicate<i32> {
predicate::function(|n: &i32| *n > 0).with_fn(|_| "var.is_positive()")
}
fn main() {
// Test is_even
let even = is_even();
println!("4 is even: {}", even.eval(&4));
println!("7 is even: {}", even.eval(&7));
// Test IsPrime
let prime = IsPrime;
for n in [1, 2, 3, 4, 5, 11, 12, 17, 18] {
println!("{} is prime: {}", n, prime.eval(&n));
}
// Combine custom predicates
let even_and_positive = is_even().and(is_positive());
for n in [-2, -1, 0, 1, 2, 3, 4] {
println!("{} is even and positive: {}", n, even_and_positive.eval(&n));
}
}use predicates::prelude::*;
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
// Filter with predicate
let greater_than_10 = predicate::gt(10);
let big_numbers: Vec<&i32> = numbers.iter()
.filter(|n| greater_than_10.eval(n))
.collect();
println!("Numbers > 10: {:?}", big_numbers);
// Complex predicate
let is_multiple_of_3_or_5 = predicate::function(|n: &i32| n % 3 == 0 || n % 5 == 0);
let multiples: Vec<&i32> = numbers.iter()
.filter(|n| is_multiple_of_3_or_5.eval(n))
.collect();
println!("Multiples of 3 or 5: {:?}", multiples);
// String filtering
let words = vec!["apple", "banana", "cherry", "date", "elderberry"];
let starts_with_vowel = predicate::str::starts_with("a")
.or(predicate::str::starts_with("e"))
.or(predicate::str::starts_with("i"))
.or(predicate::str::starts_with("o"))
.or(predicate::str::starts_with("u"));
let vowel_words: Vec<&&str> = words.iter()
.filter(|w| starts_with_vowel.eval(w))
.collect();
println!("Words starting with vowel: {:?}", vowel_words);
// Filter with length predicate
let long_word = predicate::function(|s: &&str| s.len() > 5);
let long_words: Vec<&&str> = words.iter()
.filter(|w| long_word.eval(w))
.collect();
println!("Words longer than 5 chars: {:?}", long_words);
}use predicates::prelude::*;
fn main() {
// BoxPredicate allows dynamic dispatch and storage
let mut predicates: Vec<BoxPredicate<&str>> = Vec::new();
predicates.push(BoxPredicate::new(predicate::str::contains("hello")));
predicates.push(BoxPredicate::new(predicate::str::starts_with("test")));
predicates.push(BoxPredicate::new(predicate::str::ends_with("world")));
let test_cases = vec![
"hello world",
"test case",
"big world",
"test hello world",
];
for test in test_cases {
println!("Testing: '{}'", test);
for (i, pred) in predicates.iter().enumerate() {
println!(" Predicate {}: {}", i, pred.eval(&test));
}
}
}use predicates::prelude::*;
struct User {
name: String,
age: u32,
email: String,
}
impl User {
fn new(name: &str, age: u32, email: &str) -> Self {
Self {
name: name.to_string(),
age,
email: email.to_string(),
}
}
fn is_adult(&self) -> bool {
self.age >= 18
}
}
// Predicates for User
fn is_adult_user() -> impl Predicate<User> {
predicate::function(|u: &User| u.is_adult())
}
fn has_valid_email() -> impl Predicate<User> {
predicate::function(|u: &User| u.email.contains('@'))
}
fn is_named(expected: &str) -> impl Predicate<User> + '_ {
predicate::function(move |u: &User| u.name == expected)
}
fn main() {
let alice = User::new("Alice", 25, "alice@example.com");
let bob = User::new("Bob", 15, "bob@example.com");
let charlie = User::new("Charlie", 30, "invalid-email");
// Test adult
println!("Alice is adult: {}", is_adult_user().eval(&alice));
println!("Bob is adult: {}", is_adult_user().eval(&bob));
// Test email validity
println!("Alice has valid email: {}", has_valid_email().eval(&alice));
println!("Charlie has valid email: {}", has_valid_email().eval(&charlie));
// Test name
println!("Is named Alice: {}", is_named("Alice").eval(&alice));
println!("Is named Bob: {}", is_named("Bob").eval(&alice));
// Combined predicates
let valid_user = is_adult_user().and(has_valid_email());
println!("\nUser validation:");
println!("Alice is valid: {}", valid_user.eval(&alice));
println!("Bob is valid: {}", valid_user.eval(&bob));
println!("Charlie is valid: {}", valid_user.eval(&charlie));
}use predicates::prelude::*;
fn main() {
let numbers: Vec<i32> = (1..=20).collect();
// Create predicates
let is_even = predicate::function(|n: &i32| n % 2 == 0);
let is_divisible_by_3 = predicate::function(|n: &i32| n % 3 == 0);
let is_divisible_by_6 = is_even.clone().and(is_divisible_by_3.clone());
println!("Even numbers: {:?}", numbers.iter().copied().filter(|n| is_even.eval(n)).collect::<Vec<_>>());
println!("Divisible by 3: {:?}", numbers.iter().copied().filter(|n| is_divisible_by_3.eval(n)).collect::<Vec<_>>());
println!("Divisible by 6: {:?}", numbers.iter().copied().filter(|n| is_divisible_by_6.eval(n)).collect::<Vec<_>>());
// Use iter() with predicate
let divisible_by_4 = predicate::function(|n: &i32| n % 4 == 0);
for n in numbers.iter().copied().filter(|n| divisible_by_4.eval(n)) {
println!("Divisible by 4: {}", n);
}
}use predicates::prelude::*;
use std::collections::HashMap;
struct FormValidator {
fields: HashMap<String, String>,
errors: HashMap<String, Vec<String>>,
}
impl FormValidator {
fn new() -> Self {
Self {
fields: HashMap::new(),
errors: HashMap::new(),
}
}
fn field(mut self, name: &str, value: &str) -> Self {
self.fields.insert(name.to_string(), value.to_string());
self
}
fn validate<P: Predicate<str>>(&mut self, field_name: &str, predicate: P, error_msg: &str) {
if let Some(value) = self.fields.get(field_name) {
if !predicate.eval(value) {
self.errors
.entry(field_name.to_string())
.or_insert_with(Vec::new)
.push(error_msg.to_string());
}
}
}
fn is_valid(&self) -> bool {
self.errors.is_empty()
}
fn get_errors(&self) -> &HashMap<String, Vec<String>> {
&self.errors
}
}
fn main() {
let mut validator = FormValidator::new()
.field("username", "john_doe")
.field("email", "john@example.com")
.field("age", "25");
// Validate username
validator.validate(
"username",
predicate::str::is_match(r"^[a-zA-Z_][a-zA-Z0-9_]{2,15}$").unwrap(),
"Username must be 3-16 characters, start with letter or underscore"
);
validator.validate(
"username",
predicate::function(|s: &str| s.len() >= 3),
"Username must be at least 3 characters"
);
// Validate email
validator.validate(
"email",
predicate::str::contains("@"),
"Email must contain @"
);
validator.validate(
"email",
predicate::str::is_match(r"^[^@]+@[^@]+\.[^@]+$").unwrap(),
"Email format is invalid"
);
// Validate age (as string)
validator.validate(
"age",
predicate::str::is_match(r"^[0-9]+$").unwrap(),
"Age must be a number"
);
if validator.is_valid() {
println!("Form is valid!");
} else {
println!("Form has errors:");
for (field, errors) in validator.get_errors() {
println!(" {}: {}", field, errors.join(", "));
}
}
}use predicates::prelude::*;
#[derive(Debug, Clone)]
struct Config {
environment: String,
port: u16,
debug: bool,
max_connections: usize,
}
fn matches_production_requirements() -> impl Predicate<Config> {
predicate::function(|c: &Config| c.environment == "production")
.and(predicate::function(|c: &Config| !c.debug))
.and(predicate::function(|c: &Config| c.port >= 80))
.and(predicate::function(|c: &Config| c.max_connections >= 100))
}
fn matches_development_requirements() -> impl Predicate<Config> {
predicate::function(|c: &Config| c.environment == "development")
.and(predicate::function(|c: &Config| c.debug))
}
fn is_secure_config() -> impl Predicate<Config> {
predicate::function(|c: &Config| c.port == 443 || c.environment == "development")
.and(predicate::function(|c: &Config| !c.debug || c.environment == "development"))
}
fn main() {
let configs = vec![
Config {
environment: "production".to_string(),
port: 443,
debug: false,
max_connections: 500,
},
Config {
environment: "development".to_string(),
port: 8080,
debug: true,
max_connections: 50,
},
Config {
environment: "production".to_string(),
port: 80,
debug: true, // Wrong!
max_connections: 200,
},
];
let prod_check = matches_production_requirements();
let dev_check = matches_development_requirements();
let secure_check = is_secure_config();
println!("Configuration Analysis:\n");
for (i, config) in configs.iter().enumerate() {
println!("Config {} ({:?}):", i + 1, config.environment);
println!(" Meets production requirements: {}", prod_check.eval(config));
println!(" Meets development requirements: {}", dev_check.eval(config));
println!(" Is secure: {}", secure_check.eval(config));
println!();
}
}#[cfg(test)]
mod tests {
use predicates::prelude::*;
fn calculate_score(points: u32, bonus: u32) -> u32 {
points * 10 + bonus * 5
}
fn get_status(score: u32) -> &'static str {
if score >= 100 {
"expert"
} else if score >= 50 {
"intermediate"
} else {
"beginner"
}
}
#[test]
fn test_score_calculation() {
let score = calculate_score(10, 5);
// Using predicates provides better failure messages
assert!(predicate::eq(125).eval(&score), "Score calculation failed");
// Range check
let in_expected_range = predicate::ge(100).and(predicate::le(200));
assert!(in_expected_range.eval(&score));
}
#[test]
fn test_status_levels() {
assert!(predicate::eq("expert").eval(&get_status(150)));
assert!(predicate::eq("expert").eval(&get_status(100)));
assert!(predicate::eq("intermediate").eval(&get_status(75)));
assert!(predicate::eq("intermediate").eval(&get_status(50)));
assert!(predicate::eq("beginner").eval(&get_status(25)));
}
#[test]
fn test_string_output() {
let output = format!("Score: {}", calculate_score(5, 3));
assert!(predicate::str::contains("Score:").eval(&output));
assert!(predicate::str::contains("65").eval(&output));
assert!(predicate::str::starts_with("Score:").eval(&output));
}
}
fn main() {
println!("Run with: cargo test");
}use predicates::prelude::*;
#[cfg(test)]
mod tests {
use super::*;
// Helper function that returns a predicate
fn is_valid_username() -> impl Predicate<str> {
predicate::str::is_match(r"^[a-zA-Z][a-zA-Z0-9_]{2,15}$").unwrap()
.and(predicate::str::starts_with("user_").not())
}
fn is_valid_port() -> impl Predicate<u16> {
predicate::ge(1024).and(predicate::le(65535))
}
#[test]
fn test_valid_usernames() {
let valid_names = vec!["alice", "bob123", "charlie_99", "Admin"];
let invalid_names = vec!["ab", "user_alice", "123bob", "_invalid"];
for name in valid_names {
assert!(is_valid_username().eval(name), "Expected '{}' to be valid", name);
}
for name in invalid_names {
assert!(!is_valid_username().eval(name), "Expected '{}' to be invalid", name);
}
}
#[test]
fn test_valid_ports() {
let valid_ports = vec![1024, 8080, 443, 3000];
let invalid_ports = vec![80, 21, 1023, 65536];
for port in valid_ports {
assert!(is_valid_port().eval(&port), "Expected {} to be valid", port);
}
}
}
fn main() {
println!("Run with: cargo test");
}predicates provides composable, reusable test assertions with a fluent APIeq, ne, gt, ge, lt, le for comparisonscontains, starts_with, ends_with, is_match (regex)exists, is_file, is_dir, ext() for filesystem checksis_close for approximate equality with toleranceand(), or(), not() for building complex conditionspredicate::function(|x| ...) creates custom predicates from closuresBoxPredicate allows storing predicates in collections or using dynamic dispatchassert! in tests for readable assertions