Why using boxed objects over trait objects? - rust

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.

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.

Related

Why is rust async forcing the implementors to use dynamic dispatch for `Waker`

I was reading about how async/await was implemented in rust and I noticed that the RawWaker is essentially a fat pointer. I don't quite understand why the Waker is implemented essentially like struct Waker{data: *const (), vtable: *const WakerVTable} rather than Waker<T: Wakes>{raw_walker: &mut T} which would allow the compiler to know the implementations of wake(), wake_ref(), drop() and clone() at compile time.
Wakers need to be incredibly versatile. This blog explains a lot about the design choices, and it's worth a read. The relevant part says this:
The only requirement that the poll phase introduces is dynamism: because it needs to be passed through arbitrary futures, the waker type cannot be generic. This means that every requirement introduced by the other two phases needs to be dynamically dispatched.
Now the part that the author wasn't clear on here is that "arbitrary futures" includes boxed futures (and futures which can be polled using dynamic dispatch in general). If the definition of the future trait were the following:
pub trait Future {
type Output;
fn poll<W: Wake>(
self: Pin<&mut Self>,
cx: &mut Context<'_, W>,
) -> Poll<Self::Output>;
}
then the trait Future would not be object safe, eliminating the possibility of dynamic dispatch, which was a deal-breaker.

Differences generic trait-bounded method vs 'direct' trait method

I have this code:
fn main() {
let p = Person;
let r = &p as &dyn Eatable;
Consumer::consume(r);
// Compile error
Consumer::consume_generic(r);
}
trait Eatable {}
struct Person;
impl Eatable for Person {}
struct Consumer;
impl Consumer {
fn consume(eatable: &dyn Eatable) {}
fn consume_generic<T: Eatable>(eatable: &T) {}
}
Error:
the size for values of type dyn Eatable cannot be known at
compilation time
I think it is strange. I have a method that literally takes a dyn Eatable and compiles fine, so that method knows somehow the size of Eatable. The generic method (consume_generic) will properly compile down for every used type for performance and the consume method will not.
So a few questions arise: why the compiler error? Are there things inside the body of the methods in which I can do something which I can not do in the other method? When should I prefer the one over the other?
Sidenote: I asked this question for the language Swift as well: Differences generic protocol type parameter vs direct protocol type. In Swift I get the same compile error but the underlying error is different: protocols/traits do not conform to themselves (because Swift protocols can holds initializers, static things etc. which makes it harder to generically reference them). I also tried it in Java, I believe the generic type is erased and it makes absolutely no difference.
The problem is not with the functions themselves, but with the trait bounds on types.
Every generic types in Rust has an implicit Sized bound: since this is correct in the majority of cases, it was decided not to force the developer to write this out every time. But, if you are using this type only behind some kind of reference, as you do here, you may want to lift this restriction by specifying T: ?Sized. If you add this, your code will compile fine:
impl Consumer {
fn consume(eatable: &dyn Eatable) {}
fn consume_generic<T: Eatable + ?Sized>(eatable: &T) {}
}
Playground as a proof
As for the other questions, the main difference is in static vs dynamic dispatch.
When you use the generic function (or the semantically equivalent impl Trait syntax), the function calls are dispatched statically. That is, for every type of argument you pass to the function, compiler generates the definition independently of others. This will likely result in more optimized code in most cases, but the drawbacks are possibly larger binary size and some limitations in API (e.g. you can't easily create a heterogeneous collection this way).
When you use dyn Trait syntax, you opt in for dynamic dispatch. The necessary data will be stored into the table attached to trait object, and the correct implementation for every trait method will be chosen at runtime. The consumer, however, needs to be compiled only once. This is usually slower, both due to the indirection and to the fact that individual optimizations are impossible, but more flexible.
As for the recommendations (note that this is an opinion, not the fact) - I'd say it's better to stick to generics whenever possible and only change it to trait objects if the goal is impossible to achieve otherwise.

Why can a function on a trait object not be called when bounded with `Self: Sized`?

I have the following code:
trait Bar {
fn baz(&self, arg: impl AsRef<str>)
where
Self: Sized;
}
struct Foo;
impl Bar for Foo {
fn baz(&self, arg: impl AsRef<str>) {}
}
fn main() {
let boxed: Box<dyn Bar> = Box::new(Foo);
boxed.baz();
}
playground
Which results in this error:
error: the `baz` method cannot be invoked on a trait object
--> src/main.rs:15:11
|
15 | boxed.baz();
| ^^^
Why is this not possible? It works when I remove the Self: Sized bound, but then I can't use generics which make the function more comfortable for the caller.
This is not a duplicate of Why does a generic method inside a trait require trait object to be sized? which asks why you can't call baz from a trait object. I'm not asking why the bound is required; this has already been discussed.
Because Rust's generics system works through monomorphization.
In Java, for example, type parameters in a generic function turn into variables of type Object, and are casted as necessary. Generics in languages like this simply serves as a tool to help verify the correctness of types within code.
Languages such as Rust and C++ use monomorphization for generics. For each combination of type parameters a generic function is invoked with, specialized machine code is generated which runs that function with those combinations of type parameters. The function is monomorphized. This allows data to be stored in place, eliminates the cost of casting, and allows the generic code to call "static" functions on that type paramameter.
So why can't you do that on a trait object?
Trait objects in many languages, including Rust, are implemented using a vtable. When you have some type of pointer to a trait object (raw, reference, Box, reference counter, etc.), it contains two pointers: the pointer to the data, and a pointer to a vtable entry. The vtable entry is a collection of function pointers, stored in an immutable memory region, which point to the implementation of that trait's methods. Thus, when you call a method on a trait object, it looks up the function pointer of the implementation in the vtable, and then makes an indirect jump to that pointer.
Unfortunately, the Rust compiler cannot monomorphize functions, if it does not know at compile time the code that implements the function, which is the case when you call a method on a trait object. For that reason, you cannot call a generic function (well, generic over types) on a trait object.
-Edit-
It sounds like you're asking why the : Sized restriction is necessary.
: Sized makes it so that the trait cannot be used as a trait object. I suppose there could be a couple of alternatives. Rust could implicitly make any trait with generic functions not object safe. Rust could also implicitly prevent generic functions from being called on trait objects.
However, Rust tries to be explicit with what the compiler is doing, which these implicit approaches would go against. Wouldn't it be confusing, anyways, for a beginner to try and call a generic function on a trait object and have it fail to compile?
Instead, Rust lets you explicitly make the entire trait not object safe
trait Foo: Sized {
Or explicitly make certain functions only available with static dispatch
fn foo<T>() where Self: Sized {
The bound makes the method not object safe. Traits that are not object safe cannot be used as types.
Methods that take Self as an argument, return Self or otherwise require Self: Sized are not Object safe. That's because methods on a trait object are called via dynamic dispatch and the size of the trait implementation cannot be known at compile time. -- Peter Hall
Citing the official docs:
Only traits that are object-safe can be made into trait objects. A trait is object-safe if both of these are true:
the trait does not require that Self: Sized
all of its methods are object-safe
So what makes a method object-safe? Each method must require that Self: Sized or all of the following:
must not have any type parameters
must not use Self
See also:
this answer from Why does a generic method inside a trait require trait object to be sized?

Can I define a trait whose implementations must be `!Send`?

I'd like to define a trait which forces its implementors to under no circumstances be sent to, or shared between, threads. It should suffice to mark the trait as !Send, but Rust doesn't seem to let me.
Is it possible?
Example (playground):
#![feature(optin_builtin_traits)]
// This is a syntax error
//trait ThreadThing : !Send {}
// This doesn't work either
trait ThreadThing { }
impl !Send for ThreadThing {}
No, you can't make !Send a condition of ThreadThing. The compiler just doesn't support that kind of logic.
If it would be possible for someone using your crate to make a type that is implicitly Send, contains no unsafe code in its implementation anywhere, and make it unsafe just by implementing ThreadThing for it -- in that case, you would make ThreadThing an unsafe trait to indicate that there is unsafe code somewhere that relies on an invariant that can't be described in the type system: the invariant "Things that are Send don't implement ThreadThing".
If, as is more likely, it's only unsafe to implement Send manually for a type that implements ThreadThing -- in that case, you don't need to do anything, because manually implementing Send is unsafe already. If an implementor of ThreadThing decides to manually implement Send, they take on the burden of guaranteeing not only their own invariants, but also ThreadThing's.
The answer is: yes, you can, under some very specific conditions. Whether you should need to do this is another matter.
You can define a negative trait implementation for another trait if the trait you are negating is:
an auto-trait.
from the current crate.
So the following will work (playground):
#![feature(optin_builtin_traits)]
auto trait Scary {}
trait ThreadThing { }
impl !Scary for ThreadThing {}
But it would not work if you were trying to do:
impl !Send for ThreadThing {}
Or if Scary was not an auto-trait.
Note however that, in general it should not be necessary to mark a trait !Send in this way. The concrete implementations of the trait will be marked Send or !Send by the Rust compiler based upon the contents of the implementing struct.

Is there a way other than traits to add methods to a type I don't own?

I'm trying to extend the Grid struct from the piston-2dgraphics library. There's no method for getting the location on the window of a particular cell, so I implemented a trait to calculate that for me. Then, I wanted a method to calculate the neighbours of a particular cell on the grid, so I implemented another trait.
Something about this is ugly and feels unnecessary seeing as how I'll likely never use these traits for anything other than this specific grid structure. Is there another way in Rust to extend a type without having to implement traits each time?
As of Rust 1.67, no, there is no other way. It's not possible to define inherent methods on a type defined in another crate.
You can define your own trait with the methods you need, then implement that trait for an external type. This pattern is known as extension traits. The name of extension traits, by convention, ends with Ext, to indicate that this trait is not meant to be used as a generic bound or as a trait object. There are a few examples in the standard library.
trait DoubleExt {
fn double(&self) -> Self;
}
impl DoubleExt for i32 {
fn double(&self) -> Self {
*self * 2
}
}
fn main() {
let a = 42;
println!("{}", 42.double());
}
Other libraries can also export extension traits (example: byteorder). However, as for any other trait, you need to bring the trait's methods in scope with use SomethingExt;.
No. Currently, the only way to write new methods for a type that has been defined in another crate is through traits. However, this seems too cumbersome as you have to write both the trait definition and the implementation.
In my opinion, the way to go is to use free functions instead of methods. This would at least avoid the duplication caused by traits.

Resources