::nougat nougat logo

Use (lifetime-)GATs on stable rust.

Repository Latest version Documentation MSRV unsafe forbidden License CI

Example

```rust

![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: 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 windowsiter = WindowsMut::<_, 2> { slice, start: 0 }; while let Some(item) = windowsiter.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:

  1. enabling the debug-macros Cargo feature of this dependency:

    ```toml [dependencies]

    …

    nougat.version = "…" nougat.features = ["debug-macros"] # <- ADD THIS ```

  2. Setting the DEBUG_MACROS_LOCATION env var to some absolute path where the macros will write the so-generated files.

Demo

demo

How does the macro work?

Click here to see an explanation of the implementation

Some historical context

  1. 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 : 'next GAT-bounds.

  2. 2021/03/08: I officially mention the workaround for "late/for-quantifying where T : 'lt" clauses thanks implicit bounds on types such as &'lt T.

Click to see even more context

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