Polymorphism and Trait Object

I think I might have encountered a bug on compiler but not 100% sure, I have following setup:

trait Trait {
}

struct A {}

impl Trait for A {
}

struct D<T> {
    a: T
}

And then I found:

This work:

let a = Box::new(A {});
let b: Box<dyn Trait> = a;

This work:

let x: D<Box<dyn Trait>> = D {
    a: Box::new(A{})
};

But this somehow doesn't work: (notice it has the same setup with example 2)

let d = D {
    a: Box::new(A{})
};
let x: D<Box<dyn Trait>> = d;

I found this highly confusing and almost look like a bug to me. Can anyone please help me understand what is happening in above examples?

Thanks a lot.

These all show different behaviors, you can't coerce a type inside another type except in a few specific cases. For example, Box<T> to Box<dyn Trait>. In this example

let x: D<Box<dyn Trait>> = D {
    a: Box::new(A{}) // the coercion is happening here
};

The coercion happens before D is constructed. But here

let d = D {
    a: Box::new(A{})
};
let x: D<Box<dyn Trait>> = d;

It can only happen after D is constructed, and that's not a valid coercion.

Btw: 99% time when you are learning a new language, and you run into something you think is wrong, you are usually misunderstanding something. So don't blame the compiler. :crab:

5 Likes

Thank you Yato for helping me.

Do you know if there is any other way to achieve assigning D<A> to D<dyn Trait> ? I am making a IoC/DI library at this moment and that seems to be a fundamental blocker for me now.

(My apology on making that accusation. But that behavior does go against my entire experience of programming and I can't find good doc online explaining this behavior. )

Rust only has implicit conversions very rarely. You can define your own conversion.

impl<T: Trait + 'static> From<D<Box<T>>> for D<Box<dyn Trait>> {
    fn from(d: D<Box<T>>) -> Self {
        D {
            a: d.a,
        }
    }
}

This would allow you to convert it with .into().

let d = D {
    a: Box::new(A {})
};
let x: D<Box<dyn Trait>> = d.into();

playground

6 Likes

Note that as long as your D<T> is a struct and only a single field in D contains a T, there actually is a way to archive the implicit conversion, however only by using unstable features of the rust compiler. Like this. (Links explaining the details.) The standard library uses this, among other things, for Cell and RefCell.

4 Likes

thanks @steffahn @alice, you info really helped me going further.

But I am still a bit confused regarding how far I can push this coercion feature.

My real setup is similar to:

struct D<T> {
    a: fn() -> T,
}

And I want to achieve coercing D<A> -> D<dyn Trait>, I tried both approaches but neither achieve it.

(I started to think if I am stuck in a XY problem but at the same time I can't think of any other way achieving my goal)

Well, you can't return a dyn Trait from a function because a dyn Trait can only exist behind a pointer, so you would at most be able to convert it to fn() -> Box<dyn Trait> or similar. Additionally, this would probably require a Box<dyn Fn() -> Box<dyn Trait>> if you want to do the conversion generically because the closure required to wrap the function pointer would not itself be a function pointer.

2 Likes

OMG it works! I can't remember the last time when I was so happy about beating compiler haha. (this is a compliment to rust?)

I really appreciate everyone here! It's unbelievable how responsive people are here.

On the flip side though, I do hope there is a systematic way of organizing those knowledge. I have read so many docs and SO answers but none of them is as helpful as this thread is.

1 Like

The above should be called constructor. To be more clear:

let x: D<Box<dyn Trait>> = D<Box<dyn Trait>>{
    a: Box::new(A{}) // the coercion is happening here
};

or more clear

fn constructor(a: Box<dyn Trait>) -> D<Box<dyn Trait>> {
       D { a }
}

// coerce from Box<A> to Box<dyn Trait>
// &A -> &dyn Trait
let x: D<Box<dyn Trait>> = constructor(Box::new(A{});

You are mixing constructor with copy assign constructor, which Rust doesn't have. It's a good thing for Rust because a lot of implicit conversions can bring surprises when using operator= in C++. In rust, you need to explicitly define logic to convert. More readable, IMO, rust emphasizes on explicity way more over implicity, for example, you can't index vector with u8 but you have to convert as usize.

Box is using Deref, to convert from Box<B> to Box<A> (you can always imagine replacing Box with &, as &B to &A, coercion).

But you cannot assign struct type D<B> to D<A>, even though B can be coerced to A. The reason is that there's no copy assign logic for different struct type. (I doubt there's no = operator overload for Rust, but I'm not sure).

struct D<T> {
template<class Other>
  D& operator=(D<Other>& other) {
        a = other.a;
        return *this;
  }
};

You need to define the From logic on your own.

All in all. struct D<A> = D<B> is different with Box<A> = Box<B>, even though they look similar, the latter is using Defer the former is using copy assign or operator=, which doesn't exist.

IMO, Rust needs to bring the concept constructor and copy assign into their official doc.

Besides that, the title of your post is too big...will affect other people's search if they post similar topic.

Strictly speaking, it's unsized coercion, not deref coercion. Here's a reference of coercions the Rust supports, and what unsized coercion means.

1 Like

Thanks! I assume you mean for &T to &dyn Trait?

I remember conversion between Box<T> is through Deref? Correct me if I'm wrong.

Both ones. You can't get &dyn Trait/Box<dyn Trait> by calling .deref() on &T/Box<T>.

All in all. struct D<A> = D<B> is different with Box<A> = Box<B> , even though they look similar, the latter is using Defer the former is using copy assign or operator= , which doesn't exist.

Yep, I understand it now (roughly). I didn't understand how special Box is before this thread.

Besides that, the title of your post is too big...will affect other people's search if they post similar topic.

I totally agree, my apology. However, I don't actually know how to describe these. Is it the best to describe it as "implicit coercion"? Or any other terminology? I am open to suggestion.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.