Here's another approach: avoid the need for your Successors
bound.
Make a trait at the deeper levels that enforces the constraints you want:
pub trait TupleLending<'lend, __Seal = &'lend Self>
where
Self: Lending<'lend, __Seal, Lend = (usize, <Self as TupleLending<'lend, __Seal>>::TupleLend)>
{
type SingleValue;
type TupleLend: IntoIterator<Item = Self::SingleValue>;
}
<_ as TupleLending<'lend>>::TupleLend
takes the role of Successors<'lend>
, and thus we've moved that bound here. The supertrait bound enforces the type's "shape" that was part of the associated type bound between Successors
and Iterator
. SingleValue
we'll use in a moment.
Types will have to implement this either in addition to or instead of Lending
.
impl<'succ, R: std::io::Read> TupleLending<'succ> for Iterator<R> {
type SingleValue = Vec<u64>;
type TupleLend = Labels<'succ, R>;
}
Optional: Remove some boilerplate
Unfortunately a blanket implementation here seems to run into HRTB problems still, so we can't have this.
impl<'lend, S, T: ?Sized, Lend> TupleLending<'lend, S> for T
where
T: Lending<'lend, __Seal, Lend = (usize, Lend)>,
Lend: IntoIterator,
But we can go the other direction:
impl<'lend, S, T: TupleLending<'lend, S>> Lending<'lend, S> for T {
type Lend = (usize, T::TupleLend);
}
And this way implementors only need to implement TupleLending
and not Lending
.
-impl<'succ, R> Lending<'succ> for Iterator<R> {
- type Lend = (usize, Labels<'succ, R>);
-}
...but maybe that's not tenable for your use case? For example, I had to add the R: Read
constraint to the TupleLending
implementation, so it covers less types. If this is unacceptable, types will have to implement both.
Then also make and blanket-implement a higher-ranked subtrait:
pub trait TupleLender
where
Self: Lender,
Self: for<'all> TupleLending<'all, SingleValue = <Self as TupleLender>::Value>,
{
type Value;
}
impl<T: ?Sized, Value> TupleLender for T
where
T: Lender,
T: for<'all> TupleLending<'all, SingleValue = Value>
{
type Value = Value;
}
Here we use a supertrait bound to make sure all the SingleValue
s unify into the same type (Value
).
With these in place, SequentialLabelling
can look like so:
pub trait SequentialLabelling {
type Value;
type Iterator<'node>: TupleLender<Value = Self::Value>
where
Self: 'node;
}
If you need to refer to the types that Successors
used to be, that corresponds to
pub type Successors<'succ, 'node, This> =
<
<This as SequentialLabelling>::Iterator<'node>
as TupleLending<'succ>
>::TupleLend;
I suspect any attempt to get that into a GAT with enforced type equality (analogous to how Value
is "uplifted") will run into Rust's HRTB limitations for now.
You may be thinking, why can't we use this?
pub type SuccessorsOne<'succ, This> =
<
<This as SequentialLabelling>::Iterator<'succ>
as TupleLending<'succ>
>::TupleLend;
Well, you sort-of can, but don't think you really want to. Here's how I presume you're going to want to use this:
let swh = SwhLabels { builder: SomeReader };
// Get an SwhLabels::Iterator<SomeReader::Reader<'node>>
let mut iter = swh.factory();
// Get a `(usize, Labels<'succ, Reader<'node>>)`
// This requires exclusively borrowing `iter` for `'succ`
let labels = iter.next_lend();
// Do it again a few more times...
let labels = iter.next_lend();
let labels = iter.next_lend();
If you tried to use SuccessorsOne
to dial in the type here:
let labels: Option<(usize, SuccessorsOne<'_, _>)> = iter.next_lend();
Then you force 'succ = 'node
. Then because 'node
is in an invariant position (under a &mut
), you are forced to take a &'node mut Iterator<Reader<'node>>
. That makes iter
be exclusively borrowed for its entire validity, i.e. unusable afterwards. Once you call iter.next_lend()
once with the single-lifetime annotation, you can never use iter
again; further attempts to call next_lend
will fail.
(This also illustrates that a single-parameter GAT was probably not what you wanted, even if the bounds could somehow be stated. I tried to pinpoint an issue for my two-lifetime GAT attempt, but I'm not sure which issue that compiler error corresponds to.)