Expand description

anyinput

github crates.io docs.rs build status

Easily create functions that accept any type of string, path, iterator-like, or array-line inputs. The AnyInputs are AnyString, AnyPath, AnyIter, AnyArray, and (optionally) AnyNdArray.

Contents

Usage

Add this to your Cargo.toml:

[dependencies]
anyinput = { version = "0.1", features = ["ndarray"] }

If you don’t need NdArray support, omit the ndarray feature.

Examples

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

Create a function that counts the components of any path-like thing.

use anyinput::anyinput;
use anyhow::Result;
use std::path::Path;

#[anyinput]
fn component_count(path: AnyPath) -> Result<usize, anyhow::Error> {
    let count = path.iter().count();
    Ok(count)
}

// By using AnyPath, component_count works with any
// string-like or path-like thing, borrowed or moved.
assert_eq!(component_count("usr/files/home")?, 3);
let path = Path::new("usr/files/home");
assert_eq!(component_count(&path)?, 3);
let pathbuf = path.to_path_buf();
assert_eq!(component_count(pathbuf)?, 3);

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);

Create a function that accepts an array-like thing of path-like things. Return the number of path components at an index.

use anyinput::anyinput;
use anyhow::Result;

#[anyinput]
fn indexed_component_count(
    array: AnyArray<AnyPath>,
    index: usize,
) -> Result<usize, anyhow::Error> {
    // Needs .as_ref to turn the nested AnyPath into a &Path.
    let path = array[index].as_ref();
    let count = path.iter().count();
    Ok(count)
}

assert_eq!(
    indexed_component_count(vec!["usr/files/home", "usr/data"], 1)?,
    2
);

You can easily apply NdArray functions to any array-like thing of numbers. For example, here we create a function that accepts an NdArray-like thing of f32 and returns the mean. We apply the function to both a Vec and an Array1<f32>.

Support for NdArray is provided by the optional feature ndarray.

use anyinput::anyinput;
use anyhow::Result;

#[anyinput]
fn any_mean(array: AnyNdArray<f32>) -> Result<f32, anyhow::Error> {
    if let Some(mean) = array.mean() {
        Ok(mean)
    } else {
        Err(anyhow::anyhow!("empty array"))
    }
}

// 'AnyNdArray' works with any 1-D array-like thing, but must be borrowed.
assert_eq!(any_mean(&vec![10.0, 20.0, 30.0, 40.0])?, 25.0);
assert_eq!(any_mean(&ndarray::array![10.0, 20.0, 30.0, 40.0])?, 25.0);

The AnyInputs

AnyInputDescriptionCreates Concrete Type
AnyStringAny string-like thing&str
AnyPathAny path-like or string-like thing&Path
AnyIterAny iterator-like thing<I as IntoIterator>::IntoIter
AnyArrayAny array-like thing&[T]
AnyNdArrayAny 1-D array-like thing (borrow-only)ndarray::ArrayView1<T>

Notes & Features

  • Feature requests and contributions are welcome.

  • Works with nesting, multiple inputs, and generics.

  • Automatically and efficiently converts an top-level AnyInput into a concrete type.

  • Elements of AnyArray, AnyIter, and AnyNdArray must be a single type. So, AnyArray<AnyString> accepts a vector of all &str or all String, but not mixed.

  • When nesting, efficiently convert the nested AnyInput to the concrete type with

    • .as_ref() – AnyString, AnyPath, AnyArray
    • .into_iter() – AnyIter
    • .into() – AnyNdArray

    (The iterator and array examples above show this.)

  • Let’s you easily apply NdArray functions to regular Rust arrays, slices, and Vecs.

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:

use anyinput::anyinput;

#[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.

As with all Rust generics, the compiler creates a separate function for each combination of concrete types used by the calling code.

Attribute Macros

Easily create functions that accept any type of string, path, iterator-like, or array-line inputs. The AnyInputs are AnyString, AnyPath, AnyIter, AnyArray, and (optionally) AnyNdArray.