3

In the book Rust for Rustaceans, the author writes:

Broadly speaking, though, you’ll want to use static dispatch in your libraries and dynamic dispatch in your binaries. In a library, you want to allow your users to decide what kind of dispatch is best for them, since you don’t know what their needs are.

I guess that, in the binary case, he refers to this:

fn flexible_dispatch_method(_: &dyn MyTrait) {}

// static dispatch
//
let obj = MyType {};
flexible_dispatch_method(&obj);

// dynamic dispatch
//
let trait_obj: &dyn MyTrait = &MyType {};
flexible_dispatch_method(trait_obj);

Given the above, what's the advantage of using boxed objects instead of trait objects? Is it because of the need to use lifetimes:

fn return_new_trait_object<'a>() -> &'a dyn MyTrait {
    &MyType {}
}

or there is something else? Based on my understanding, in the dynamic dispatch case, the object needs to be allocated in the heap, anyway, so I presume there isn't much difference with a boxed object.

Marcus
  • 5,104
  • 2
  • 28
  • 24
  • In addition to the great answer, let me just point out that returning `&MyType {}` doesn't (and can't) compile. You can only return a reference to the data you've previously received, not to newly created data. Attempting to do the latter creates a dangling reference and is prevented by the borrow checker. – user4815162342 May 17 '22 at 06:54
  • @user4815162342 Technically it should compile in this case because of static promotion. But in the general case, yeah. – Chayim Friedman May 17 '22 at 14:58

1 Answers1

12

I think you might be misunderstanding a couple things here.

  • (Generally) static dispatch occurs whenever you call a method on a concrete type that's not dyn Trait. It's when the compiler decides which function to call at compile-time.
  • A trait object is anything that's some pointer to a dyn Trait, including Box<dyn Trait>, &dyn Trait, Arc<dyn Trait>, etc. When you call a function through a dyn Trait, the compiler inserts code to lookup the function to call at runtime, allowing for polymorphism and greater flexibility.

In your code, flexible_dispatch_method(_: &dyn MyTrait) always uses dynamic dispatch, as signaled by the fact that its argument has the type &dyn MyTrait. An example of static dispatch would be the following:

fn flexible_dispatch_method<T: MyTrait + ?Sized>(_: &T) {}

With this declaration your first usage would use static dispatch and the second would use dynamic dispatch.

Dynamic dispatch is a little more flexible since it avoids having generics all over the place. This can be useful for writing large applications where you might want to have polymorphism and easily add new implementations. However, dynamic dispatch has a performance cost, so that's why libraries should leave the dispatch choice up to the caller as much as possible.

As per when to use &dyn Trait vs Box<dyn Trait>: it's all based on ownership. If you want an owned trait object use Box<dyn Trait>, and if you want a borrowed trait object use &dyn Trait.

Ian S.
  • 1,831
  • 9
  • 17
  • Thanks! Yes, I was very confused by the book – Marcus May 16 '22 at 23:06
  • 2
    I presume you're talking about the book by Jon Gjengset (same title at least)? I recall him mentioning that the book was mainly targeted towards people who already knew Rust and wanted to take a deeper dive. This is not to say you shouldn't read it, I have a lot of respect for Jon's work and I'm sure it's amazing, you might just be able to get more out of it after reading [the Rust book](https://doc.rust-lang.org/book/) to get more familiar with the language first. The Rust community is always here to help you if you get stuck! – Ian S. May 16 '22 at 23:13
  • 1
    "Dynamic dispatch is a little more flexible since it avoids having generics all over the place" - this is an argument for it being more _convenient_, not more _flexible_. If anything, static dispatch is more flexible because it can be used on non-object-safe traits, too, and be generic over associated types and consts etc.. And I'm not even sure dyn is more convenient: `impl Trait` is not that worse than `dyn Trait`, and doesn't require an indirection. However, both have their uses, and there are places when you need to use `dyn Trait`. – Chayim Friedman May 17 '22 at 15:03
  • @IanS. Thanks for the tip, but I've actually read several Rust books, and I have experience on a few non-trivial Rust projects (but I'm not a professional Rust programmer). My impression is that Gjengset's book is an advanced book (fair enough), rather than an intermediate one as claimed. Of course, this is subjective, but I've found that there is a significant gap between this book and the many others on the market. – Marcus May 18 '22 at 08:00