How to specify multiple possibilities for rust trait bounds

98 views Asked by At

I am trying to write a function that takes any iterable to a generic type as input and loops over the elements. Here is a working example

 pub fn test1<'a, IterableT, ItemT>(nodes: &'a IterableT)
    where 
        &'a IterableT: IntoIterator<Item = &'a ItemT>,
        ItemT: 'a + Debug
    {
        for x in nodes {
            println!("consuming or iterating: {:?}!", x);
        }
    }

This works with reference types. I struggle to implement a version that works with both regular types and references. The closest I have got it the following

 pub fn test1<IterableT>(nodes: IterableT)
    where 
        IterableT: IntoIterator,
        IterableT::Item: Debug
    {
        for x in nodes {
            println!("consuming or iterating: {:?}!", x);
        }
    }

This works as long as the reference version of the type I am using implements IntoIterator (for example vec, &vec and &mut vec). The problem is that I can't reintroduce the ItemT generic since then the underlying Item is narrowed down to one of Item or &Item

 pub fn test1<IterableT, ItemT>(nodes: IterableT)
    where 
        IterableT: IntoIterator<Item = ItemT>, // alternatively IntoIterator<Item = &ItemT>
        ItemT: Debug
    {
        for x in nodes {
            println!("consuming or iterating: {:?}!", x);
        }
    }

Depending on whether I use IntoIterator<Item = ItemT> or IntoIterator<Item = &ItemT>, this works with vec or &vec but not both. A hacky workaround is to specify IterableT::Item: Into<ItemT>, but this only works if the underlying type implements the Into traits (so this will work with built-in types like i32, i64, but not for some custom made struct).

Is there a way to indicate that Item can either be ItemT or &ItemT in the where clause? The reason I need to introduce ItemT is because it is a generic variable of the parent struct that this function is a part of. I need to accept iterators specifically with that type.

1

There are 1 answers

1
Mac O'Brien On BEST ANSWER

You can use std::borrow::Borrow to treat T and &T the same.

use std::{borrow::Borrow, fmt::Debug};

fn debug_iterator<I, T>(it: I)
where
    I: IntoIterator,
    I::Item: Borrow<T>,
    T: Debug + ?Sized,
{
    for item in it.into_iter() {
        println!("  item: {:?}", item.borrow());
    }
}

In action:

fn main() {
    let v1 = vec![String::from("hello"), String::from("world")];

    println!("By reference:");
    debug_iterator::<_, String>(v1.iter());
    
    println!("By value:");
    debug_iterator::<_, String>(v1);
}

Output:

By reference:
  item: "hello"
  item: "world"
By value:
  item: "hello"
  item: "world"

Note that because the Borrow trait has a type parameter, you have to provide the correct type for T explicitly at the call site; Rust will not be able to infer the type you want.