Generalizing iteraton method in Rust - rust

Suppose I have some custom collection of Foos:
struct Bar {}
struct Foo {
bar: Bar
}
struct SubList {
contents: Vec<Foo>,
}
and suppose I also have a SuperList which is a custom collection of SubLists:
struct SuperList {
contents: Vec<SubList>,
}
SubList and SuperList each provide a method bars:
impl SubList {
fn bars(&self) -> impl Iterator<Item = &Bar> + '_ {
self.contents.iter().map(|x| &x.bar)
}
}
impl SuperList {
fn bars(&self) -> impl Iterator<Item = &Bar> + '_ {
self.contents.iter().flat_map(|x| x.items())
}
}
I want to define a trait that provides a method items, and implement that trait on SubList and SuperList so that SubList::items is equivalent to SubList::bars and SuperList::items is equivalent to SuperList::bars, so that I can do this:
fn do_it<T: Buz<Bar>>(buz: &T) {
for item in buz.items() {
println!("yay!")
}
}
fn main() {
let foos = vec![Foo{ bar: Bar{} }];
let sublist = SubList{ contents: foos };
do_it(&sublist);
let superlist = SuperList{ contents: vec![sublist] };
do_it(&superlist);
}
I can do what I want with dynamic dispatch:
trait Buz<T> {
fn items(&self) -> Box<dyn Iterator<Item = &T> + '_>;
}
impl Buz<Bar> for SubList {
fn items(&self) -> Box<dyn Iterator<Item = &Bar> + '_> {
SubList::bars(self)
}
}
impl Buz<Bar> for SuperList {
fn items(&self) -> Box<dyn Iterator<Item = &Bar> + '_> {
SuperList::bars(self)
}
}
However, the following doesn't work:
trait Baz<T> {
fn items(&self) -> impl Iterator<Item = &T> + '_;
}
impl Baz<Bar> for SubList {
fn items(&self) -> impl Iterator<Item = &Bar> + '_ {
SubList::bars(self)
}
}
impl Baz<Bar> for SuperList {
fn items(&self) -> impl Iterator<Item = &Bar> + '_ {
SuperList::bars(self)
}
}
(error[E0562]: `impl Trait` not allowed outside of function and inherent method return types)
Here's a playground link to what I've tried so far
How can I define a trait Baz which provides an items method to abstract over the bars methods of SubList and SuperList without using dynamic dispatch?

Unfortunately, what you are trying to do is not really possible in Rust right now. Not by design, but simply because some relevant type level features are not implemented or stabilized yet.
Unless you have spare time, an interest in type level stuff and are willing to use nightly: just use boxed iterators. They are simple, they just work, and in most cases it will likely not even hurt performance in a meaningful way.
You're still reading? Ok let's talk about it.
As you intuitively tried, impl Trait in return type position would be the obvious solution here. But as you noticed, it doesn't work: error[E0562]: `impl Trait` not allowed outside of function and inherent method return types. Why is that? RFC 1522 says:
Initial limitations:
impl Trait may only be written within the return type of a freestanding or inherent-impl function, not in trait definitions [...] Eventually, we will want to allow the feature to be used within traits [...]
These initial limitations were put in place because the type level machinery to make this work was/is not in place yet:
One important usecase of abstract return types is to use them in trait methods.
However, there is an issue with this, namely that in combinations with generic trait methods, they are effectively equivalent to higher kinded types. Which is an issue because Rust's HKT story is not yet figured out, so any "accidental implementation" might cause unintended fallout.
The following explanation in the RFC is also worth reading.
That said, some uses of impl Trait in traits can be achieved already today: with associated types! Consider this:
trait Foo {
type Bar: Clone;
fn bar() -> Self::Bar;
}
struct A;
struct B;
impl Foo for A {
type Bar = u32;
fn bar() -> Self::Bar { 0 }
}
impl Foo for B {
type Bar = String;
fn bar() -> Self::Bar { "hello".into() }
}
This works and is "basically equivalent" to:
trait Foo {
fn bar() -> impl Clone;
}
Each impl block can choose a different return type as long as it implements a trait. So why then does impl Trait not simply desugar to an associated type? Well, let's try with your example:
trait Baz<T> {
type Iter: Iterator<Item = &Bar>;
fn items(&self) -> Self::Iter;
}
We get a missing lifetime specifier error:
4 | type Iter: Iterator<Item = &Bar>;
| ^ expected named lifetime parameter
Trying to add a lifetime parameter... we notice that we can't do that. What we need is to use the lifetime of &self here. But we can't get it at that point (in the associated type definition). This limitation is very unfortunate and is encountered in many situations (search term "streaming iterator"). The solution here are GATs: generic associated types. They allow us to write this:
trait Baz<T> {
// vvvv That's what GATs allow
type Iter<'s>: Iterator<Item = &'s Bar>;
fn items(&self) -> Self::Iter<'_>;
}
GATs are not fully implemented and certainly not stable yet. See the tracking issue.
But even with GATs, we cannot fully make your example work. That's because you use iterator types that are unnameable, due to using closures. So the impl Baz for ... blocks would not be able to provide the type Iter<'s> definition. Here we can use another feature that is not stable yet: impl Trait in type aliases!
impl Baz<Bar> for SubList {
type Iter<'s> = impl Iterator<Item = &'s Bar>;
fn items(&self) -> Self::Iter<'_> {
SubList::bars(self)
}
}
This actually works! (Again, on nightly, with these unstable features.) You can see your full, working example in this playground.
This type system work has been going on for a long time and it seems like it's slowly reaching a state of being usable. (Which I am very happy about ^_^). I expect that a few of the foundational features, or at least subsets of them, will be stabilized in the not-too-distant future. And once these are used in practice and we see how they work, more convenience features (like impl Trait in traits) will be reconsidered and stabilized. Or so I think.
Also worth noting that async fns in traits are also blocked by this, since they basically desugar into a method returning impl Future<Output = ...>. And those are also a highly requested feature.
In summary: these limitations have been a pain point for quite some time and they resurface in different practical situations (async, streaming iterator, your example, ...). I'm not involved in compiler development or language team discussions, but I kept an eye on this topic for a long time and I think we are getting close to finally resolving a lot of these issues. Certainly not in the next releases, but I see a decent chance we get some of this in the next year or two.

Related

`impl Trait` return type causes wrong lifetime elision

The impl Trait syntax for return types seems to cause the compiler to incorrectly assume that the lifetime of the input argument must match the output in some situations. Consider the function
fn take_by_trait<T: InTrait>(_: T) -> impl OutTrait {}
If the input type contains a lifetime the compiler complains when the output outlives the it even though they are completely independent. This will not happen if the input type is not generic, or if the output is a Box<dyn OutTrait>.
Full code:
trait InTrait {}
struct InStruct<'a> {
_x: &'a bool, // Just a field with some reference
}
impl<'a> InTrait for InStruct<'a> {}
trait OutTrait {}
impl OutTrait for () {}
fn take_by_type(_: InStruct) -> impl OutTrait {}
fn take_by_trait<T: InTrait>(_: T) -> impl OutTrait {}
fn take_by_trait_output_dyn<T: InTrait>(_: T) -> Box<dyn OutTrait> {
Box::new(())
}
fn main() {
let _ = {
let x = true;
take_by_trait(InStruct{ _x: &x }) // DOES NOT WORK
// take_by_type(InStruct { _x: &x }) // WORKS
// take_by_trait_output_dyn(InStruct { _x: &x }) // WORKS
};
}
Is there some elided lifetime here that I could qualify to make this work, or do I need to do heap allocation?
The impl Trait semantic means that the function returns some type that implements the Trait, but the caller cannot make any assumptions about which type that would be or what lifetimes it would use.
For all the compiler knows the take_by_trait function can be used in many different modules, or, probably, in other crates. Now, your implementation would work fine in all use cases. It can be rewritten like
fn take_by_trait<T: InTrait>(_: T) -> () {}
This is a perfectly fine function and will work just fine. But then at some point you might want to add another implementation for the OutTrait and change your take_by_trait function a little.
trait OutTrait { fn use_me(&self) {} }
impl<T: InTrait> OutTrait for T {}
fn take_by_trait<T: InTrait>(v: T) -> impl OutTrait {v}
If we expand the generic parameter and impl definition, we get this code:
fn take_by_trait<'a>(v: InStruct<'a>) -> InStruct<'a> {v}
fn main() {
let v = {
let x = true;
take_by_trait(InStruct{ _x: &x })
};
v.use_me();
}
This obviously cannot work because x is dropped before println! tries to access its value. So by adding a new implementation for the OutTrait you broke code that uses this function, potentially somewhere in a crate that depends on yours. That's why the compiler is reluctant to allow you defining such things.
So, again, the issue with impl OutTrait is just that the compiler cannot make any assumptions about the returned type and its lifetime, so it uses the maximum possible bound, which produces the borrow checker error you see.
EDIT: I've modified the code a little so that the signature of the function would not change and the code actually compiles and produces the same lifetime error: playground
impl Trait in return position implicitly captures the any lifetime appearing in generic parameters. This is not something you can express in normal Rust, only with impl Trait.
On stable as far as I know there is no way to avoid that. On nightly you can use type_alias_impl_trait:
#![feature(type_alias_impl_trait)]
type Ret = impl OutTrait;
fn take_by_trait<T: InTrait>(v: T) -> Ret {}
See issues Unclear compiler error when impl Trait return value captures non-'static argument (#82171), impl Trait capturing lifetime of type parameter (#79415), False-positive "temporary dropped while borrowed" involving return-position impl Trait (#98997), impl Trait + 'static is not static if returned from generic function (#76882).

Understanding "the trait X cannot be made into an object" for `&mut Box<Self>` parameter

I've got this code snippet (playground):
struct TeddyBear {
fluffiness: u8,
}
trait Scruffy {
fn scruff_up(self: &mut Box<Self>) -> Box<dyn Scruffy>;
}
impl Scruffy for TeddyBear {
fn scruff_up(self: &mut Box<Self>) -> Box<dyn Scruffy> {
// do something about the TeddyBear's fluffiness
}
}
It doesn't compile. The error is:
the trait Scruffy cannot be made into an object
, along with the hint:
because method scruff_up's self parameter cannot be dispatched on.
I checked the "E0038" error description, but I haven't been able to figure out which category my error falls into.
I also read the "object-safety" entry in "The Rust Reference", and I believe this matches the "All associated functions must either be dispatchable from a trait object", but I'm not sure, partly because I'm not sure what "receiver" means in that context.
Can you please clarify for me what's the problem with this code and why it doesn't work?
The problem is when you pass it in as a reference, because the inner type may not be well-sized (e.g. a trait object, like if you passed in a Box<Fluffy>) the compiler doesn't have enough information to figure out how to call methods on it. If you restrict it to sized objects (like your TeddyBear) it should compile
trait Scruffy {
fn scruff_up(self: &mut Box<Self>) -> Box<dyn Scruffy> where Self: Sized;
}
A receiver is the self (&self, &mut self, self: &mut Box<Self> and so on).
Note that the list you cited from the reference lists both Box<Self> and &mut Self, but does not list &mut Box<Self> nor it says that combinations of these types are allowed.
This is, indeed, forbidden. As for the why, it is a little more complex.
In order for a type to be a valid receiver, it needs to hold the following condition:
Given any type Self that implements Trait and the receiver type Receiver, the receiver type should implement DispatchFromDyn<dyn Trait> for itself with all Self occurrences of Self replaced with dyn Trait.
For instance:
&self (has the type &Self) has to implement DispatchFromDyn<&dyn Trait>, which it does.
Box<Self> has to implement DispatchFromDyn<Box<dyn Trait>>, which it does.
But in order for &mut Box<Self> to be an object-safe receiver, it would need to impl DispatchFromDyn<&mut Box<dyn Trait>>. What you want is kind of blanket implementation DispatchFromDyn<&mut T> for &mut U where U: DispatchFromDyn<T>.
This impl will never exist. Because it is unsound (even ignoring coherence problems).
As explained in the code in rustc that calculates this:
The only case where the receiver is not dispatchable, but is still a valid receiver type (just not object-safe), is when there is more than one level of pointer indirection. E.g., self: &&Self, self: &Rc<Self>, self: Box<Box<Self>>. In these cases, there is no way, or at least no inexpensive way, to coerce the receiver from the version where Self = dyn Trait to the version where Self = T, where T is the unknown erased type contained by the trait object, because the object that needs to be coerced is behind a pointer.
The problem is inherent to how Rust handles dyn Trait.
dyn Trait is a fat pointer: it is actually two words sized. One is a pointer to the data, and the other is a pointer to the vtable.
When you call a method on dyn Trait, the compiler looks up in the vtable, find the method for the concrete type (which is unknown at compilation time, but known at runtime), and calls it.
This all may be very abstract without an example:
trait Trait {
fn foo(&self);
}
impl Trait for () {
fn foo(&self) {}
}
fn call_foo(v: &dyn Trait) {
v.foo();
}
fn create_dyn_trait(v: &impl Trait) {
let v: &dyn Trait = v;
call_foo(v);
}
The compiler generates code like:
trait Trait {
fn foo(&self);
}
impl Trait for () {
fn foo(&self) {}
}
struct TraitVTable {
foo: fn(*const ()),
}
static TRAIT_FOR_UNIT_VTABLE: TraitVTable = TraitVTable {
foo: unsafe { std::mem::transmute(<() as Trait>::foo) },
};
type DynTraitRef = (*const (), &'static TraitVTable);
impl Trait for dyn Trait {
fn foo(self: DynTraitRef) {
(self.1.foo)(self.0)
}
}
fn call_foo(v: DynTraitRef) {
v.foo();
}
fn create_dyn_trait(v: &impl Trait) {
let v: DynTraitRef = (v as *const (), &TRAIT_FOR_UNIT_VTABLE);
call_foo(v);
}
Now suppose that the pointer to the value is behind an indirection. I'll use Box<&self> because it's simple but demonstrates the concept best, but the concept applies to &mut Box<Self> too: they have the same layout. How will we write foo() for impl Trait for dyn Trait?
trait Trait {
fn foo(self: Box<&Self>);
}
impl Trait for () {
fn foo(self: Box<&Self>) {}
}
struct TraitVTable {
foo: fn(Box<*const ()>),
}
static TRAIT_FOR_UNIT_VTABLE: TraitVTable = TraitVTable {
foo: unsafe { std::mem::transmute(<() as Trait>::foo) },
};
type DynTraitRef = (*const (), &'static TraitVTable);
impl Trait for dyn Trait {
fn foo(self: Box<DynTraitRef>) {
let concrete_foo: fn(Box<*const ()>) = self.1.foo;
let data: *const () = self.0;
concrete_foo(data) // We need to wrap `data` in `Box`! Error.
}
}
You may think "then the compiler should just insert a call to Box::new()!" But besides Box not being the only one here (what with Rc, for example?) and we will need some trait to abstract over this behavior, Rust never performs any hard work implicitly. This is a design choice, and an important one (as opposed to e.g. C++, where an innocent-looking statement like auto v1 = v; can allocate and copy 10GB by a copy constructor). Converting a type to dyn Trait and back is done implicitly: the first one by a coercion, the second one when you call a method of the trait. Thus, the only thing that Rust does for that is attaching a VTable pointer in the first case, or discarding it in the second case. Even allowing only references (&&&Self, no need to call a method, just take the address of a temporary) exceeds that. And it can have severe implications in unexpected places, e.g. register allocation.
So, what to do? You can take &mut self or self: Box<Self>. Which one to choose depends on whether you need ownership (use Box) or not (use a reference). And anyway, &mut Box<Self> is not so useful (its only advantage over &mut T is that you can replace the box and not just its contents, but when you do that that's usually a mistake).

How to return an anonymous type from a trait method without using Box?

I have an extension trait whose methods are just shorthands for adapters/combinators:
fn foo(self) -> ... { self.map(|i| i * 2).foo().bar() }
The return type of Trait::foo() is some nested Map<Foo<Bar<Filter..., including closures, and is therefor anonymous for all practical purposes. My problem is how to return such a type from a trait method, preferably without using Box.
impl Trait in return position would be the way to go, yet this feature is not implemented for trait methods yet.
Returning a Box<Trait>is possible, yet I don't want to allocate for every adapter shorthanded by the trait.
I can't put the anonymous type into a struct and return that, because struct Foo<T> { inner: T } can't be implemented (I promise an impl for all T, yet only return a specific Foo<Map<Filter<Bar...).
Existential types would probably solve the above problem, yet they won't be implemented for some time.
I could also just avoid the problem and use a macro or a freestanding function; this also feels unhygienic, though.
Any more insights?
What is the correct way to return an Iterator (or any other trait)? covers all the present solutions. The one you haven't used is to replace closures with function pointers and then use a type alias (optionally wrapping in a newtype). This isn't always possible, but since you didn't provide a MCVE of your code, we can't tell if this will work for you or not:
use std::iter;
type Thing<T> = iter::Map<iter::Filter<T, fn(&i32) -> bool>, fn(i32) -> i32>;
trait IterExt: Iterator<Item = i32> {
fn thing(self) -> Thing<Self>
where
Self: Sized + 'static,
{
// self.filter(|&v| v > 10).map(|v| v * 2)
fn a(v: &i32) -> bool { *v > 10 }
fn b(v: i32) -> i32 { v * 2 }
self.filter(a as fn(&i32) -> bool).map(b as fn(i32) -> i32)
}
}
impl<I> IterExt for I
where
I: Iterator<Item = i32>,
{}
fn main() {}
Honestly, in these cases I would create a newtype wrapping the boxed trait object. That way, I have the flexibility to internally re-implement it with a non-boxed option in an API-compatible fashion when it becomes practical to do so.

Get generic trait from ordinal one without Self type?

I have two traits, one ordinal (Foo), another generic (TypedFoo<T>). I have several structures, each of them have both traits implemented.
Is it possible to convert from Foo to TypedFoo<T>, without converting to an intermediate structure?
trait Foo {
fn f(&mut self);
}
trait TypedFoo<T> {
fn get(&self) -> T;
}
#[derive(Clone)]
struct Data(i32);
impl Foo for Data {
fn f(&mut self) {
self.0 += 1;
}
}
impl TypedFoo<Data> for Data {
fn get(&self) -> Data {
self.clone()
}
}
//struct Data2(f64);
//impl Foo for Data2...
//impl TypedFoo<Data2> for Data2..
fn main() {
let v: Vec<Box<Foo>> = vec![Box::new(Data(1))];
}
I can change Foo to this:
trait Foo {
fn f(&mut self);
fn get_self(&self) -> &Any;
}
Then get v[0].get_self() downcast_ref to Data, and then Data to &TypedFoo<Data>.
But is it possible to get &TypedFoo<Data> from &Foo without knowing "data type", some analog of Any but for a trait.
I imagine syntax like this:
let foo: &Foo = ...;
if let Some(typed_foo) = foo.cast::<Data>() {
}
My question is different from Can I cast between two traits?
because I have one generic and one ordinal trait. If I had two ordinal traits then the solution would be as simple as:
trait Foo {
fn f(&mut self);
fn as_typed_foo(&self) -> &TypedFoo;
}
Since TypedFoo is generic, none of the answers in that question help me. One possible solution could be:
trait Foo {
fn f(&mut self);
fn cast(&mut self, type_id: ::std::any::TypeId) -> Option<*mut ::std::os::raw::c_void>;
}
I am not sure how safe it is to cast *mut TypedFoo<T> -> *mut ::std::os::raw::c_void and then back to *mut TypedFoo<T>.
The signature of the function that you want is
fn convert<T>(x: &Box<Foo>) -> &TypedFoo<T>
To type check this signature compiler must know that the type inside Box implements TypedFoo<T> for some T. But conversion into trait-object erases information about the real type. Which means that it is impossible to statically type check signature of this function.
So we need to do it dynamically and if we want to use types a current crate doesn't know about, we'll need to resort to unsafe.
One option is to limit a set of types, which can be used in TypedFoo, and provide conversion functions in the Foo trait. This allows to avoid unsafe.
Playground link
Second option is to add to trait Foo a function, which returns a slice of pairs (TypeId, *const ()). The pointer is a type erased pointer to function, which does actual conversion. Conversion function searches required type identifier and executes corresponding function.
Playground link
For the sake of demonstration I used Vec instead of slice in conversion_registrar. But it shouldn't be too hard to change return type to &'static [(TypeId, *const ())], using lazy_static crate.

Can I cast between two traits?

Is there a way to cast from one trait to another?
I have the traits Foo and Bar and a Vec<Box<dyn Foo>>. I know some of the items in the Vec implement the Bar trait, but is there any way I could target them?
I don't understand if this is possible or not.
trait Foo {
fn do_foo(&self);
}
trait Bar {
fn do_bar(&self);
}
struct SomeFoo;
impl Foo for SomeFoo {
fn do_foo(&self) {
println!("doing foo");
}
}
struct SomeFooBar;
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
}
impl Bar for SomeFooBar {
fn do_bar(&self) {
println!("doing bar");
}
}
fn main() {
let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.do_foo();
// if let Some(val) = foo.downcast_whatever::<Bar>() {
// val.bar();
// }
}
}
[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b637bddc4fc923ce705e84ad1d783d4)
No. There is no way to cast between two unrelated traits. To understand why, we have to understand how trait objects are implemented. To start with, let's look at TraitObject.
TraitObject is a reflection of how trait objects are actually implemented. They are composed of two pointers: data and vtable. The data value is just a reference to the original object:
#![feature(raw)]
use std::{mem, raw};
trait Foo {}
impl Foo for u8 {}
fn main() {
let i = 42u8;
let t = &i as &dyn Foo;
let to: raw::TraitObject = unsafe { mem::transmute(t) };
println!("{:p}", to.data);
println!("{:p}", &i);
}
vtable points to a table of function pointers. This table contains references to each implemented trait method, ordered by some compiler-internal manner.
For this hypothetical input
trait Foo {
fn one(&self);
}
impl Foo for u8 {
fn one(&self) { println!("u8!") }
}
The table is something like this pseudocode
const FOO_U8_VTABLE: _ = [impl_of_foo_u8_one];
A trait object knows a pointer to the data and a pointer to a list of methods that make up that trait. From this information, there is no way to get any other piece of data.
Well, almost no way. As you might guess, you can add a method to the vtable that returns a different trait object. In computer science, all problems can be solved by adding another layer of indirection (except too many layers of indirection).
See also:
Why doesn't Rust support trait object upcasting?
But couldn't the data part of the TraitObject be transmuted to the struct
Not safely, no. A trait object contains no information about the original type. All it has is a raw pointer containing an address in memory. You could unsafely transmute it to a &Foo or a &u8 or a &(), but neither the compiler nor the runtime data have any idea what concrete type it originally was.
The Any trait actually does this by also tracking the type ID of the original struct. If you ask for a reference to the correct type, the trait will transmute the data pointer for you.
Is there a pattern other than the one I described with my FooOrBar trait to handle such cases where we need to iterate over a bunch of trait objects but treat some of them slightly different?
If you own these traits, then you can add as_foo to the Bar trait and vice versa.
You could create an enum that holds either a Box<dyn Foo> or a Box<dyn Bar> and then pattern match.
You could move the body of bar into the body of foo for that implementation.
You could implement a third trait Quux where calling <FooStruct as Quux>::quux calls Foo::foo and calling <BarStruct as Quux>::quux calls Bar::foo followed by Bar::bar.
so... I don't think this is exactly what you want, but it's the closest I can get.
// first indirection: trait objects
let sf: Box<Foo> = Box::new(SomeFoo);
let sb: Box<Bar> = Box::new(SomeFooBar);
// second level of indirection: Box<Any> (Any in this case
// is the first Box with the trait object, so we have a Box<Box<Foo>>
let foos: Vec<Box<Any>> = vec![Box::new(sf), Box::new(sb)];
// downcasting to the trait objects
for foo in foos {
match foo.downcast::<Box<Foo>>() {
Ok(f) => f.do_foo(),
Err(other) => {
if let Ok(bar) = other.downcast::<Box<Bar>>() {
bar.do_bar();
}
}
}
}
note that we can call SomeFooBar as a Box<Bar> only because we stored it as a Box<Bar> in the first place. So this is still not what you want (SomeFooBar is a Foo too, but you can't convert it to a Box<Foo> any longer, so we're not really converting one trait to the other)
The short answer is: there is extremely limited support for downcasting at the moment in the language.
The long answer is that being able to downcast is not seen as high-priority for both technical and philosophical reasons:
from a technical stand-point, there are workarounds for most if not all situations
from a philosophical stand-point, downcasting leads to more brittle software (as you unexpectedly start relying on implementation details)
There have been multiple proposals, and I myself participated, but for now none has been selected and it is unclear whether Rust will ever get downcasting or if it does what its limitations will be.
In the mean time, you have essentially two workarounds:
Use TypeId: each type has an associated TypeId value which can be queried, then you can build a type-erased container such as Any and query whether the type it holds is a specific X. Behind the scenes Any will simply check the TypeId of this X against the TypeId of the value stored.
Create a specific trait, as you did.
The latter is more open-ended, and notably can be used with traits, whereas the former is limited to concrete types.
Here is what I did.
I added an as_bar method to the Foo trait that returns an Option<&Bar>. I gave the trait a default implementation to return None so that there is little to no inconvenience for Foo implementers that don't bother about Bar.
trait Foo {
fn do_foo(&self);
fn as_bar(&self) -> Option<&dyn Bar> {
None
}
}
I overwrite that method for the SomeFooBar struct which implements both Foo and Bar to return Some(self):
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
fn as_bar(&self) -> Option<&dyn Bar> {
Some(self)
}
}
Which makes the calling code look pretty much the way I want it to look.
fn main() {
let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.do_foo();
if let Some(bar) = foo.as_bar() {
bar.do_bar();
}
}
}
Playground
I would love to see Rust improve on this part in the future, but it's a solution I can totally live with for my case.
The only solution that I found originally is to introduce a third trait FooOrBar with explicit converter methods and implement that for both types. It doesn't feel like the right tool for the job though.
trait FooOrBar {
fn to_bar(&self) -> Option<&dyn Bar>;
fn to_foo(&self) -> Option<&dyn Foo>;
}
impl FooOrBar for SomeFooBar {
fn to_bar(&self) -> Option<&dyn Bar> {
Some(self)
}
fn to_foo(&self) -> Option<&dyn Foo> {
None
}
}
impl FooOrBar for SomeFoo {
fn to_bar(&self) -> Option<&dyn Bar> {
None
}
fn to_foo(&self) -> Option<&dyn Foo> {
Some(self)
}
}
fn main() {
let foos: Vec<Box<dyn FooOrBar>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.to_foo().map(|foo| foo.do_foo());
foo.to_bar().map(|foo| foo.do_bar());
}
}

Resources