Search code examples
rustiteratortraitsownership

Idiomatic way to accept owned and referenced iterables?


Consider this rust code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d6f2075a8872305334a8ba513241950b

fn main() {
    let v: Vec<i32> = vec![1, 2, 3];
    // This works, of course.
    println!("{}", foo(&v));
    
    // Now let's create an "extra conversion" step:
    let vs: Vec<&str> = vec!["1", "2", "3"];
    
    // We want to "stream" straight from this vec. Let's create an 
    // iterator that converts:
    let converting_iterator = vs.iter().map(|s| s.parse::<i32>().unwrap());
    
    // This does not work
    println!("{}", foo(converting_iterator));
}

fn foo<'a>(it: impl IntoIterator<Item=&'a i32>) -> i32 {
    it.into_iter().sum()
}

I understand why the second line doesn't work. It creates an iterator over i32, not &i32. I can't just slap a & into the closure, because that would attempt to reference a temporary value.

What I'd be curious about though is if there is any way to write foo in such a way that it can deal with both types of iterables? If I were to just add the .sum() to the end of creating the converting_iterator, it would just work. So I feel that there should be some way to "intercept" the result (i.e. the converting iterator), pass that to something, and have that something call .sum on it.

Maybe something with Borrow or AsRef, but I couldn't figure that out from the documentation of those traits.


Solution

  • A more broad answer is to use the Borrow trait to accept references and non-references generically:

    use std::borrow::Borrow;
    
    fn foo<'a, T: Borrow<i32>>(it: impl IntoIterator<Item = T>) -> i32 {
        it.into_iter().map(|t| *t.borrow()).sum()
    }
    

    It is unfortunately not as nice as PitaJ's answer since .sum() is already fine with both references and non-references, but doesn't handle impl Borrow, so we have to introduce a .map() to get the integers out of the potentially-borrowed type. But this is a trick to use if your function was different.

    This is a pattern seen in other questions: