(lifetime) GATs in stable Rust

Overview

::nougat nougat logo

Use (lifetime-)GATs on stable rust.

Repository Latest version Documentation MSRV unsafe forbidden License CI

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:

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

    [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
  • I don't think I fully came out with this idea by myself; it's a bit fuzzy but I vaguely recall URLO user steffahn working with similar shenanigans and I clearly remember somebody over the Rust discord (was it Kestrer?) pointing out the implicit bound hack.

    So all this, around that time became "advanced knowledge" shared amongst some URLO regulars (such as steffahn and quinedot), 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 autogatic at the time:

    • post summary

    • 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 autogatic idea, and so I committed to writing that prototypical idea I had in mind: nougat was born 🙂

  • At which point user Jannis Harder chimed in and suggested another implementation / alternative to polyfilling GATs:

    1. 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 {}
    2. And then, to replace type Assoc<'lt>; with:

      type Assoc : ?Sized + HKT;
      • and use <Self::Assoc as WithLifetime<'lt>>::T instead of Self::Assoc<'lt> when resolving the type with a concrete lifetime.
    3. 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 as dyn for<…> Traits)

    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 dedicated HKT trait featuring such bounds on T seems to be needed.

    That being said, this HKT-based approach has the advantage of being the only one that is remotely capable of being dyn-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> nor type 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 using Gat! is needed.

  • Adding trait bounds to GATs in functions breaks type inference for that function (thanks to Discord use Globi for identifying and reporting this)

Comments
  • ඞ character in generated type is not keyboard friendly

    ඞ character in generated type is not keyboard friendly

    Heya, I'm experimenting using this crate (it's magic!), and noticed that implementation crates need to import my_lib::TheTraitඞItem, defined here:

    https://github.com/danielhenrymantilla/nougat.rs/blob/7ddcdefd416c1538c0fa578e74e2f9eeca1b1e5f/src/proc_macros/mod.rs#L71

    It also means the library crate needs to export it.

    Given I can't type that character (though my editor helpfully generates the import), could it be changed to a typable character, or even no character? (considering collisions, instinctively TheTraitItem doesn't tend to show up in the way I name types, but that's me)

    opened by azriel91 1
  • Reduce MSRV

    Reduce MSRV

    I went for a quite high initial MSRV, so that I could focus on other things. But in order to make this crate even more useful, it would be nice to try and target the lowest possible Rust version properly supporting these higher-order-lifetime contraints (I know some older versions struggled with such situations (a.k.a., the infamous "lazy normalization" bugs), so I doubt we could reach the <1.50, but we never know 🤞)

    good first issue 
    opened by danielhenrymantilla 1
  • Handle gat binding constraints in remaining positions (trait bounds)

    Handle gat binding constraints in remaining positions (trait bounds)

    • Previous PR made Gat!(impl for<'n> LendingIterator<Item<'n> = …>) work (and also with #[apply(Gat!)], of course);
    • Current PR uses #[apply(Gat!)] call-site syntax to also handle : for<'n> LendingIterator<Item<'n> = …> bounds
    enhancement 
    opened by danielhenrymantilla 0
  • Speak about nougat & GAT at Rust Meetup?

    Speak about nougat & GAT at Rust Meetup?

    Hello Daniel,

    We're hosting a Rust meetup on November 16 at Stockly in our offices.

    During this meetup, there will be 3 talks of about 20 minutes, 15 minutes presentation and 5 minutes questions.

    Would you like to present nougat during such a presentation?

    Thanks and have a nice day, Oscar

    opened by Elrendio 0
  • GATs can not refer to other GATs in trait requirement's associated types

    GATs can not refer to other GATs in trait requirement's associated types

    Evaluating whether nougat could help get coap-message rid of its nightly wart, I found what appears to be another limitation of nougat that might warrant being pointed out in the list (or maybe fixed if it's easy): GATs do not work when referring to each other in a their required traits' associated types.

    This code works with #![feature(generic_associated_types)], but not with #[nougat::gat]:

    pub trait Trait {
        type Item<'a> where Self: 'a;
        type Iterator<'b>: Iterator<Item=Self::Item<'b>> where Self: 'b;
    }
    

    The relevant error message with macro-backtrace is:

    error[E0277]: the trait bound `Self: TraitඞItem<'b>` is not satisfied
      --> src/lib.rs:3:1
       |
    3  |   #[nougat::gat]
       |   ^^^^^^^^^^^^^^
       |   |
       |   the trait `TraitඞItem<'b>` is not implemented for `Self`
       |   in this procedural macro expansion
       |
      ::: /home/chrysn/.cargo/registry/src/github.com-1ecc6299db9ec823/nougat-proc_macros-0.2.1/mod.rs:41:25
       |
    41 |   #[proc_macro_attribute] pub
       |  _________________________-
    42 | | fn gat (
    43 | |     attrs: TokenStream,
    44 | |     input: TokenStream,
    45 | | ) -> TokenStream
       | |________________- in this expansion of `#[nougat::gat]`
    
    bug 
    opened by chrysn 4
  • `dyn`-safety / `dyn`-friendlyness

    `dyn`-safety / `dyn`-friendlyness

    The current workaround used by nougat (that is, the : for<'any> LendingIterator__Item super trait which "only" features the specific associated type) does not bode well with dyn Traits.

    Mainly: dyn Traits require that their associated types be provided, but to my knowledge it is not possible to higher-order specify the associated type of a super trait.

    That is:

    • dyn for<'any> LendingIterator<T = &'any u8> fails saying that 'any is unbound / an unknown lifetime parameter;
    • dyn LendingIterator + for<'any> LendingIterator__Item<T = …> fails because it combines two full traits under a dyn;
    • dyn for<'any> LendingIterator__Item<T = …> may actually work (as in, it's a ?Sized implementor of LendingIterator), but, alas, it is no dyn LendingIterator;
    enhancement 
    opened by danielhenrymantilla 0
  • Offer a workaround for https://github.com/rust-lang/rust/issues/97554

    Offer a workaround for https://github.com/rust-lang/rust/issues/97554

    The issue:

    • https://github.com/rust-lang/rust/issues/97554

    will be an annoying hurdle for users of this crate, so an automatic workaround ought to be offered.

    • A hand-written instance of the workaround I have in mind is showcased over here

    cc @sabrinaJewson @Globidev

    enhancement 
    opened by danielhenrymantilla 0
Releases(v0.2.4)
  • v0.2.4(Aug 8, 2022)

    What's Changed

    • Fix Self::Gat<'…> resolution in impl path::to::Trait for … { blocks by @danielhenrymantilla in https://github.com/danielhenrymantilla/nougat.rs/pull/14

    Full Changelog: https://github.com/danielhenrymantilla/nougat.rs/compare/v0.2.3...v0.2.4

    Source code(tar.gz)
    Source code(zip)
  • v0.2.2(Aug 3, 2022)

    What's Changed

    • Version 0.2.1 release by @danielhenrymantilla in https://github.com/danielhenrymantilla/nougat.rs/pull/10
    • Make Gat! handle impl for<'any> Trait<Assoc<'any> = …> as well by @danielhenrymantilla in https://github.com/danielhenrymantilla/nougat.rs/pull/12

    New Contributors

    • @azriel91 made their first contribution in https://github.com/danielhenrymantilla/nougat.rs/pull/9

    Full Changelog: https://github.com/danielhenrymantilla/nougat.rs/compare/v0.2.0...v0.2.2

    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Jul 8, 2022)

    What's Changed

    • Fix missing pub on the helper GAT trait(s) by @danielhenrymantilla in https://github.com/danielhenrymantilla/nougat.rs/pull/5
    • Lowered MSRV down to 1.53.0
    • Doc nits

    Full Changelog: https://github.com/danielhenrymantilla/nougat.rs/compare/v0.1.0...v0.2.0

    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(May 30, 2022)

Owner
Daniel Henry-Mantilla
🦀 Feeling crabulous 🦀 https://danielhenrymantilla.github.io
Daniel Henry-Mantilla
Tools to feature more lenient Polonius-based borrow-checker patterns in stable Rust

Though this be madness, yet there is method in 't. More context Hamlet: For yourself, sir, shall grow old as I am – if, like a crab, you could go back

Daniel Henry-Mantilla 52 Dec 26, 2022
Rust crate implementing short & stable ids based on timestamps

Lexicoid Short & stable IDs based on timestamps. Heavily inspired by Short, friendly base32 slugs from timestamps by @brandur. Install Install with ca

Luciano Mammino 6 Jan 29, 2023
A traditional web forum built in Rust with modern technology to be fast, secure, scalable, and stable.

Volksforo A traditional web forum built in Rust with modern technology to be fast, secure, scalable, and stable. Stack Rust actix-web askama ScyllaDB

Josh 5 Mar 21, 2023
Rust libraries for Bluesky's AT Protocol services. NOT STABLE (yet)

ATrium ATrium is a collection of Rust libraries designed to work with the AT Protocol, providing a versatile and coherent ecosystem for developers. Th

Yoshihiro Sugi 43 Jun 25, 2023
Leetcode Solutions in Rust, Advent of Code Solutions in Rust and more

RUST GYM Rust Solutions Leetcode Solutions in Rust AdventOfCode Solutions in Rust This project demostrates how to create Data Structures and to implem

Larry Fantasy 635 Jan 3, 2023
Simple autoclicker written in Rust, to learn the Rust language.

RClicker is an autoclicker written in Rust, written to learn more about the Rust programming language. RClicker was was written by me to learn more ab

null 7 Nov 15, 2022
Rust programs written entirely in Rust

mustang Programs written entirely in Rust Mustang is a system for building programs built entirely in Rust, meaning they do not depend on any part of

Dan Gohman 561 Dec 26, 2022
Rust 核心库和标准库的源码级中文翻译,可作为 IDE 工具的智能提示 (Rust core library and standard library translation. can be used as IntelliSense for IDE tools)

Rust 标准库中文版 这是翻译 Rust 库 的地方, 相关源代码来自于 https://github.com/rust-lang/rust。 如果您不会说英语,那么拥有使用中文的文档至关重要,即使您会说英语,使用母语也仍然能让您感到愉快。Rust 标准库是高质量的,不管是新手还是老手,都可以从中

wtklbm 493 Jan 4, 2023
A library for extracting #[no_mangle] pub extern "C" functions (https://docs.rust-embedded.org/book/interoperability/rust-with-c.html#no_mangle)

A library for extracting #[no_mangle] pub extern "C" functions In order to expose a function with C binary interface for interoperability with other p

Dmitrii - Demenev 0 Feb 17, 2022
clone of grep cli written in Rust. From Chapter 12 of the Rust Programming Language book

minigrep is a clone of the grep cli in rust Minigrep will find a query string in a file. To test it out, clone the project and run cargo run body poem

Raunak Singh 1 Dec 14, 2021
Rust-blog - Educational blog posts for Rust beginners

pretzelhammer's Rust blog ?? I write educational content for Rust beginners and Rust advanced beginners. My posts are listed below in reverse chronolo

kirill 5.2k Jan 1, 2023
The ray tracer challenge in rust - Repository to follow my development of "The Raytracer Challenge" book by Jamis Buck in the language Rust

The Ray Tracer Challenge This repository contains all the code written, while step by implementing Ray Tracer, based on the book "The Ray Tracer Chall

Jakob Westhoff 54 Dec 25, 2022
Learn-rust-the-hard-way - "Learn C The Hard Way" by Zed Shaw Converted to Rust

Learn Rust The Hard Way This is an implementation of Zed Shaw's Learn X The Hard Way for the Rust Programming Language. Installing Rust TODO: Instruct

Ryan Levick 309 Dec 8, 2022
Learn to write Rust procedural macros [Rust Latam conference, Montevideo Uruguay, March 2019]

Rust Latam: procedural macros workshop This repo contains a selection of projects designed to learn to write Rust procedural macros — Rust code that g

David Tolnay 2.5k Dec 29, 2022
The Rust Compiler Collection is a collection of compilers for various languages, written with The Rust Programming Language.

rcc The Rust Compiler Collection is a collection of compilers for various languages, written with The Rust Programming Language. Compilers Language Co

null 2 Jan 17, 2022
Integra8 rust integration test framework Rust with a focus on productivity, extensibility, and speed.

integra8 Integra8 rust integration test framework Rust with a focus on productivity, extensibility, and speed. | This repo is in a "work in progress"

exceptional 3 Sep 26, 2022
Neofetch but in Rust (rust-toml-fetch)

rtfetch Configuration Recompile each time you change the config file logo = "arch.logo" # in src/assets. info = [ "", "", "<yellow>{host_n

Paolo Bettelini 6 Jun 6, 2022
Rust Sandbox [code for 15 concepts of Rust language]

Rust-Programming-Tutorial Rust Sandbox [code for 15 concepts of Rust language]. The first time I've been introduced to Rust was on January 2022, you m

Bek Brace 4 Aug 30, 2022
TypeRust - simple Rust playground where you can build or run your Rust code and share it with others

Rust playground Welcome to TypeRust! This is a simple Rust playground where you can build or run your Rust code and share it with others. There are a

Kirill Vasiltsov 28 Dec 12, 2022