| Crates.io | syn-match |
| lib.rs | syn-match |
| version | 0.3.0 |
| created_at | 2025-11-09 16:51:13.806276+00 |
| updated_at | 2025-11-10 23:29:03.761232+00 |
| description | a macro for matching on syn paths |
| homepage | |
| repository | https://github.com/crowlKats/syn-match |
| max_upload_size | |
| id | 1924276 |
| size | 71,853 |
A Rust procedural macro for pattern matching on syn::Path structures with binding capabilities.
syn-match provides a path_match! macro that allows you to pattern match against Rust path expressions (like std::collections::HashMap or Option<String>) with support for:
use syn_match::path_match;
use syn::Path;
let path: Path = syn::parse_quote!(std::collections::HashMap);
let result = path_match!(&path,
std::collections::HashMap => "found HashMap",
std::vec::Vec => "found Vec",
_ => "something else"
);
assert_eq!(result, "found HashMap");
let path: Path = syn::parse_quote!(String);
let result = path_match!(&path,
String => "matched String",
_ => "no match"
);
Use ? to make path segments optional:
let path1: Path = syn::parse_quote!(std::str::String);
let path2: Path = syn::parse_quote!(str::String);
let path3: Path = syn::parse_quote!(String);
for path in [&path1, &path2, &path3] {
let result = path_match!(path,
std?::str?::String => "matched",
_ => "no match"
);
assert_eq!(result, "matched"); // All match!
}
Bind path segments to variables using $name:
let path: Path = syn::parse_quote!(std::collections::HashMap);
let result = path_match!(&path,
std::$module::$ty => format!("{}::{}", module.ident, ty.ident),
_ => "no match".to_string()
);
assert_eq!(result, "collections::HashMap");
Capture multiple segments using $name*:
let path: Path = syn::parse_quote!(std::collections::HashMap);
let result = path_match!(&path,
$prefix*::HashMap => {
prefix.iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<_>>()
.join("::")
},
_ => "no match".to_string()
);
assert_eq!(result, "std::collections");
Capture segments that may or may not exist using $name?:
let path: Path = syn::parse_quote!(std::collections::HashMap);
let result = path_match!(&path,
std::$middle?::HashMap => {
if let Some(seg) = middle {
format!("Found middle: {}", seg.ident)
} else {
"No middle segment".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "Found middle: collections");
Match and bind generic type arguments:
let path: Path = syn::parse_quote!(Option<String>);
let result = path_match!(&path,
Option<$inner> => {
if let syn::GenericArgument::Type(syn::Type::Path(type_path)) = inner {
type_path.path.segments.last().unwrap().ident.to_string()
} else {
"not a path".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "String");
Use ::$name to bind only path-type generic arguments:
let path: Path = syn::parse_quote!(Vec<String>);
let result = path_match!(&path,
Vec<::$ty> => ty.segments.last().unwrap().ident.to_string(),
_ => "no match".to_string()
);
assert_eq!(result, "String");
// Won't match non-path types:
let path2: Path = syn::parse_quote!(Vec<[u8]>);
let result2 = path_match!(&path2,
Vec<::$ty> => "matched path",
_ => "no match"
);
assert_eq!(result2, "no match");
Match associated types in generic parameters:
let path: Path = syn::parse_quote!(Future<Output = String>);
let result = path_match!(&path,
Future<Output = $output> => {
if let syn::Type::Path(type_path) = output {
format!("Future output: {}", type_path.path.segments.last().unwrap().ident)
} else {
"not a path".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "Future output: String");
Match and bind lifetimes using $'name:
let path: Path = syn::parse_quote!(Cow<'static, str>);
let result = path_match!(&path,
Cow<$'lt, str> => format!("lifetime: {}", lt.ident),
_ => "no match".to_string()
);
assert_eq!(result, "lifetime: static");
Use '_ for lifetime wildcards:
let path: Path = syn::parse_quote!(Cow<'a, str>);
let result = path_match!(&path,
Cow<'_, str> => "matched any lifetime",
_ => "no match"
);
assert_eq!(result, "matched any lifetime");
Note: Lifetime bindings do not support optional patterns ($'name?). Only regular lifetime bindings ($'name) and wildcards ('_) are supported.
Match slice types in generic arguments:
let path: Path = syn::parse_quote!(std::borrow::Cow<foo, [u8]>);
let result = path_match!(&path,
std?::borrow?::Cow<_, [$elem]> => elem.to_token_stream().to_string(),
_ => "no match".to_string()
);
assert_eq!(result, "u8");
Use | to match multiple patterns in a single arm:
let path: Path = syn::parse_quote!(HashMap);
let result = path_match!(&path,
String | HashMap | Vec => "collection type",
_ => "other"
);
assert_eq!(result, "collection type");
Use _ to match any remaining patterns:
let path: Path = syn::parse_quote!(Something::Unknown);
let result = path_match!(&path,
String => "string",
Vec => "vector",
_ => "wildcard match"
);
assert_eq!(result, "wildcard match");
let path: Path = syn::parse_quote!(Result<std::collections::HashMap, io::Error>);
let result = path_match!(&path,
Result<std::$module::$ty, io::Error> => {
format!("Result with {}::{}", module.ident, ty.ident)
},
_ => "no match".to_string()
);
assert_eq!(result, "Result with collections::HashMap");
let path: Path = syn::parse_quote!(Option<Outer<Other<More<Yet<String>>>>>);
let result = path_match!(&path,
Option<Outer<Other<More<Yet<$inner>>>>> => {
if let syn::GenericArgument::Type(syn::Type::Path(type_path)) = inner {
type_path.path.segments.last().unwrap().ident.to_string()
} else {
"not a path".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "String");
let path: Path = syn::parse_quote!(Result<String, Error>);
let result = path_match!(&path,
$_package*::Result<$ok> | $_package*::Result<$ok, _> => {
if let syn::GenericArgument::Type(syn::Type::Path(p)) = ok {
format!("Result<{}, ?>", p.path.segments.last().unwrap().ident)
} else {
"?".to_string()
}
},
_ => "no match".to_string()
);
assert_eq!(result, "Result<String, ?>");