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