I have this code (playground):
use std::sync::Arc;
pub trait Messenger : Sync + Send {
fn send_embed<F: FnOnce(String) -> String>(&self, u64, &str, f: F)
-> Option<u64> where Self: Sync + Send;
}
struct MyMessenger {
prefix: String,
}
impl MyMessenger {
fn new(s: &str) -> MyMessenger {
MyMessenger { prefix: s.to_owned(), }
}
}
impl Messenger for MyMessenger {
fn send_embed<F: FnOnce(String) -> String>(&self, channel_id: u64, text: &str, f: F) -> Option<u64> {
println!("Trying to send embed: chid={}, text=\"{}\"", channel_id, text);
None
}
}
struct Bot {
messenger: Arc<Messenger>,
}
impl Bot {
fn new() -> Bot {
Bot {
messenger: Arc::new(MyMessenger::new("HELLO")),
}
}
}
fn main() {
let b = Bot::new();
}
I wanted to make a polymorphic object (trait Messenger
and one of polymorphic implementations is MyMessenger
). But when I try to compile it I have an error:
error[E0038]: the trait `Messenger` cannot be made into an object
--> <anon>:25:5
|
25 | messenger: Arc<Messenger>,
| ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Messenger` cannot be made into an object
|
= note: method `send_embed` has generic type parameters
I have found that I must require Sized
in this case, but this does not solve it. If I change my send_embed
method to the following:
fn send_embed<F: FnOnce(String) -> String>(&self, u64, &str, f: F)
-> Option<u64> where Self: Sized + Sync + Send;
Then it compiles successfully but:
- Why do we need
Sized
here? This violates polymorphism if we can not use this method from a trait object. We actually can't use this method from
Arc<Messenger>
then:fn main() { let b = Bot::new(); b.messenger.send_embed(0u64, "ABRACADABRA", |s| s); }
Gives:
error[E0277]: the trait bound `Messenger + 'static: std::marker::Sized` is not satisfied --> <anon>:37:17 | 37 | b.messenger.send_embed(0u64, "ABRACADABRA", |s| s); | ^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `Messenger + 'static` | = note: `Messenger + 'static` does not have a constant size known at compile-time
I am totally stuck here. No idea how to use polymorphism with generic method in a trait. Is there a way?
Dynamic dispatch (i.e. calling methods through trait objects) works by calling through a vtable, (i.e. using a function pointer), since you don't know at compile time which function it will be.
But if your function is generic, it needs to be compiled differently (monomorphised) for every instance of
F
which is actually used. Which means you'll have a different copy ofsend_embed
for every different closure type it's called with. Every closure is a different type.These two models are incompatible: you can't have a function pointer which works with different types.
However, you can change the method to use a trait object as well instead of being compile-time generic:
(Playground)
Instead of a different
send_embed
for every type which can beFn(String) -> String
, it now accepts a trait object reference. (You could also use aBox<Fn()>
or similar). You do have to useFn
orFnMut
and notFnOnce
, since the latter takesself
by value, i.e. it's also not object safe (the caller doesn't know what size to pass in as the closure'sself
parameter).You can still call
send_embed
with a closure/lambda function, but it just needs to be by reference, like this:I've updated the playground to include an example of calling
send_embed
directly with a referenced closure, as well as the indirect route through a generic wrapper onBot
.Traits and Traits
In Rust, you can use
trait
to define an interface comprised of:and you can use traits either:
However... only some traits can be used directly as types. Those traits that do are labeled Object Safe.
It is now considered unfortunate that a single
trait
keyword exists to define both full-featured and object-safe traits.Interlude: How does run-time dispatch work?
When using a trait as a type:
&Trait
,Box<Trait>
,Rc<Trait>
, ... the run-time implementation uses a fat pointer composed of:Method calls are dispatched through the virtual pointer to a virtual table.
For a trait like:
implemented for type
X
, the virtual table will look like(<X as A>::one, <X as A>::two)
.The run-time dispatch is thus performed by:
This means that
<X as A>::two
looks like:Why cannot I use any trait as a type? What's Object Safe?
It's a technical limitation.
There are a number of traits capabilities that cannot be implemented for run-time dispatches:
Self
in the signature.There are two ways to signal this issue:
trait
as a type if it has any of the above,trait
as a type.For now, Rust chooses to signal the issue early on: traits that do not use any of the above features are call Object Safe and can be used as types.
Traits that are not Object Safe cannot be used as types, and an error is immediately triggered.
Now what?
In your case, simply switch from compile-time polymorphism to run-time polymorphism for the method:
There is a little wrinkle:
FnOnce
requires moving out of thef
and it's only borrowed here, so instead you need to useFnMut
orFn
.FnMut
is next more generic method, so:This makes the
Messenger
trait Object Safe and therefore allows you to use a&Messenger
,Box<Messenger>
, ...A generic method cannot be made object-safe, because you can't implement a vtable with it. @ChrisEmerson's answer explained in detail why.
In your case, you could make
send_embed
object-trait, by makingf
take a trait-object instead of generic parameter. If your function accepts anf: F where F: Fn(X) -> Y
, you could make it acceptf: &Fn(X) -> Y
, similarly for FnMutf: &mut FnMut(X) -> Y
. FnOnce is more tricky since Rust doesn't support moving unsized types, but you could try to Box it:However, as of Rust 1.17.0 you cannot box an FnOnce and call it, you have to use FnBox:
If you don't want to use unstable feature, you could use the crate boxfnonce as a workaround: