When I make a function with a path input, sometimes I'd like it to accept &Path, PathBuf, &PathBuf, &str, String, &String, etc.
You can do this with generics, but I got tired of remembering the syntax, especially for iterators and ndarrays. I wrote a proc macro that remembers the syntax for you.
I'd be interested in opinions about if this is a terrible/good idea. It seemed useful in my bed-reader genomics crate to simplify AnyIter<AnyString>
inputs.]
Example: Create a function that adds 2 to the length of any string-like thing.
use anyinput::anyinput;
use anyhow::Result;
#[anyinput]
fn len_plus_2(s: AnyString) -> Result<usize, anyhow::Error> {
Ok(s.len()+2)
}
// By using AnyString, len_plus_2 works with
// &str, String, or &String -- borrowed or moved.
assert_eq!(len_plus_2("Hello")?, 7); // move a &str
let input: &str = "Hello";
assert_eq!(len_plus_2(&input)?, 7); // borrow a &str
let input: String = "Hello".to_string();
assert_eq!(len_plus_2(&input)?, 7); // borrow a String
let input2: &String = &input;
assert_eq!(len_plus_2(&input2)?, 7); // borrow a &String
assert_eq!(len_plus_2(input2)?, 7); // move a &String
assert_eq!(len_plus_2(input)?, 7); // move a String
Nesting and multiple AnyInputs are allowed. Here we create a function with two inputs. One input accepts any iterator-like thing of usize. The second input accepts any iterator-like thing of string-like things. The function returns the sum of the numbers and string lengths.
We apply the function to the range 1..=10 and a slice of &str’s.
use anyinput::anyinput;
use anyhow::Result;
#[anyinput]
fn two_iterator_sum(
iter1: AnyIter<usize>,
iter2: AnyIter<AnyString>,
) -> Result<usize, anyhow::Error> {
let mut sum = iter1.sum();
for any_string in iter2 {
// Needs .as_ref to turn the nested AnyString into a &str.
sum += any_string.as_ref().len();
}
Ok(sum)
}
assert_eq!(two_iterator_sum(1..=10, ["a", "bb", "ccc"])?, 61);
See the link for examples with paths and arrays. There is also an example of applying NdArray
functions to any array-like collection of numbers. For example, you can apply mean
or std
to a Vec
of f32
.
Suggestions, feature requests, and contributions are welcome.
How it works:
The #[anyinput] macro uses standard Rust generics to support multiple input types. To do this, it rewrites your function with the appropriate generics. It also adds lines to your function to efficiently convert from any top-level generic to a concrete type. For example, the macro transforms len_plus_2 from:
#[anyinput]
fn len_plus_2(s: AnyString) -> Result<usize, anyhow::Error> {
Ok(s.len()+2)
}
into
fn len_plus_2<AnyString0: AsRef<str>>(s: AnyString0) -> Result<usize, anyhow::Error> {
let s = s.as_ref();
Ok(s.len() + 2)
}
Here AnyString0 is the generic type. The line let s = s.as_ref() converts from generic type AnyString0 to concrete type &str.
Any feedback, negative or positive, is welcome.