Crate crossroads
source ·Expand description
A proc macro that turns one function into many - along user-defined forks points.
Motivation and Usage
This crate allows you to define multiple functions sharing significant part of their logic.
To understand why this is useful, consider the following example for a set of unit tests
(the #[test]
attribute is only commented out to have these be picked up by doctest):
use std::collections::HashMap;
// #[test]
fn empty_be_default() {
let map: HashMap<String, usize> = Default::default();
assert!(map.is_empty());
}
// #[test]
fn empty_after_clear() {
let mut map: HashMap<String, usize> = Default::default();
map.insert("test".to_string(), 1);
map.clear();
assert!(map.is_empty());
}
// #[test]
fn empty_after_remove() {
let mut map: HashMap<String, usize> = Default::default();
map.insert("test".to_string(), 1);
map.remove("test");
assert!(map.is_empty());
}
With this crate, you can write the following instead:
use std::collections::HashMap;
use crossroads::crossroads;
#[crossroads]
// #[test]
fn empty() {
let mut map: HashMap<String, usize> = Default::default();
match fork!() {
by_default => {}
after_add => {
map.insert("Key".to_owned(), 1337);
match fork!() {
and_remove => map.remove("Key"),
and_clear => map.clear(),
};
}
}
assert!(map.is_empty());
}
The #[crossroads]
macro will replace the function with as many functions as there are distinct paths through your fork points.
In this case, it will generate:
// #[test]
fn empty_by_default() { /* ... */ }
// #[test]
fn empty_after_add_and_remove() { /* ... */ }
// #[test]
fn empty_after_add_and_clear() { /* ... */ }
The contents of the methods are the result of replacing the match
expressions with a
block containing the expression specified in the corresponding match
arms.
You can find the above example in the examples
folder and confirm that it will indeed produce the following output when run as a test:
running 3 tests
test empty_by_default ... ok
test empty_after_add_and_clear ... ok
test empty_after_add_and_remove ... ok
Questions and Answers
- Why did you decide to use the
match
-based syntax and not implement a new one? The main reason for using thematch
syntax in the way this crate does is to make it as compatible as possible with code formattting tools such asrustfmt
. See theselect!
macros used in the async context for an example of issues a new syntax can cause.
Attribute Macros
FnItem
s, i.e. freestanding functions everywhere.
It will replace the function with a set of functions induced by the different paths through the
function along the match fork!() { a => { ... }, ... }
points, where the name of the function is induced by the
sequence of the identifier
specified in the patterns of the match
branches used with the for that specific function instance.