A Rust library providing a tri-state type for representing value presence in schemas and data structures.
Presence
A Rust library providing a tri-state type for representing value presence
in schemas and data structures.
[!TIP]
If you what to read more about the motivation behind this crate, check out
Stop Losing Intent: Absent, Null, and Value in Rust- Overview - Cardinality - Why Not
Option<Option<T>>?
- Usage
- Examples
- Basic Usage
- Practical Example: API Update Request
- Use Cases
- Contributing
- License
Overview
Presence<T> extends the traditional Option<T> two-state model (Some/None) with an additional distinction between "absent" and "null". This is particularly useful when working with serialization formats like JSON where the following states are semantically different:
- Absent: Field not present in the data structure:
{} - Null: Field present but explicitly set to null:
{"field": null} - Some: Field present with a concrete value:
{"field": value}
Cardinality
The Presence type increases the cardinality (number of possible states) of any wrapped type by adding two states: Absent and Null.
| Type | Valid States | Cardinality | | ---------------- | --------------------------------------------- | ----------- | | bool | true, false | 2 | | Option<bool> | None, Some(true), Some(false) | 3 | | Presence<bool> | Absent, Null, Some(true), Some(false) | 4 |
This distinction is particularly important in schema design and APIs where the semantic difference between "field not present" and "field explicitly set to null" has meaning.
Why Not Option<Option<T>>?
While Option<Option<T>> can technically represent three states, Presence<T> offers several advantages:
- Clarity:
Presence::Absent,Presence::Null, andPresence::Some(value)
None, Some(None), and Some(Some(value))
where the meaning of nested None values is ambiguous.
- Ergonomics: Method names like
isabsent(),isnull(), andis_present()
option.is_none() or option == Some(None).
- Type Safety: The compiler understands the three distinct states,
- Semantics:
Presencemodels the domain concept directly rather than
// With Presence - clear and explicit
match value {
Presence::Absent => println!("Field not in payload"),
Presence::Null => println!("Field explicitly null"),
Presence::Some(v) => println!("Value: {}", v),
}
// With Option<Option<T>> - confusing match value { None => println!("Field not in payload"), Some(None) => println!("Field explicitly null"), // Wait, which None? Some(Some(v)) => println!("Value: {}", v), }
Usage
Add this to your Cargo.toml:
[dependencies]
presence-rs = "0.2.0"
Examples
Basic Usage
use presence_rs::Presence;
// Create Presence values let absent: Presence<i32> = Presence::Absent; let null: Presence<i32> = Presence::Null; let some: Presence<i32> = Presence::Some(42);
// Query the state assert!(absent.is_absent()); assert!(null.is_null()); assert!(some.is_present());
Practical Example: API Update Request
use presence_rs::Presence;
#[derive(Debug)] struct UserUpdate { name: Presence<String>, email: Presence<String>, age: Presence<u32>, }
fn applyupdate(currentname: &str, update: UserUpdate) -> String { match update.name { Presence::Absent => { // Field not provided - keep current value println!("Name unchanged: {}", current_name); current_name } Presence::Null => { // Field explicitly set to null - clear it println!("Name cleared"); String::new() } Presence::Some(new_name) => { // Field has a new value - update it println!("Name updated to: {}", new_name); new_name } } }
// Example: Partial update where only email is provided let update = UserUpdate { name: Presence::Absent, // Not in request payload email: Presence::Some("new@example.com".to_string()), age: Presence::Null, // Explicitly set to null };
applyupdate("Alice".tostring(), update); // Output: "Name unchanged: Alice"
Use Cases
This type is particularly useful in:
- API clients/servers where you need to distinguish between a field not
- Partial updates where absence means "don't change" vs. null means "clear the value"
- Schema validation where field presence has semantic meaning
- GraphQL implementations where null and undefined are distinct concepts
- Database operations where you need to differentiate between "not provided"
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.