Traits and Traits
In Rust, you can use trait
to define an interface comprised of:
- associated types,
- associated constants,
- associated functions.
and you can use traits either:
- as compile-time bounds for generic parameters
- as types, behind references or pointers.
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:
- the data pointer,
- the virtual pointer.
Method calls are dispatched through the virtual pointer to a virtual table.
For a trait like:
trait A {
fn one(&self) -> usize;
fn two(&self, other: usize) -> usize;
}
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:
- picking the right member of the table,
- calling it with the data pointer and arguments.
This means that <X as A>::two
looks like:
fn x_as_a_two(this: *const (), other: usize) -> usize {
let x = unsafe { this as *const X as &X };
x.two(other)
}
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:
- associated types,
- associated constants,
- associated generic functions,
- associated functions with
Self
in the signature.
- ... maybe others ....
There are two ways to signal this issue:
- early: refuse to use a
trait
as a type if it has any of the above,
- late: refuse to use any of the above on a
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:
pub trait Messenger : Sync + Send {
fn send_embed(&self, u64, &str, f: &FnOnce(String) -> String)
-> Option<u64>;
}
There is a little wrinkle: FnOnce
requires moving out of the f
and it's only borrowed here, so instead you need to use FnMut
or Fn
. FnMut
is next more generic method, so:
pub trait Messenger : Sync + Send {
fn send_embed(&self, u64, &str, f: &FnMut(String) -> String)
-> Option<u64>;
}
This makes the Messenger
trait Object Safe and therefore allows you to use a &Messenger
, Box<Messenger>
, ...