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
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}
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.
While Option<Option<T>> can technically represent three states, Presence<T>
offers several advantages:
- Clarity:
Presence::Absent,Presence::Null, andPresence::Some(value)are self-documenting. Compare this toNone,Some(None), andSome(Some(value))where the meaning of nestedNonevalues is ambiguous. - Ergonomics: Method names like
is_absent(),is_null(), andis_present()clearly express intent, versus checkingoption.is_none()oroption == Some(None). - Type Safety: The compiler understands the three distinct states, making pattern matching more explicit and reducing cognitive load.
- Semantics:
Presencemodels the domain concept directly rather than forcing a tri-state model into a two-level optional structure.
// 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),
}Add this to your Cargo.toml:
[dependencies]
presence-rs = "0.1.2"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());use presence_rs::Presence;
#[derive(Debug)]
struct UserUpdate {
name: Presence<String>,
email: Presence<String>,
age: Presence<u32>,
}
fn apply_update(current_name: &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("[email protected]".to_string()),
age: Presence::Null, // Explicitly set to null
};
apply_update("Alice".to_string(), update);
// Output: "Name unchanged: Alice"This type is particularly useful in:
- API clients/servers where you need to distinguish between a field not being sent vs. being explicitly set to null
- 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" and "set to NULL"
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.