Re-boxing trait objects to generically implemented trait - rust

So far I have rarely had issues with Rust's type inference, but I fear I don't quite understand the problem with the following code:
trait SpecificTrait {}
struct SpecificStruct;
impl SpecificTrait for SpecificStruct {}
trait GeneralTrait {}
impl<T: SpecificTrait> GeneralTrait for T {}
fn new_specific_box() -> Box<dyn SpecificTrait> {
Box::new(SpecificStruct {})
}
fn new_general_box(from_specific_box: bool) -> Box<dyn GeneralTrait> {
if from_specific_box {
new_specific_box()
} else {
Box::new(SpecificStruct {})
}
}
Playground
I assume it has to do with Rust probably still not supporting upcasting, though in this code SpecificTrait does not require GeneralTrait, but rather implements the more general trait generically over all types that implement SpecificTrait.
I am aware that the trait object types are different (which leads to the error in the above code), but I would expect type inference to acknowledge that every dyn SpecificTrait object should also be expressable as a dyn GeneralTrait object. However, I also cannot simply cast a Box<dyn SpecificTrait> as Box<dyn GeneralTrait>, either.
So, how would I (idomatically) have to go about re-expressing my Box<dyn SpecificTrait> as a Box<dyn GeneralTrait>?

I would expect type inference to acknowledge that every dyn SpecificTrait object should also be expressable as a dyn GeneralTrait object
But it isn't. A dyn SpecificTrait includes a pointer to the "virtual table" of function pointers to SpecificTrait methods, and you can't get a pointer to the corresponding virtual table for GeneralTrait from it. One of answers to the question you linked explains the problem for subtraits in detail, but
implements the more general trait generically over all types that implement SpecificTrait
makes this even worse. With subtraits, the methods of supertraits are at least present in the subtrait vtable (24- |methods of Self and supertraits in that answer). With the blanket implementation they aren't.

Another answer explained why you can't simply cast the trait object to get the result you want. However, there is a workaround:
impl SpecificTrait for Box<dyn SpecificTrait>{}
fn new_general_box(from_specific_box: bool) -> Box<dyn GeneralTrait> {
if from_specific_box {
Box::new(new_specific_box())
} else {
Box::new(SpecificStruct {})
}
}
In words, simply implement your specific trait for a boxed trait object, then box that. Not the most efficient, but it will work.
Playground

Maybe I did not get the deeper problem you are trying to express, but from the code, the reason why it is not compile is bacause the return type of the function fn new_specific_box() -> Box<dyn SpecificTrait> is Box<dyn SpecificTrait> where as you are expecting Box<dyn GeneralTrait>. These two are different types, so this code will not compile. If you can match the type, then it should be okay to compile.

Related

Why is it necessary to specify associated types in trait objects

This code doesn't compile:
trait Trait {
type Type;
}
struct Struct;
impl Trait for Struct {
type Type = i32;
}
fn main() {
Box::new(Struct) as Box<dyn Trait>;
}
The fix is to change
Box::new(Struct) as Box<dyn Trait>;
into
Box::new(Struct) as Box<dyn Trait<Type = i32>>;
I want to know why we have to specify associated types in trait objects. Wouldn't compiler look for impl Trait for Struct and find type Type = i32?
Think about this from another side. You got Box<dyn Trait>. How can you know what is Trait::Type? Well, you cannot. Trait objects function as type erasure. And since you could depend on this associated type you must be sure you can name it.
If you for example got Vec<Box<dyn Trait>> and want to get Vec<Trait::Type> (assume that Trait::Type: Clone for the sake of this example) how can you require all trait object's to have the same associated type? The answer is in your question. You simply must specify it. Without it you would pretty fast run in the wall.

What does this "As lifts over &" AsRef impl mean?

On debugging (step-in) this Rust code:
use std::fs;
fn main() {
fs::create_dir("my_dir").unwrap();
}
I go to an impl I feel hard to understand:
// rustup\toolchains\stable-x86_64-pc-windows-gnu\lib\rustlib\src\rust\library\core\src\convert\mod.rs
// As lifts over &
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized, U: ?Sized> AsRef<U> for &T
where
T: AsRef<U>,
{
fn as_ref(&self) -> &U {
<T as AsRef<U>>::as_ref(*self)
}
}
Which then calls:
#[stable(feature = "rust1", since = "1.0.0")]
impl AsRef<Path> for str {
#[inline]
fn as_ref(&self) -> &Path {
Path::new(self)
}
}
What does this "As lifts over &" impl mean? Why it is needed?
This piece of code:
<T as AsRef<U>>
I've never seen this syntax before. What does it mean?
Seems like there is a lot of implicit conversions like this done by Rust compiler behind our daily Rust code. Is there any way to learn them besides debugging?
I assume As lifts over & simply means that if T can be taken as a reference to U, so does &T, because why not?
It also gives some convenience, e.g. if you happened to have a value of type &&&&&&T, you won't need to (******t).as_ref(), it's just t.as_ref(), because by using such implementation, as_ref goes through all levels of references up to the T object itself and takes it as a reference to U.
How can you learn it? Well, the top 3 most popular Rust textbooks have it in some way (not necessarily explicitly explaining you this particular case, but giving you enough knowledge to understand them implicitly. So I advice you to read one (and read it more carefully).
About the <T as AsRef<U>>, it is definitely covered in the books. It's needed to disambiguate calls to functions. For example, if some type implements two different traits, but both traits have the same method as_ref, then you'll need to fully qualify function of which trait you mean to call, for this you <T as AsRef<U>> and it's immediately clear which as_ref function you call.
Lets say you have 2 traits that share an item and a type that implements both. You can use this syntax to specify which implementation to use.
pub trait Foo {
const BAZ: usize;
}
pub trait Bar {
const BAZ: usize;
}
pub fn thing<T: Foo + Bar>() {
// This wont compile since it doesn't know which BAZ to use
println!("Unknown: {}", T::BAZ);
// You can use <a as b>::c to further specify which to use
println!("As Foo: {}", <T as Foo>::BAZ);
println!("As Bar: {}", <T as Bar>::BAZ);
}
You can read <A as B>::C as follows where C can be anything inside a trait (constant, function, type, etc.). It can also be used to make your code more verbose. If it is unclear which trait is being used, this can help make your code more explicit.
"Interpret type A as an instance of trait B for the purpose of retrieving C"

Why does returning `Self` in trait work, but returning `Option<Self>` requires `Sized`?

This trait definition compiles fine:
trait Works {
fn foo() -> Self;
}
This, however, does lead to an error:
trait Errors {
fn foo() -> Option<Self>;
}
error[E0277]: the size for values of type `Self` cannot be known at compilation time
--> src/lib.rs:6:5
|
6 | fn foo() -> Option<Self>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `Self`
= note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= help: consider adding a `where Self: std::marker::Sized` bound
= note: required by `std::option::Option`
With the : Sized supertrait bound, it works.
I know that the Self type in traits is not automatically bound to be Sized. And I understand that Option<Self> cannot be returned (via the stack) unless it is sized (which, in turn, requires Self to be sized). However, the same would go for Self as return type, right? It also cannot be stored on the stack unless it's sized.
Why doesn't the first trait definition already trigger that error?
(This question is related, but it doesn't answer my exact question – unless I didn't understand it.)
There are two sets of checks happening here, which is why the difference appears confusing.
Each type in the function signature is checked for validity. Option inherently requires T: Sized. A return type that doesn't require Sized is fine:
trait Works {
fn foo() -> Box<Self>;
}
The existing answer covers this well.
Any function with a body also checks that all of the parameters are Sized. Trait functions without a body do not have this check applied.
Why is this useful? Allowing unsized types to be used in trait methods is a key part of allowing by-value trait objects, a very useful feature. For example, FnOnce does not require that Self be Sized:
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
fn call_it(f: Box<dyn FnOnce() -> i32>) -> i32 {
f()
}
fn main() {
println!("{}", call_it(Box::new(|| 42)));
}
A big thanks to pnkfelix and nikomatsakis for answering my questions on this topic.
It's in the error message:
= note: required by `std::option::Option`
Option requires the type to be Sized because it allocates on the stack. All type parameters to a concrete type definition are bound to Sized by default. Some types choose to opt out with a ?Sized bound but Option does not.
Why doesn't the first trait definition already trigger that error?
I think this is a conscious design decision, due to history, future-proofing and ergonomics.
First of all, Self is not assumed to be Sized in a trait definition because people are going to forget to write where Self: ?Sized, and those traits would be less useful. Letting traits be as flexible as possible by default is a sensible design philosophy; push errors to impls or make developers explicitly add constraints where they are needed.
With that in mind, imagine that trait definitions did not permit unsized types to be returned by a method. Every trait method that returns Self would have to also specify where Self: Sized. Apart from being a lot of visual noise, this would be bad for future language development: if unsized types are allowed to be returned in the future (e.g. to be used with placement-new), then all of these existing traits would be overly constrained.
The issue with Option is just the tip of the iceberg and the others have already explained that bit; I'd like to elaborate on your question in the comment:
is there a way I can implement fn foo() -> Self with Self not being
Sized? Because if there is no way to do that, I don't see the point in
allowing to return Self without a Sized bound.
That method indeed makes it impossible (at least currently) to utilize the trait as a trait object due to 2 issues:
Method has no receiver:
Methods that do not take a self parameter can't be called since there
won't be a way to get a pointer to the method table for them.
Method references the Self type in its arguments or return type:
This renders the trait not object-safe, i.e. it is impossible to create a trait object from it.
You are still perfectly able to use it for other things, though:
trait Works {
fn foo() -> Self;
}
#[derive(PartialEq, Debug)]
struct Foo;
impl Works for Foo {
fn foo() -> Self {
Foo
}
}
fn main() {
assert_eq!(Foo::foo(), Foo);
}

Is it possible to get back the implementing type from a trait? [duplicate]

I have a collection of Trait, a function that iterates over it and does something, and then I would like to check the implementor type and if it is of type Foo then downcast it and call some Foo method.
Basically, something similar to Go's type-switch and interface conversion.
Searching around I found about the Any trait but it can only be implemented on 'static types.
To help demonstrate what I want:
let vec: Vec<Box<Trait>> = //
for e in vec.iter() {
e.trait_method();
// if typeof e == Foo {
// let f = e as Foo;
// f.foo_method();
//}
}
As you have noticed, downcasting only works with Any trait, and yes, it only supports 'static data. You can find a recent discussion on why it is so here. Basically, implementing reflection for references of arbitrary lifetimes is difficult.
It is also impossible (as of now, at least) to combine Any with your custom trait easily. However, a macro library for automatic implementation of Any for your trait has recently been created. You can also find some discussion on it here.
This isn't a Rust-specific problem, although the vocabulary may be a little different. The ideal way to solve a problem like this, not just with traits in Rust but in any language, is to add the desired behavior (foo_method in your example) to the abstract interface (Trait):
trait Trait {
fn trait_method(&self);
fn foo_method(&self) {} // does nothing by default
}
struct Foo;
impl Trait for Foo {
fn trait_method(&self) {
println!("In trait_method of Foo");
}
fn foo_method(&self) {
// override default behavior
println!("In foo_method");
}
}
struct Bar;
impl Trait for Bar {
fn trait_method(&self) {
println!("In trait_method of Bar");
}
}
fn main() {
let vec: Vec<Box<dyn Trait>> = vec![Box::new(Foo), Box::new(Bar)];
for e in &vec {
e.trait_method();
e.foo_method();
}
}
In this example, I have put a default implementation of foo_method in Trait which does nothing, so that you don't have to define it in every impl but only the one(s) where it applies. You should really attempt to make the above work before you resort to downcasting to a concrete type, which has serious drawbacks that all but erase the advantages of having trait objects in the first place.
That said, there are cases where downcasting may be necessary, and Rust does support it -- although the interface is a little clunky. You can downcast &Trait to &Foo by adding an intermediate upcast to &Any:
use std::any::Any;
trait Trait {
fn as_any(&self) -> &dyn Any;
}
struct Foo;
impl Trait for Foo {
fn as_any(&self) -> &dyn Any {
self
}
}
fn downcast<T: Trait + 'static>(this: &dyn Trait) -> Option<&T> {
this.as_any().downcast_ref()
}
as_any has to be a method in Trait because it needs access to the concrete type. Now you can attempt to call Foo methods on a Trait trait object like this (complete playground example):
if let Some(r) = downcast::<Foo>(&**e) {
r.foo_method();
}
To make this work, you have to specify what type you expect (::<Foo>) and use if let to handle what happens when the referenced object is not an instance of Foo. You can't downcast a trait object to a concrete type unless you know exactly what concrete type it is.
If you ever need to know the concrete type, trait objects are almost useless anyway! You probably should use an enum instead, so that you will get compile-time errors if you omit to handle a variant somewhere. Furthermore, you can't use Any with non-'static structs, so if any Foo might need to contain a reference, this design is a dead end. The best solution, if you can do it, is to add foo_method to the trait itself.

Is it possible to cast a trait to the implementing struct? [duplicate]

I have a collection of Trait, a function that iterates over it and does something, and then I would like to check the implementor type and if it is of type Foo then downcast it and call some Foo method.
Basically, something similar to Go's type-switch and interface conversion.
Searching around I found about the Any trait but it can only be implemented on 'static types.
To help demonstrate what I want:
let vec: Vec<Box<Trait>> = //
for e in vec.iter() {
e.trait_method();
// if typeof e == Foo {
// let f = e as Foo;
// f.foo_method();
//}
}
As you have noticed, downcasting only works with Any trait, and yes, it only supports 'static data. You can find a recent discussion on why it is so here. Basically, implementing reflection for references of arbitrary lifetimes is difficult.
It is also impossible (as of now, at least) to combine Any with your custom trait easily. However, a macro library for automatic implementation of Any for your trait has recently been created. You can also find some discussion on it here.
This isn't a Rust-specific problem, although the vocabulary may be a little different. The ideal way to solve a problem like this, not just with traits in Rust but in any language, is to add the desired behavior (foo_method in your example) to the abstract interface (Trait):
trait Trait {
fn trait_method(&self);
fn foo_method(&self) {} // does nothing by default
}
struct Foo;
impl Trait for Foo {
fn trait_method(&self) {
println!("In trait_method of Foo");
}
fn foo_method(&self) {
// override default behavior
println!("In foo_method");
}
}
struct Bar;
impl Trait for Bar {
fn trait_method(&self) {
println!("In trait_method of Bar");
}
}
fn main() {
let vec: Vec<Box<dyn Trait>> = vec![Box::new(Foo), Box::new(Bar)];
for e in &vec {
e.trait_method();
e.foo_method();
}
}
In this example, I have put a default implementation of foo_method in Trait which does nothing, so that you don't have to define it in every impl but only the one(s) where it applies. You should really attempt to make the above work before you resort to downcasting to a concrete type, which has serious drawbacks that all but erase the advantages of having trait objects in the first place.
That said, there are cases where downcasting may be necessary, and Rust does support it -- although the interface is a little clunky. You can downcast &Trait to &Foo by adding an intermediate upcast to &Any:
use std::any::Any;
trait Trait {
fn as_any(&self) -> &dyn Any;
}
struct Foo;
impl Trait for Foo {
fn as_any(&self) -> &dyn Any {
self
}
}
fn downcast<T: Trait + 'static>(this: &dyn Trait) -> Option<&T> {
this.as_any().downcast_ref()
}
as_any has to be a method in Trait because it needs access to the concrete type. Now you can attempt to call Foo methods on a Trait trait object like this (complete playground example):
if let Some(r) = downcast::<Foo>(&**e) {
r.foo_method();
}
To make this work, you have to specify what type you expect (::<Foo>) and use if let to handle what happens when the referenced object is not an instance of Foo. You can't downcast a trait object to a concrete type unless you know exactly what concrete type it is.
If you ever need to know the concrete type, trait objects are almost useless anyway! You probably should use an enum instead, so that you will get compile-time errors if you omit to handle a variant somewhere. Furthermore, you can't use Any with non-'static structs, so if any Foo might need to contain a reference, this design is a dead end. The best solution, if you can do it, is to add foo_method to the trait itself.

Resources