&mut trait bound is invalid and I don't get it


#1

I wrote code similar to the following.

fn main() {
  let mut count = 0;
  closure_runner(&mut || {
    count += 1;
  });
}

fn closure_runner<F>(cb: F) -> ()
  where F: &mut FnMut() -> () {
    for i in 0..10 {
        cb();
    }
}

That code doesn’t compile because of the &mut specifier on FnMut in the where clause. If I modify the code to specify &mut on the argument to closure_runner, as follows, the compiler is happy.

fn main() {
  let mut count = 0;
  closure_runner(&mut || {
    count += 1;
  });
}

fn closure_runner<F>(cb: &mut F) -> ()
  where F: FnMut() -> () {
    for i in 0..10 {
        cb();
    }
}

I don’t understand that.

As I understand the type system &mut is part of the type. That is,

&mut FnMut 

is a type separate and distinct from

FnMut.

So why is it illegal to declare the where clause as &mut FnMut?

I suspect my interpretation of the type system is wrong. Would someone please clarify this for me?

Thanks.


#2

It looks like you are mixing up T: SomeTrait and T = SomeType. The : is used when a type should implement some trait(s) or have some generic lifetime, like this: T: Display + Send + Sync + 'static. It says that T must implement the traits Display, Send and Sync, and be valid for the 'static lifetime, but it doesn’t say anything about what type it is. Only what it can be used for. Fn(), FnMut() and FnOnce() are also traits, but not &mut FnMut(). It’s a trait object, which is a concrete type.


#3

@ogeon

I missed the trait objects topic in the book and just assumed people were writing about objects in memory as in the traditional sense of the word object.

That’s a perfect explanation. Thank you.


#4

You are not completely mistaken. People are often referring to instances of a type as objects, and “trait objects” are just a more specific kind of objects. They are a bit like when an object in a language with a similar model as Java is cast as a super class or as an interface, to abstract away and forget the concrete type. This requires a pointer of some sort (e.g. &Trait, Box<Trait> or Rc<Trait>) to be involved, to make sure that the size is known at compiletime. You can keep calling things objects, but keep in mind that “trait object” refers to such an abstract object behind a pointer.


#5

That makes sense. Thanks again.