| Crates.io | option-chain-tool |
| lib.rs | option-chain-tool |
| version | 0.11.0 |
| created_at | 2026-01-09 14:06:44.03376+00 |
| updated_at | 2026-01-09 14:06:44.03376+00 |
| description | A Rust macro that brings JavaScript-like optional chaining to Rust. |
| homepage | |
| repository | https://github.com/Sergo007/option-chain-tool |
| max_upload_size | |
| id | 2032137 |
| size | 36,699 |
A Rust macro that brings JavaScript-like optional chaining to Rust, allowing you to use the ? operator in any context - even in functions that don't return Option or Result.
In Rust, the ? operator is a powerful tool for ergonomic error handling, but it comes with a significant limitation: it only works inside functions that return Option or Result. This restriction forces developers into verbose workarounds when working with nested optional values in regular functions.
Consider a common scenario: you have deeply nested optional structures and want to safely access a value deep within. In Rust, you typically have three options, all with drawbacks:
and_then and map:let value = test_struct.value
.and_then(|v1| v1.value)
.and_then(|v2| v2.value);
This becomes increasingly unreadable as nesting increases.
if let Some(____v) = &test_struct.value {
if let Some(____v) = &____v.value {
Some(&____v.value)
} else {
None
}
} else {
None
}
Even more verbose and error-prone.
fn get_nested_value(test_struct: &TestStruct) -> Option<i32> {
test_struct.value?.value?.value
}
let value = get_nested_value(&test_struct);
Creates unnecessary function overhead for simple access patterns.
JavaScript and TypeScript developers enjoy optional chaining (?.) that makes this trivial:
const value = test_struct?.value?.value?.value;
option-chain brings this ergonomic experience to Rust with a simple macro that lets you use the natural ? operator anywhere:
let value = opt!(test_struct.value?.value?.value?);
Clean, readable, and efficient - without requiring your function to return Option.
if let Some(...) = ... chains.? operator syntax.(), concrete types, or anything else.Add this to your Cargo.toml:
[dependencies]
option-chain-tool = "0.10"
use option_chain_macro::opt;
#[derive(Debug, Clone)]
struct User {
profile: Option<Profile>,
}
#[derive(Debug, Clone)]
struct Profile {
address: Option<Address>,
}
#[derive(Debug, Clone)]
struct Address {
city: Option<String>,
}
fn main() {
let user = User {
profile: Some(Profile {
address: Some(Address {
city: Some("New York".to_string()),
}),
}),
};
// â
With opt! macro - clean and readable
let city = opt!(user.profile?.address?.city?);
println!("City: {:?}", city); // City: Some("New York")
// â Without macro - verbose and nested
let city_verbose = user.profile
.and_then(|p| p.address)
.and_then(|a| a.city);
}
use option_chain_macro::opt;
#[derive(Debug, Clone)]
struct Team {
members: Option<Vec<Member>>,
}
#[derive(Debug, Clone)]
struct Member {
name: String,
email: Option<String>,
}
fn main() {
let team = Team {
members: Some(vec![
Member {
name: "Alice".to_string(),
email: Some("alice@example.com".to_string())
},
Member {
name: "Bob".to_string(),
email: None
},
]),
};
// Access nested vector elements safely
let first_email = opt!(team.members?.get(0)?.email?);
println!("First email: {:?}", first_email); // Some("alice@example.com")
let second_email = opt!(team.members?.get(1)?.email?);
println!("Second email: {:?}", second_email); // None
}
use option_chain_macro::opt;
#[derive(Debug, Clone)]
struct Config {
database: Option<DatabaseConfig>,
}
#[derive(Debug, Clone)]
struct DatabaseConfig {
host: String, // Required field
port: i32, // Required field
credentials: Option<Credentials>,
}
#[derive(Debug, Clone)]
struct Credentials {
username: String,
}
fn main() {
let config = Config {
database: Some(DatabaseConfig {
host: "localhost".to_string(),
port: 5432,
credentials: None,
}),
};
// Access required fields through optional chain
let port = opt!(&config.database?.port);
println!("Port: {:?}", port); // Some(5432)
let host = opt!(&config.database?.host);
println!("Host: {:?}", host); // Some("localhost")
}
use option_chain_macro::opt;
#[derive(Debug, Clone)]
struct ApiResponse {
data: Option<ResponseData>,
}
#[derive(Debug, Clone)]
struct ResponseData {
user: Option<UserData>,
}
#[derive(Debug, Clone)]
struct UserData {
id: i32,
metadata: Option<Metadata>,
}
#[derive(Debug, Clone)]
struct Metadata {
last_login: Option<String>,
}
// Regular function that doesn't return Option
fn process_response(response: ApiResponse) {
// Without opt! macro, you'd need verbose chaining or helper functions
// With opt! macro, it's clean and expressive
let user_id = opt!(&response.data?.user?.id);
let last_login = opt!(response.data?.user?.metadata?.last_login?);
println!("User ID: {:?}", user_id); // Some(42)
println!("Last login: {:?}", last_login); // Some("2024-01-01")
}
JavaScript/TypeScript:
const city = user?.profile?.address?.city;
Rust without option-chain:
let city = user.profile
.and_then(|p| p.address)
.and_then(|a| a.city);
Rust with option-chain:
let city = opt!(user.profile?.address?.city?);
The opt! macro transforms your code at compile time, converting the intuitive ? syntax into efficient if let Some(...) = ... chains. There's no runtime overhead - it's purely a syntactic convenience.
// What you write:
let a: Option<&String> = opt!(user.profile?.address?.city?);
// What the compiler sees (roughly):
let a: Option<&String> = if let Some(____v) = &user.profile {
if let Some(____v) = &____v.address {
if let Some(____v) = &____v.city {
Some(____v)
} else {
None
}
} else {
None
}
} else {
None
};
â Use option-chain when:
Optionand_then chainsâ Consider alternatives when:
Option or Result (use native ? operator)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.