Storing list of `IsA<gtk::Widget>`: trait cannot be made into an object

Hi there,

I am writing a generic WidgetPager struct which needs to be able to show different widgets of different kinds. First I just used [gtk::Widget; N], the problem comes when gtk::Button != gtk::Widget. Then I thought of using IsA<gtk::Widget> since that's what the library uses internally.

It seems the trait IsA<> is using Self somewhere, or something inside it like ToGlibPtr, which Rust doesn't accept to use safely.

Log:

error[E0038]: the trait `gtk4::prelude::IsA` cannot be made into an object
   --> src/widgets/widget_pager.rs:5:17
    |
5   |     pages: [&'a IsA<gtk::Widget>; N],
    |                 ^^^^^^^^^^^^^^^^ `gtk4::prelude::IsA` cannot be made into an object
    |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
   --> /home/aitor/.cargo/registry/src/index.crates.io-6f17d22bba15001f/glib-0.20.0/src/object.rs:28:7
    |
28  |     + PartialEq
    |       ^^^^^^^^^ the trait cannot be made into an object because it uses `Self` as a type parameter
29  |     + Eq
30  |     + PartialOrd
    |       ^^^^^^^^^^ the trait cannot be made into an object because it uses `Self` as a type parameter
...
37  |     + for<'a> ToGlibPtr<'a, *mut <Self as ObjectType>::GlibType>
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait cannot be made into an object because it uses `Self` as a type parameter
38  |     + IntoGlibPtr<*mut <Self as ObjectType>::GlibType>
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait cannot be made into an object because it uses `Self` as a type parameter
    |
   ::: /home/aitor/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cmp.rs:329:15
    |
329 | pub trait Eq: PartialEq<Self> {
    |               ^^^^^^^^^^^^^^^ the trait cannot be made into an object because it uses `Self` as a type parameter
...
822 | pub trait Ord: Eq + PartialOrd<Self> {
    |                     ^^^^^^^^^^^^^^^^ the trait cannot be made into an object because it uses `Self` as a type parameter

error[E0308]: mismatched types
  --> src/widgets/widget_pager.rs:39:13
   |
14 |     pub fn new(_pages: [&'a impl IsA<gtk::Widget>; N], active_page: Option<...
   |                             --------------------- found this type parameter
...
39 |             container_selected_page,
   |             ^^^^^^^^^^^^^^^^^^^^^^^ expected `&Widget`, found `&impl IsA<gtk::Widget>`
   |
   = note: expected reference `&'a gtk4::Widget`
              found reference `&impl IsA<gtk::Widget>`

error[E0782]: trait objects must include the `dyn` keyword
 --> src/widgets/widget_pager.rs:5:17
  |
5 |     pages: [&'a IsA<gtk::Widget>; N],
  |                 ^^^^^^^^^^^^^^^^
  |
help: add `dyn` keyword before this trait
  |
5 |     pages: [&'a dyn IsA<gtk::Widget>; N],
  |                 +++

You don't even have to suss out where Self is used: IsA<T> has Into<T> as a supertrait, which has Sized as a supertrait, so unsized types like dyn _ can't implement IsA<T>.

You'll need a different approach. Perhaps using glib's object heirarchy instead of Rust's type erasure. I'm can't help with that, but perhaps someone else with more gtk-rs experience can.

1 Like

an array of Widget is the correct way to do it, just like how you do it in C. it's just when you put a Button into the container, you need to explicit upcast to Widget first.

rust doesn't have OO-style subtyping (a.k.a. inheritance), only lifetime subtyping, and Button and Widget are just structs (so coersion doesn't apply), so in the rust type system, you cannot assign a value of Button to a variable of WIdget.

gtk-rs emulates the OOP inheritance relations via the IsA<Class> trait, and the way to utilize the relationship is to explicitly cast via the Cast trait.

upcast() is checked at compile time only, suitable to cast a "derived class" (like Button) into a "base class" (like Widget).

on the other hand, downcast() and dynamic_cast() must be checked at runtime.

3 Likes

So, if I understand well, unsafe trait IsA<T> is not necessary to append a gtk::Button to [gtk::Widget; N]. Instead, you can upcast the button to "become" a Widget. But how do you do this? As far as I know, Rust doesn't support upcasting by default (or any kind of casting, that I could find about). Do I need this upcast cargo? But this is for dyn traits, so IsA<T>, not gtk::Widget.

Note: IsA shouldn't be used anyway as it's unsafe (see this issue). The maintainers of gtk-rs recommend AsRef<>, but then how's it supposed to be used?

"cast" in the context of gtk-rs (actually, glib) refers to specifically the Cast trait, which is basically the OOP-style casts, but explicitly:

actually, upcast() and downcast() requires IsA<Class>, which is to ensure the cast is between a "base" class and a "derived" class. on the other hand, dynamic_cast() doesn't require IsA<Class>. see the documentation for details.

a trait is unsafe doesn't mean it is deprecated or what. it is marked as unsafe just to fix soundness issue. in rust term, if some code can cause UB in "safe" code, the code is said to be unsound.

AsRef is used as a super trait for IsA, so it is guarenteed to be implemented for a type if you know IsA is implemented for the corresponding class. but AsRef is a standard rust trait, it has nothing to do with the OOP inheritance hierarchy, it is mainly meant to be used in static typed rust code to make it more idiomatic.

although in this example, you could use an array of Box<dyn AsRef<Widget>, it has no benefit over using Widget directly, and it is less efficient due to extra indirections.

3 Likes

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.