Why must pointer trait bounds be specified redundantly?
Asked Answered
P

1

6

Consider the following situation, with two Traits. The first Trait is for pointer types, and the second Trait is for regular objects. The second trait has a HRTB so that any reference to a type that implements the second trait, must implement the first trait:

pub trait PtrTrait {}

pub trait RegTrait
where
    for<'a> &'a Self: PtrTrait,
{
}

Yes, this actually works! In the following, the compiler would complain if the first generic impl were not included:

pub struct S();

// Note, this impl IS needed for the next impl to compile.
impl<'a> PtrTrait for &'a S {}

impl RegTrait for S {}

The problem is that the compiler doesn't seem to remember about this when writing a generic function. The following function definition doesn't compile:

pub fn bar<T: RegTrait>() {}

The error from rustc simply says that the trait bound for<'a> &'a T: PtrTrait is not satisfied for the type &'a T. If I add a where clause to the function:

where for<'a> &'a T: PtrTrait

then it works - but why should that be needed if it's already a constraint on the trait itself?

playground

Prophesy answered 5/1, 2021 at 5:21 Comment(0)
U
1

Yes, this does not really feel intuitive. After a bit of research however, it seems that it is a somewhat-intentional restriction of the type checker that was reported as soon as 2015.

Indeed, the #107699 Rust issue that was opened fairly recently describes exactly the situation encountered here, only with the function being replaced by a trait instead, but that does not change anything regarding the constraints check. It is linked to the old #20671. #103387 seems to be another duplicate of it, where this comment makes a good summary of the opened issues. See also this other SO question on the matter.

Generally speaking, the situation occurs because the trait bound is not added on Self, but rather on &'a Self, and by current design only bounds on Self are propagated to use sites ("elaborated" in rustc terminology) and may be implied there. For example:

trait SuperTrait {}
trait SubTrait: SuperTrait {}
fn f<T: SubTrait>() {}

compiles just fine, but this:

trait OtherTrait {}
trait SuperTrait { type A; }
trait SubTrait: SuperTrait where Self::A: OtherTrait {}
fn f<T: SubTrait>() {}

does not, on the following error:

error[E0277]: the trait bound `<T as SuperTrait>::A: OtherTrait` is not satisfied
 --> src/lib.rs:4:9
  |
4 | fn f<T: SubTrait>() {}
  |         ^^^^^^^^ the trait `OtherTrait` is not implemented for `<T as SuperTrait>::A`
  |
note: required by a bound in `SubTrait`
 --> src/lib.rs:3:43
  |
3 | trait SubTrait: SuperTrait where Self::A: OtherTrait {}
  |                                           ^^^^^^^^^^ required by this bound in `SubTrait`
help: consider further restricting the associated type
  |
4 | fn f<T: SubTrait>() where <T as SuperTrait>::A: OtherTrait {}
  |                     ++++++++++++++++++++++++++++++++++++++

that is very similar to what is obtained when compiling the example provided. Adding the suggested where bound does also solve the error.

Also note that the situation is not HRTB-specific since simply bubbling up the lifetime in:

trait PtrTrait {}

trait RegTrait<'a>: 'a
where
    &'a Self: PtrTrait,
{}

fn bar<'a, T>()
where
    T: RegTrait<'a>,
    // &'a T: PtrTrait,
{}

also exhibits the same behavior.

Regarding a workaround, supposing a no-HRTB scenario, I think the above can be transformed to:

trait PtrTrait {}

trait Intermediary {
    type Type: PtrTrait;
}

trait RegTrait<'a>: Intermediary<Type = &'a Self> + 'a {}

fn bar<'a, T: RegTrait<'a>>() {}

and is accepted by the compiler, although it might be too convoluted to be usable in your concrete case at this point.

Unjust answered 15/9 at 1:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.