Expand description
§::nougat 
Use (lifetime-)GATs on stable rust.
§Example
#![forbid(unsafe_code)]
#[macro_use]
extern crate nougat;
#[gat]
trait LendingIterator {
type Item<'next>
where
Self : 'next,
;
fn next(&mut self)
-> Option<Self::Item<'_>>
;
}
struct WindowsMut<Slice, const SIZE: usize> {
slice: Slice,
start: usize,
}
#[gat]
impl<'iter, Item, const SIZE: usize>
LendingIterator
for
WindowsMut<&'iter mut [Item], SIZE>
{
type Item<'next>
where
Self : 'next,
=
&'next mut [Item; SIZE]
;
/// For reference, the signature of `.array_chunks_mut::<SIZE>()`'s
/// implementation of `Iterator::next()` would be:
/** ```rust ,ignore
fn next<'next> (
self: &'next mut AChunksMut<&'iter mut [Item], SIZE>,
) -> Option<&'iter mut [Item; SIZE]> // <- no `'next` nor "lending-ness"! ``` */
fn next<'next> (
self: &'next mut WindowsMut<&'iter mut [Item], SIZE>,
) -> Option<&'next mut [Item; SIZE]> // <- `'next` instead of `'iter`: lending!
{
let to_yield =
self.slice
.get_mut(self.start ..)?
.get_mut(.. SIZE)?
.try_into() // `&mut [Item]` -> `&mut [Item; SIZE]`
.expect("slice has the right SIZE")
;
self.start += 1;
Some(to_yield)
}
}
fn main() {
let mut array = [0, 1, 2, 3, 4];
let slice = &mut array[..];
// Cumulative sums pattern:
let mut windows_iter = WindowsMut::<_, 2> { slice, start: 0 };
while let Some(item) = windows_iter.next() {
let [fst, ref mut snd] = *item;
*snd += fst;
}
assert_eq!(
array,
[0, 1, 3, 6, 10],
);
}§Debugging / tracing the macro expansions
You can make the macros go through intermediary generated files so as to get well-spanned error messages and files which you can open and inspect yourself, with the remaining macro non-expanded for readability, by:
-
enabling the
debug-macrosCargo feature of this dependency:[dependencies] ## ⊠nougat.version = "âŠ" nougat.features = ["debug-macros"] # <- ADD THIS -
Setting the
DEBUG_MACROS_LOCATIONenv var to some absolute path where the macros will write the so-generated files.
§Demo
§How does the macro work?
Click here to see an explanation of the implementation
§Some historical context
-
2021/02/24: Experimentation with
for<'lt> Trait<'lt>as a super-trait to emulate GATs- (I suspect there may even be previous experimentations and usages over URLO; but I just canât find them at the moment)
This already got GATs almost done, but for two things, regarding which I did complain at the time đ :
-
The
Trait<'lt>embedded all the associated items, including the methods, and not just the associated âgenericâ type.This, in turn, could lead to problems if these other items relied on the associated type being fully generic, as I observe here, on the 2021/03/06.
-
I was unable to express the
where Self : 'nextGAT-bounds.
Click to see even more context
-
I didnât come out with this idea by myself; itâs a bit fuzzy but I recall URLO user
steffahnworking a lot with similar shenanigans (e.g., this 2021/04/26 issue), and I clearly rememberKestrerover the community Discord pointing out the implicit bound hack.- For those interested, I used this technique, later on, to work around a nasty âoverly restrictive lifetime-bound in higher-order closure contextâ issue in a very detailed URLO post that I think youâll find interesting.
So all this, around that time became âadvanced knowledgeâ shared amongst some URLO regulars (such as
steffahnandquinedot), but never really actioned from there on: the idea was to wait for the proper solution, that is, GATs. -
Nonetheless, I started pondering about the idea of this very crate, dubbed
autogaticat the time:-
a post with near identical examples to what this crate currently offers
-
Sadly the proposal was received rather coldly: GATs were very close to stabilization, so a tool to automate a workaround/polyfill that was expected to quickly become stale was not deemed useful.
So I waited. And waited. Finally the stabilization issue was opened, and⊠kind of âshut downâ (more precisely, delayed until a bunch of aspects can be sorted out, see that issue for more info). And truth be told, the arguments not to stabilize right now seem quite legitimate and well-founded, imho, even if I still hope for a mid-term stabilization of the issue.
What all that made was justify my
autogaticidea, and so I committed to writing that prototypical idea I had in mind:nougatwas born đ
-
At which point user
Jannis Harderchimed in and suggested another implementation / alternative to polyfilling GATs:-
to use the âstandard GAT workaroundâ to define a HKT trait:
trait WithLifetime<'lt> { type T; } trait HKT : for<'any> WithLifetime<'any> {} impl<T : ?Sized + for<'any> WithLifetime<'any>> HKT for T {} -
And then, to replace
type Assoc<'lt>;with:âtype Assoc : ?Sized + HKT;- and use
<Self::Assoc as WithLifetime<'lt>>::Tinstead ofSelf::Assoc<'lt>when resolving the type with a concrete lifetime.
- and use
-
So as to, on the implementor side, use:
âimpl LendingIterator for Thing { // type Item // <'next> // = &'next str // ; type Item = dyn for<'next> WithLifetime<'next, T = &'next str >; // formatted: type Item = dyn for<'next> WithLifetime<'next, T = &'next str>; }- (or use
for<âŠ> fnâŠpointers, but in practice they donât work as well asdyn for<âŠ> Traits)
- (or use
This approach has a certain number of drawbacks (implicit bounds are harder (but not impossible!) to squeeze in), and when
Assoc<'lt>has bounds of its own, a dedicatedHKTtrait featuring such bounds onTseems to be needed.That being said, this
HKT-based approach has the advantage of being the only one that is remotely capable of beingdyn-friendly(-ish), which is not the case for the âclassical workaroundâ approach.See
Sabrina Jewsonâs blog post below to see a more in-depth comparison of these two approaches. -
§The actual explanation
As I was bracing myself to spend hours detailing these tricks đ
, luckily for
me, I learned that somebody had already done all that work, with definitely
nicer prose than mine: Sabrina Jewson đ. She has written a very complete and
thorough blog post about GATs, their stable polyfills, and how they compare with
each other (funnily enough, GATs are currently worse than their polyfills
since due to a compiler bug whenever one adds a trait bound to a GAT, then the
GAT in question ends up having to be : 'static,
for no actual reason other than the compiler brain-farting on it).
Here is the link to said blog post, pointing directly at the workaround that this crate happens to be using, but feel free to remove the anchor and read the full post, itâs definitely worth it:
§đ The Better Alternative to Lifetime GATs â by Sabrina Jewson đ
§Limitations
-
Only lifetime GATs are supported (no
type Assoc<T>nortype Assoc<const âŠ>). -
The code generated by the macro is currently not
dyn-friendly at all. This will likely be improved in the future; potentially using another desugaring for the implementation. -
In order to refer to GATs outside of
#[gat]-annotated items usingGat!is needed. -
Adding trait bounds to GATs in functions breaks type inference for that function (thanks to Discord use
Globifor identifying and reporting this)
Macros§
- Gat
- Refer to a
<Type as Trait>::Assoc<âŠ>type. Or expressTrait<Assoc<âŠ> = âŠ>constraints.
Attribute Macros§
- apply
- Reëxport of
::macro_rules_attribute::apply - gat
- Entrypoint of the crate. Enables (lifetime) GATs on the annotated
traitorimplblock.
