Looking for trait bound which works across all collection types

Hi everyone,

I'm looking for advice on how to set a trait bound for a particular problem. I have a trait which has a method taking a reference to a generic type T:

trait MyTrait<T> {
    fn do_something(&self, t: &T);
}

I would like to implement this for a structure for any type T for which I can call IntoIterator::into_iter() in a reference to T (so that the call doesn't try to consume T):

struct MyStruct;

impl<T> MyTrait<T> for MyStruct
where
    for<'a> &'a T: IntoIterator
{
    fn do_something(&self, t: &T) {
        if t.into_iter().next().is_some() {
            println!("I did something!");
        }
    }
}

This works fine for most collections like Vec and HashSet:

fn main() {
    call_do_something(Vec::<i32>::new());
    call_do_something(HashSet::<i32>::new());
}

fn call_do_something<T>(t: T)
where
    for<'a> &'a T: IntoIterator
{
    MyStruct.do_something(&t);
}

But it doesn't work on slices, since &&[T] doesn't implement IntoIterator:

fn main() {
    call_do_something(&[1, 2, 3]); // <-- Error! &&[T] doesn't implement IntoIterator!
}

The solution needs to work with owned collections like Vec and HashSet, not just with references to those. But it should also work with slices &[T] in the same way. I added an extra layer of indirection call_do_something above to illustrate that.

The underlying code in MyStruct::do_something should compile and work in all of the above cases due to auto-deref. So the question is: is there a trait bound I can use to make the code above compile? I've played around with various combinations of Borrow, AsRef, and Deref to no avail.

See the Rust playground I created.

Any advice would be appreciated. Thanks!

In your playground, you're not passing a &[i32] to call_do_something, you're passing a &[i32; 3]. You can pass the [i32; 3] by value instead, and it works.

As for slices ([T]) -- most places you introduce generics or generic bounds have an implicit Sized bound:

fn foo<T /* : Sized */>(tea: T) { /* ... */ }

The reasoning being, that's usually what you want and you shouldn't have to type : Sized so much. Slices ([T]) are unsized, i.e., they don't meet the Sized bound. So in your code, you probably want to relax the implicit bound by using ?Sized:

-trait MyTrait<T> {
+trait MyTrait<T: ?Sized> {
     fn do_something(&self, t: &T);
 }
 
 struct MyStruct;
 
-impl<T> MyTrait<T> for MyStruct
+impl<T: ?Sized> MyTrait<T> for MyStruct

You can't pass the dynamically sized slices to call_do_something, but you can pass them by reference to do_something. (There is no autoref or autoderef for non-receiver (self, &self, ...) arguments -- and even if there was, you couldn't pass an unsized value, e.g. to call_do_something.)

Playground.

3 Likes

Thanks for your input.

Unfortunately this solution doesn't quite work in my case. I was too imprecise in specifying the question, so please allow me to elaborate.

I'm looking to implement a macro which the caller can invoke the same way with a Vec, a HashSet, or a slice:

macro_rules! call_do_something {
    ($arg:expr) => {
        MyStruct.do_something(&$arg);
    }
}

fn main() {
    call_do_something!(Vec::<i32>::new());
    call_do_something!(HashSet::<i32>::new());
    let v = vec![1, 2, 3];
    call_do_something!(v.as_slice()); // <-- Error! &&[T] doesn't implement IntoIterator!
}

The macro invokes a function which must not consume the input, so it borrows it. But the result is that, when one passes a slice to the macro, one ends up passing &&[T] rather than &[T] to the function, and this doesn't compile.

See also updated playground.

Thanks again for your help!

Well, if the macro always and unconditionally expects an owned value to be passed, and it unconditionally borrows its argument for this reason, then you can just remove the unnecessary level of indirection by writing

call_do_something!(*v.as_slice());

However, all of this seems wrong. If you need borrowed data, why don't you just require the user to pass in borrowed data to the macro in the first place? I.e.:

macro_rules! call_do_something {
    ($arg:expr) => {
        MyStruct.do_something($arg);
    }
}

fn main() {
    call_do_something!(&Vec::<i32>::new());
    call_do_something!(&HashSet::<i32>::new());
    let v = vec![1, 2, 3];
    call_do_something!(v.as_slice());
}
1 Like

Thanks for your reply.

Dereferencing the slice at the call site (and adding extra ?Sized bounds in a few places) might be okay. Thanks!

I'd like to avoid requiring the user to pass a reference to the macro if at all possible. It would only be justified by this one case described here, which is really a corner case involving only a tiny minority of callers.

Perhaps arrange things so you're calling a method on the target objects. Then they'll benefit from auto-deref.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.