rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.11k stars 12.69k forks source link

Trait with associated type bounds cannot be made into an object because it uses `Self` as a type parameter #80256

Open ldr709 opened 3 years ago

ldr709 commented 3 years ago

A simplified example where this shows up is the following trait.

trait Conversion {
    type Source: Into<Self::Target>;
    type Target;
}

(Full code)

If you try to make a trait object, say &dyn Conversion<Source = String, Target = Box<str>>, then Rust errors:

error[E0038]: the trait `Conversion` cannot be made into an object
  --> src/main.rs:14:12
   |
1  | trait Conversion {
   |       ---------- this trait cannot be made into an object...
2  |     type Source: Into<Self::Target>;
   |                  ------------------ ...because it uses `Self` as a type parameter in this
...
14 |     let b: &dyn Conversion<Source = String, Target = Box<str>> = &a;
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Conversion` cannot be made into an object

If the bound was something like Source: Into<Self> this error would make sense, but instead the bound references another associated type, which all must be fully specified when making a trait object type anyway.

This problem does not seem to occur when associated types are used to define functions in the trait, as creating a &dyn Deref<Target = ...> trait object works.

p-avital commented 1 year ago

Hi,

I'm currently building stabby an alternative to abi_stable (with added values: use niches in sum types and stable multi-trait objects).

My proc-macro generates v-tables (with auto-generated identifiers) and makes them accessible through a trait which roughly looks like this:

impl<Assoc1> VtProvider for dyn MyTrait<Assoc=Assoc1> {
  type Vt = AutoGeneratedVtForMyTrait<Assoc1>;
}

allowing the user to refer to AutoGeneratedVtForMyTrait<Assoc1> by vtable!(MyTrait<Assoc=Assoc1>), which resolves to <dyn MyTrait<Assoc=Assoc1> as VtProvider>::Vt.

This bug prevents this trick from working when associated types are constrained.

Is anyone willing to look into it? Or provide mentoring so that I could try? (I don't have any experience working with the compiler)

I'll trade you a stable ABI that keeps enums small in stable Rust for fixing this bug :)

Tamschi commented 3 months ago

Is this the same issue or should I file it separately?

use std::ops::Deref;
use std::borrow::Borrow;

trait Guard: Deref + Borrow<Self::Target> {}

fn foo(_: &dyn Guard<Target = i32>) {}
error[E0038]: the trait `Guard` cannot be made into an object
 --> src/main.rs:7:12
  |
7 | fn foo(_: &dyn Guard<Target = i32>) {}
  |            ^^^^^^^^^^^^^^^^^^^^^^^ `Guard` 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>
 --> src/main.rs:5:22
  |
5 | trait Guard: Deref + Borrow<Self::Target> {}
  |       -----          ^^^^^^^^^^^^^^^^^^^^ ...because it uses `Self` as a type parameter
  |       |
  |       this trait cannot be made into an object...
help: consider using an opaque type instead
  |
7 | fn foo(_: &impl Guard<Target = i32>) {}
  |            ~~~~

For more information about this error, try `rustc --explain E0038`.

(Thanks to Helix in the Community Discord for constructing the example.)

I ran into both versions of this in a project of mine, and while it's easy to work around by making the trait generic, that doesn't seem ideal with my Guard example here, as it's implicitly exclusive.

Edit: This might be #65078 instead.