Recent Rust changes have made "trait objects" more prominent to me, but I only have a nebulous grasp of what actually makes something into a trait object. One change in particular is the upcoming change to allow trait objects to forward trait implementations to the inner type.
Given a trait Foo, I'm pretty sure that Box<Foo> / Box<dyn Foo> is a trait object. Is &Foo / &dyn Foo also a trait object? What about other smart-pointer things like Rc or Arc? How could I make my own type that would count as a trait object?
The reference only mentions trait objects once, but nothing like a definition.
You have trait objects when you have a pointer to a trait.
Box, Arc, Rc and the reference & are all, at their core, pointers. In terms of defining a "trait object" they work in the same way.
"Trait objects" are Rust's take on dynamic dispatch.
Here's an example that I hope helps show what trait objects are:
// define an example struct, make it printable
#[derive(Debug)]
struct Foo;
// an example trait
trait Bar {
fn baz(&self);
}
// implement the trait for Foo
impl Bar for Foo {
fn baz(&self) {
println!("{:?}", self)
}
}
// This is a generic function that takes any T that implements trait Bar.
// It must resolve to a specific concrete T at compile time.
// The compiler creates a different version of this function
// for each concrete type used to call it so &T here is NOT
// a trait object (as T will represent a known, sized type
// after compilation)
fn static_dispatch<T>(t: &T)
where
T: Bar,
{
t.baz(); // we can do this because t implements Bar
}
// This function takes a pointer to a something that implements trait Bar
// (it'll know what it is only at runtime). &dyn Bar is a trait object.
// There's only one version of this function at runtime, so this
// reduces the size of the compiled program if the function
// is called with several different types vs using static_dispatch.
// However performance is slightly lower, as the &dyn Bar that
// dynamic_dispatch receives is a pointer to the object +
// a vtable with all the Bar methods that the object implements.
// Calling baz() on t means having to look it up in this vtable.
fn dynamic_dispatch(t: &dyn Bar) {
// ----------------^
// this is the trait object! It would also work with Box<dyn Bar> or
// Rc<dyn Bar> or Arc<dyn Bar>
//
t.baz(); // we can do this because t implements Bar
}
fn main() {
let foo = Foo;
static_dispatch(&foo);
dynamic_dispatch(&foo);
}
For further reference, there is a good Trait Objects chapter of the Rust book
Short Answer: You can only make object-safe traits into trait objects.
Object-Safe Traits: Traits that do not resolve to concrete type of implementation. In practice two rules govern if a trait is object-safe.
The return type isn’t Self.
There are no generic type parameters.
Any trait satisfying these two rules can be used as trait objects.
Example of trait that is object-safe can be used as trait object:
trait Draw {
fn draw(&self);
}
Example of trait that cannot be used as trait object:
trait Draw {
fn draw(&self) -> Self;
}
For detailed explanation: https://doc.rust-lang.org/book/second-edition/ch17-02-trait-objects.html
Trait objects are the Rust implementation of dynamic dispatch. Dynamic dispatch allows one particular implementation of a polymorphic operation (trait methods) to be chosen at run time. Dynamic dispatch allows a very flexible architecture because we can swap function implementations out at runtime. However, there is a small runtime cost associated with dynamic dispatch.
The variables/parameters which hold the trait objects are fat pointers which consists of the following components:
pointer to the object in memory
pointer to that object’s vtable, a vtable is a table with pointers which point to the actual method(s) implementation(s).
Example
struct Point {
x: i64,
y: i64,
z: i64,
}
trait Print {
fn print(&self);
}
// dyn Print is actually a type and we can implement methods on it
impl dyn Print + 'static {
fn print_traitobject(&self) {
println!("from trait object");
}
}
impl Print for Point {
fn print(&self) {
println!("x: {}, y: {}, z: {}", self.x, self.y, self.z);
}
}
// static dispatch (compile time): compiler must know specific versions
// at compile time generates a version for each type
// compiler will use monomorphization to create different versions of the function
// for each type. However, because they can be inlined, it generally has a faster runtime
// compared to dynamic dispatch
fn static_dispatch<T: Print>(point: &T) {
point.print();
}
// dynamic dispatch (run time): compiler doesn't need to know specific versions
// at compile time because it will use a pointer to the data and the vtable.
// The vtable contains pointers to all the different different function implementations.
// Because it has to do lookups at runtime it is generally slower compared to static dispatch
// point_trait_obj is a trait object
fn dynamic_dispatch(point_trait_obj: &(dyn Print + 'static)) {
point_trait_obj.print();
point_trait_obj.print_traitobject();
}
fn main() {
let point = Point { x: 1, y: 2, z: 3 };
// On the next line the compiler knows that the generic type T is Point
static_dispatch(&point);
// This function takes any obj which implements Print trait
// We could, at runtime, change the specfic type as long as it implements the Print trait
dynamic_dispatch(&point);
}
This question already has good answers about what a trait object is. Let me give here an example of when we might want to use trait objects and why. I'll base my example on the one given in the Rust Book.
Let's say we need a GUI library to create a GUI form. That GUI form will consist of visual components, such as buttons, labels, check-boxes, etc. Let's ask ourselves, who should know how to draw a given component? The library or the component itself? If the library came with a fixed set of all the components you might ever need, then it could internally use an enum where each enum variant represents a single component type and the library itself could take care of all the drawing (as it knows all about its components and how exactly they should be drawn). However, it would be much better if the library allowed you to also use third-party components or ones that you wrote by yourself.
In OOP languages like Java, C#, C++ and others, this is typically done by having a component hierarchy where all components inherit a base class (let's call it Component). That Component class would have a draw() method (which could even be defined as abstract, so as to force all sub-classes to implement that method).
However, Rust doesn't have inheritance. Rust enums are very powerful, as each enum variant can have different types and amounts of associated data, and they are often used in situations where you'd use inheritance in a typical OOP language. An important advantage of using enums and generics in Rust is that everything is known at compile time, which means you don't need to sacrifice performance (no need for things like vtables). But in some cases, as in our example, enums don't provide enough flexibility. The library needs to keep track of components of different type and it needs a way to call methods on components that it doesn't even know about. That's generally known as dynamic dispatch and as explained by others, trait objects are the Rust way of doing dynamic dispatch.
Related
I get the implications of object safety, but I'm trying to find an idiomatic way to solve for this situation.
Say I have two structs that share common behavior and also need to derive PartialEq for comparison in another part of the program:
trait Growl:PartialEq {
fn growl(&self);
}
#[derive(PartialEq)]
struct Pikachu;
#[derive(PartialEq)]
struct Porygon;
impl Growl for Pikachu {
fn growl(&self) {
println!("pika");
}
}
impl Growl for Porygon {
fn growl(&self) {
println!("umm.. rawr?");
}
}
In another struct, I want to hold a Vec of these objects. Since I can't use a trait object with Vec<Box<Growl>>...
struct Region{
pokemon: Vec<Box<dyn Growl>>,
}
// ERROR: `Growl` cannot be made into an object
... I need to get more creative. I read this article, which suggests using an enum or changing the trait. I haven't yet explored type erasure, but it seems heavy-handed for my use case. Using an enum like this is what I've ended up doing but it feels unnecessarily complex
enum Pokemon {
Pika(Pikachu),
Pory(Porygon),
}
Someone coming through this code in the future now needs to understand the individual structs, the trait (which provides all functionality for the structs), and the wrapper enum type to make changes.
Is there a better solution for this pattern?
I read this article, which suggests using an enum or changing the trait. I haven't yet explored type erasure, but it seems heavy-handed for my use case.
Type erasure is just a synonym term for dynamic dispatch - even your original Box<dyn Growl> "erases the type" of the Pokemon. What you want here is to continue in the same vein, by creating a new trait better taylored to your use case and providing a blanket implementation of that trait for any type that implements the original trait.
It sounds complex, but it's actually very simple, much simpler than erased-serde, which has to deal with serde's behemoth traits. Let's go through it step by step. First, you create a trait that won't cause issues with dynamic dispatch:
/// Like Growl, but without PartialEq
trait Gnarl {
// here you'd have only the methods which are actually needed by Region::pokemon.
// Let's assume it needs growl().
fn growl(&self);
}
Then, provide a blanket implementation of your new Gnarl trait for all types that implement the original Growl:
impl<T> Gnarl for T
where
T: Growl,
{
fn growl(&self) {
// Here in the implementation `self` is known to implement `Growl`,
// so you can make use of the full `Growl` functionality, *including*
// things not exposed to `Gnarl` like PartialEq
<T as Growl>::growl(self);
}
}
Finally, use the new trait to create type-erased pokemon:
struct Region {
pokemon: Vec<Box<dyn Gnarl>>,
}
fn main() {
let _region = Region {
pokemon: vec![Box::new(Pikachu), Box::new(Porygon)],
};
}
Playground
I would to write a method that returns self.hash_map.keys() while hiding from the caller the concrete type Keys<'_, K, V>.
A downside of the Keys return type is that it exposes to the caller that the K elements are coming from a HashMap. Intuitively, it seems as though it shouldn't be necessary to expose this information.
What is the cheapest way (with respect to CPU/allocations) to return an iterator of key references?
Can it be accomplished by some precise choice of return type? Is some form of type-erasure possible?
Or does it require an invocation in the function body? Is some transformation necessary?
Both of the options you speculated about are possible.
The simplest option is to use the impl type syntax, which is an “existential” type: “I am going to return a value which implements Iterator but I'm not telling you what the concrete type is”. In this case, the compiler knows what the type is (so the compiled code is exactly the same as if it wasn't hidden), but the user of your method cannot rely on anything but the specified trait, so you aren't leaking implementation details.
impl MyType {
fn keys(&self) -> impl Iterator<Item = &MyKeyType> {
self.hash_map.keys()
}
}
(Note that this resembles but is not the same as dyn Iterator; when you use dyn, you're using runtime dispatch and the same function can return different concrete types from different calls to it. With impl, the type is static, just hidden, and there is no overhead.)
The disadvantage of this option is that the type is entirely unnameable; for example, nobody can write a structure that holds your keys() iterator except by making it generic over all Iterators. (This is rarely a problem for iterators in particular, since iterator wrappers are usually generic anyway.)
Also, if your iterator implements any additional traits you want to allow the caller to use, like Debug or ExactSizeIterator, then you need to add them to the impl type or they won't be visible.
Another option is to wrap the iterator in your own struct. This allows you to hide the implementation type while still allowing callers to refer to it by name, so it's the most flexible. The disadvantage of this option is that you have to explicitly implement Iterator (and any other traits) for the wrapper:
impl MyType {
fn keys(&self) -> MyKeyIterator<'_> {
MyKeyIterator(self.hash_map.keys())
}
}
#[derive(Clone, Debug)]
struct MyKeyIterator<'a>(Keys<'a, MyKeyType, MyValueType>);
impl<'a> Iterator for MyKeyIterator<'a> {
type Item = &'a MyKeyType;
fn next(&mut self) -> Option<&'a MyKeyType> {
self.0.next()
}
}
Rust Playground link with supporting code
This wrapper should not add any performance cost (when compiled with optimization), except that by default the wrapper method will not be inlined if called from another crate. If you're writing a library and this method is performance-sensitive, you can either enable link-time optimization (LTO) in the build, or add #[inline] to the next method (which enables cross-crate inlining). Of course, don't do any such tweaking without checking whether it makes a difference to actual performance; otherwise you're just increasing compile time (and instruction cache thrashing).
Can it be accomplished by some precise choice of return type? Is some form of type-erasure possible?
Yes! You can return an impl Trait to indicate that you're returning a type that implements Trait but doesn't expose the concrete type:
fn keys(&self) -> impl Iterator<Item = &K> {
self.hash_map.keys()
}
See it working on the playground.
For some OpenGL code, I created a trait Vertex. currently it looks like this
trait Vertex: Sized + Clone {
//...
}
Because of the way Vertex's are used in my program, its very important that anything is a vertex uses the packed representation. If there is any padding it could potentially create problems. Is there any way I can constrain the Vertex trait to only types that use the packed representation? If need be, I could just define my own marker trait that I manually implement for everything that implements Vertex. It seems like something the compiler could easily surface and enforce properly, but I haven't been able to find any kind of representation trait. Thanks
This question gave me the impetus to finish a project I had lying around to do exactly this.
I just pushed it to crates.io. I've been using it for some similar work (dealing with some strange FFI's), but never published it.
It lets you write this code:
use repr_trait::Packed;
// Safety: Only safe to call when T has #[repr(packed)]
unsafe fn safe_when_packed<T>(_param: T) {
unimplemented!()
}
fn safe_wrapper<T: Packed>(param: T) {
// Safety: Safe because T is guaranteed to be #[repr(packed)]
unsafe {
safe_when_packed(param)
}
}
#[derive(Packed, Default)]
#[repr(packed)]
struct PackedData(u32, u8);
safe_wrapper(PackedData(123, 45));
But this is a compile error:
#[derive(Packed)]
struct NotPacked(u32, u8);
You would write your vertex trait as:
trait Vertex: Sized + Clone + Packed {
//...
}
I have a simple trait with an associated type with no bounds.
trait Board {
type Move;
fn moves(&self) -> Vec<Self::Move>;
}
I also want to use this trait as a supertrait. In particular, I want my new subtrait to have stricter bounds on the associated type. Something like this:
trait TextBoard: Board {
type Move: fmt::Debug; // Trying to tighten bounds on associated type
fn printMoves(&self) {
println!("{:?}", self.moves());
}
}
The example is highly simplified, but seems to show the problem: The compiler thinks I'm trying to create a new associated type, but I just want the subtrait to require tighter bounds. Is there any way to achieve this?
Here's what you asked for:
trait TextBoard: Board
where
Self::Move: Debug,
{
// ...
}
All the bounds on a trait have to be in the "headline"; you can't impose additional restrictions once you start writing the body of the trait. This bound will prevent you from writing impl TextBoard for Foo when <Foo as Board>::Move does not implement Debug (playground).
Maybe that is what you want, but do you really need to prevent implementing TextBoard for other types? For some type there could be another way to write print_moves that makes more sense, and the Debug requirement is just noise. In that case you probably want to skip the where clause and move the body of print_moves to a blanket impl:
trait TextBoard {
fn print_moves(&self);
}
impl<B: Board> TextBoard for B
where
B::Move: Debug, // or <Self as Board>::Move: Debug
{
fn print_moves(&self) {
println!("{:?}", self.moves());
}
}
With this version, you still don't need to write an impl for types where Self::Move: Debug, but you're not prevented from writing an impl for other types where that doesn't hold. It's more of an extension than a refinement.
On the other hand, you should pretty much always implement Debug for every type, so is it really useful to have that trait? Maybe what you want is just an optional method on Board that's implemented when Move: Debug:
trait Board {
type Move;
fn moves(&self) -> Vec<Self::Move>;
fn print_moves(&self)
where
Self::Move: Debug,
{
println!("{:?}", self.moves());
}
}
This is like the original version, but doesn't require the addition of a new TextBoard trait, so it will probably cut down on the number of explicit bounds you have to write. Many of the standard library traits such as Iterator have optional methods defined with bounds like this. The downside, besides the requirement that Move must be Debug, is that it clutters the Board trait with printing code, which you might not consider really part of what it means to be a Board.
When compiling the following code:
trait RenderTarget {}
struct RenderWindow;
impl RenderTarget for RenderWindow {}
trait Drawable {
fn draw<RT: RenderTarget>(&self, target: &mut RT);
}
fn main() {
let mut win = RenderWindow;
let mut vec: Vec<Box<Drawable>> = Vec::new();
for e in &vec {
e.draw(&mut win);
}
}
I get the error:
error: the trait `Drawable` is not implemented for the type `Drawable` [E0277]
src/main.rs:15 e.draw(&mut win);
^~~~~~~~~~~~~~
What is the error message trying to tell? Also, how to fix it?
There's a related question but the solution there was to modify the trait A (which corresponds to Drawable in my case), but that's not possible here since Drawable is from an external library.
Update: fixed object safety rules to the 1.0 version of them. Namely, by-value self makes method object-unsafe no longer.
This error happens because of object safety.
In order to be able to create a trait object out of a trait, the trait must be object-safe. A trait is object-safe if both of these statements hold:
it does not have Sized requirement, as in trait Whatever: Sized {};
all its methods are object-safe.
A method is object-safe if both of these statements are true:
it has where Self: Sized requirement, as in fn method() where Self: Sized;
none of the following statements holds:
this method mentions Self in their signature in any form, even under a reference, except associated types;
this method is static;
this method is generic.
These restrictions are in fact fairly natural if you think more of them.
Remember that when values are made into trait objects, actual information of their type is erased, including their size. Therefore, trait objects can only be used through a reference. References (or other smart pointers, like Box or Rc), when applied to trait objects, become "fat pointers" - along with the pointer to the value, they also contain a pointer to the virtual table for that value.
Because trait objects can only be used through a pointer, by-value self methods can't be called on them - you'd need the actual value in order to call such methods. This was a violation of object safety at one point, which meant that traits with such methods couldn't be made trait objects, however, even before 1.0 the rules had been tweaked to allow by-value self methods on trait objects. These methods still can't be called, though, due to the reason described above. There are reasons to expect that in the future this restriction will be lifted because it currently leads to some quirks in the language, for example, the inability to call Box<FnOnce()> closures.
Self can't be used in methods which should be called on trait objects precisely because trait objects have their actual type erased, but in order to call such methods the compiler would need to know this erased type.
Why static methods can't be called on trait objects, I guess, is obvious - static methods by definition "belong" to the trait itself, not to the value, so you need to know the concrete type implementing the trait to call them. More concretely, regular methods are dispatched through a virtual table stored inside a trait object, but static methods do not have a receiver, so they have nothing to dispatch on, and for this reason they can't be stored in a virtual table. Thus they are uncallable without knowing the concrete type.
Generic trait methods can't be called for another reason, more technical than logical, I think. In Rust generic functions and methods are implemented through monomorphization - that is, for each instantiation of a generic function with a concrete set of type parameters the compiler generate a separate function. For the language user it looks like that they're calling a generic function; but on the lowest level for each set of type parameters there is a separate copy of the function, specialized to work for the instantiated types.
Given this approach, in order to call generic methods on a trait object you would need its virtual table to contain pointers to virtually each and every possible instantiation of the generic method for all possible types, which is, naturally, impossible because it would require infinite number of instantiations. And so calling generic methods on trait objects is disallowed.
If Drawable is an external trait, then you're stuck - it is impossible to do what you want, that is, to call draw() on each item in a heterogeneous collection. If your set of drawables is statically known, you can create a separate collection for each drawable type or, alternatively, create your own enum which would contain a variant for each drawable type you have. Then you can implement Drawable for the enum itself, which would be fairly straightforward.
I refer to Vladimir's excellent answer which explains Object's safety, however I am afraid than in the middle of the discussion the concrete problem at hand was forgotten.
As Vladimir mentions, the issue is that a method generic over types (generic over lifetimes is fine) renders the trait it belongs to unusable for run-time polymorphism; this, in Rust, is called Object Safety.
The simplest fix, therefore, is to remove the generic parameter of the method!
trait RenderTarget {}
struct RenderWindow;
impl RenderTarget for RenderWindow {}
trait Drawable {
fn draw(&self, target: &mut RenderTarget);
}
fn main() {
let mut win = RenderWindow;
let mut vec: Vec<Box<Drawable>> = Vec::new();
for e in &vec {
e.draw(&mut win);
}
}
The main difference between:
fn draw<RT: RenderTarget>(&self, target: &mut RT)
and
fn draw(&self, target: &mut RenderTarget)
is that the latter requires RenderTarget to be Object Safe too as it is now used in a run-time polymorphism situation (so, no static method, no generic method, no Self, ...).
Another (more technical) difference is that the former is "monorphised" at compile-time (that is RT is substituted with the real type and all relevant optimizations applied) whereas the latter is not (and so, no such optimizations occur).
If you're stuck with what you're given, there are two options you could try.
In this case, you can't, but if you were given an unsized RenderTarget
trait Drawable {
fn draw<RT: RenderTarget + ?Sized>(&self, target: &mut RT);
}
you could implement
trait DrawableDynamic {
fn draw(&self, target: &mut RenderTarget);
}
impl<T: Drawable> DrawableDynamic for T {
fn draw(&self, target: &mut RenderTarget) {
Drawable::draw(self, target)
}
}
to redirect the types you're given to an object-safe dynamically dispatched alternative. It looks like such a change could be made upstream, since you can't really use the fact that RT is sized.
The other doesn't allow you to put arbitrary Drawables in your Vec, but should work without allowing unsized types upstream. This is to use an enum to wrap the possible values of the vector:
enum AllDrawable {
Square(Square),
Triangle(Triangle)
}
impl Drawable for AllDrawable {
fn draw<RT: RenderTarget>(&self, target: &mut RT) {
match *self {
AllDrawable::Square(ref x) => x.draw(target),
AllDrawable::Triangle(ref x) => x.draw(target),
}
}
}
One might want to add From implementations and such; you might find it easier if using wrapped_enum! which will automatically implement those for you.