3
votes

I am attempting to write a function that validates a given collection using a closure. The function takes ownership of a collection, iterates over the contents, and if no invalid item was found, returns ownership of the collection. This is so it can be used like this (without creating a temp for the Vec): let col = validate(vec![1, 2], |&v| v < 10)?;

This is the current implementation of the function:

use std::fmt::Debug;

fn validate<C, F, V>(col: C, pred: F) -> Result<C, String>
    where C: Debug,
          for<'c> &'c C: IntoIterator<Item = V>,
          F: Fn(&V) -> bool,
          V: Debug
{
    if let Some(val) = (&col).into_iter().find(|v| !pred(v)) {
        Err(format!("{:?} contains invalid item: {:?}.", col, val))?;
    }
    Ok(col)
}

It does compile, but it doesn't work when I try to use it:

use std::collections::BTreeMap;
use std::iter::{FromIterator, once};

fn main() {
    println!("Vec: {:?}", validate(vec![1, 2, 3, 4], |&&v| v <= 3));
    //                    ^^^^^^^^ expected bound lifetime parameter 'c, found concrete lifetime

    println!("Map: {:?}",
             validate(BTreeMap::from_iter(once((1, 2))), |&(&k, &v)| k <= 3));
}

Rust Playground

Is what I'm trying to accomplish here possible?

Background

I am writing a parser for a toy project of mine and was wondering if I could write a single validate function that works with all the collection types I use: Vecs, VecDeques, BTreeSets, BTreeMaps, &[T] slices.

Each of these collections implements the IntoIterator trait for a reference of itself, which can be used to call .into_iter() on a reference without consuming the items in the collection:

This is the what the for<'c> &'c C: IntoIterator<Item = V> in the function declaration refers to. Since the reference is defined in the function body itself, we can't just use a lifetime that's declared on the function (like fn validate<'c, ...), because this would imply that the reference has to outlive the function (which it cannot). Instead we have to use a Higher-Rank Trait Bound to declare this lifetime.

It seems to me that this lifetime is also the source of the trouble, since a version of the function that takes and returns a reference to the collection works fine:

// This works just fine.
fn validate<'c, C, F, V>(col: &'c C, pred: F) -> Result<&'c C, String>
    where C: Debug,
          &'c C: IntoIterator<Item = V>,
          F: Fn(&V) -> bool,
          V: Debug
{
    if let Some(val) = col.into_iter().find(|v| !pred(v)) {
        Err(format!("{:?} contains invalid item: {:?}.", col, val))?;
    }
    Ok(col)
}

Rust Playground

Furthermore, I managed to implement two other versions of the function, one which works for Vec, VecDeque, BTreeSet and &[T] slices, and another which works for BTreeMap and probably other mappings:

use std::fmt::Debug;

pub fn validate_collection<C, F, V>(col: C, pred: F) -> Result<C, String>
    where C: Debug,
          for<'c> &'c C: IntoIterator<Item = &'c V>,
          F: Fn(&V) -> bool,
          V: Debug
{
    if let Some(val) = (&col).into_iter().find(|&v| !pred(v)) {
        Err(format!("{:?} contains invalid item: {:?}.", col, val))?;
    }
    Ok(col)
}

pub fn validate_mapping<C, F, K, V>(col: C, pred: F) -> Result<C, String>
    where C: Debug,
          for<'c> &'c C: IntoIterator<Item = (&'c K, &'c V)>,
          F: Fn(&K, &V) -> bool,
          K: Debug,
          V: Debug
{
    if let Some(val) = (&col).into_iter().find(|&(k, v)| !pred(k, v)) {
        Err(format!("{:?} contains invalid item: {:?}.", col, val))?;
    }
    Ok(col)
}

Rust Playground

In the end I hope to create a Validate trait. Currently, I can only impl it for either collections or mappings, because the impls conflict.

use std::fmt::Debug;

trait Validate<V>: Sized {
    fn validate<F>(self, F) -> Result<Self, String> where F: Fn(&V) -> bool;
}

// Impl that only works for collections, not mappings.
impl<C, V> Validate<V> for C
    where C: Debug,
          for<'c> &'c C: IntoIterator<Item = &'c V>,
          V: Debug
{
    fn validate<F>(self, pred: F) -> Result<C, String>
        where F: Fn(&V) -> bool
    {
        if let Some(val) = (&self).into_iter().find(|&v| !pred(v)) {
            Err(format!("{:?} contains invalid item: {:?}.", self, val))?;
        }
        Ok(self)
    }
}

fn main() {
    println!("Vec: {:?}", vec![1, 2, 3, 4].validate(|&v| v <= 3));
}

Rust Playground

1

1 Answers

4
votes

Looking at your trait bounds (reformatted a little):

fn validate<C, F, V>(col: C, pred: F) -> Result<C, String>
    where C: Debug,
          for<'c> &'c C: IntoIterator<Item = V>,
          F: Fn(&V) -> bool,
          V: Debug {

the problem is that &C won't implement IntoIterator<Item = V>; references tend to iterate over references.

Fixing that (and the extra reference in the closure) makes it work:

fn validate<C, F, V>(col: C, pred: F) -> Result<C, String>
    where C: Debug,
          for<'c> &'c C: IntoIterator<Item = &'c V>,
          F: Fn(&V) -> bool,
          V: Debug
{
    if let Some(val) = (&col).into_iter().find(|v| !pred(v)) {
        Err(format!("{:?} contains invalid item: {:?}.", col, val))?;
    }
    Ok(col)
}

fn main() {
    println!("Vec: {:?}", validate(vec![1, 2, 3, 4], |&v| v <= 3));
}

Playground

To extend this to work with BTreeMap values, we can abstract over the method used to generate the iterators. Let's add a trait HasValueIterator which knows how to get an iterator over values:

trait HasValueIterator<'a, V: 'a> {
    type ValueIter : Iterator<Item=&'a V>;

    fn to_value_iter(&'a self) -> Self::ValueIter;
}

and use that instead of IntoIterator:

fn validate<C, F, V>(col: C, pred: F) -> Result<C, String>
    where C: Debug,
          for<'c> C: HasValueIterator<'c, V>,
          F: Fn(&V) -> bool,
          V: Debug
{
    if let Some(val) = (&col).to_value_iter().find(|v| !pred(v)) {
        Err(format!("{:?} contains invalid item: {:?}.", col, val))?;
    }
    Ok(col)
}

Now we can implement it for Vec and BTreeMap (the latter using .values()), thought you have to name the iterator types:

impl<'c, V:'c> HasValueIterator<'c, V> for Vec<V> {
    type ValueIter = std::slice::Iter<'c,V>;

    fn to_value_iter(&'c self) -> Self::ValueIter {
        self.iter()
    }
}

impl<'c, V:'c, K:'c> HasValueIterator<'c, V> for BTreeMap<K, V> {
    type ValueIter = std::collections::btree_map::Values<'c, K, V>;

    fn to_value_iter(&'c self) -> Self::ValueIter {
        self.values()
    }

}

Now this works with both Vec and BTreeMap, at least with values:

fn main() {
    println!("Vec: {:?}", validate(vec![1, 2, 3, 4], |&v| v <= 3));

    let mut map = BTreeMap::new();
    map.insert("first", 1);
    map.insert("second", 2);
    map.insert("third", 3);
    println!("Map: {:?}", validate(map, |&v| v<=2));
}

Playground

This outputs:

Vec: Err("[1, 2, 3, 4] contains invalid item: 4.")
Map: Err("{\"first\": 1, \"second\": 2, \"third\": 3} contains invalid item: 3.")