[HN Gopher] Rust: "Explain GATs Like I'm 5 Years Old"
       ___________________________________________________________________
        
       Rust: "Explain GATs Like I'm 5 Years Old"
        
       Author : ubj
       Score  : 174 points
       Date   : 2022-11-07 11:50 UTC (11 hours ago)
        
 (HTM) web link (old.reddit.com)
 (TXT) w3m dump (old.reddit.com)
        
       | stefan_ wrote:
       | Rust better figure out quick how they can shut down the Haskell &
       | C++ language artisans or it will be their downfall (in favor of
       | something like Zig).
        
         | josephcsible wrote:
         | Rust getting more of Haskell's type system features is a good
         | thing. The stronger your type system is, the more mistakes you
         | can discover as compiler errors instead of runtime bugs.
        
           | titzer wrote:
           | > The stronger your type system is...
           | 
           | I used to think a stronger and stronger type system was a
           | universal good, then I got experience with code written by
           | architecture astronauts[1] armed with such type systems.
           | Knowing exactly which kinds of things to make impossible at
           | compile time and which not is absolutely an art form.
           | Unfortunately, such type systems seem to result in so many
           | nightmares.
           | 
           | https://www.joelonsoftware.com/2001/04/21/dont-let-
           | architect...
        
             | exceptione wrote:
             | The linked article talks about abstraction, and that you
             | could abstract so much that the concrete problem doesn't
             | fit in your abstraction. I.e., your abstraction is broken.
             | 
             | I think that is precisely why OOP started to disappoint
             | (tbh: I also think many people did not understand OOP
             | really well). Haskell pushes you to compose types instead
             | of building inheritance hierarchies. Multiple inheritance
             | turned out to be a bad idea too, so yeah, OOP program do
             | typically not have the best models of the problem domain
             | they were intended to model.
             | 
             | At the other end of the spectrum you see people just give
             | up: everything is a kind of dict or a string, or wo knows,
             | null? Looking at you php, python, javascript..
             | 
             | All the improvements you see in todays OOP languages are
             | ideas stolen from functional languages like Haskell. What
             | Haskell could have done better is be strict instead of lazy
             | by default.
             | 
             | To model the real world and make assumptions explicit, a
             | type system, as Haskell gives you, is so nice.
             | 
             | I agree that you can make types also really abstract like
             | some people indeed do. It's fine if you do that as a
             | library author, but you should offer a facade with some
             | simpler type aliases. If you stick to Haskell98 and
             | possibly enable GADT support you already get an immensely
             | powerful language.
             | 
             | Also, I have not used Haskell for a while, but I heard the
             | compiler error messages have become way more human friendly
             | these days, so that helps with complex types.
        
               | titzer wrote:
               | As with anything, it all goes well until it doesn't. When
               | you have a single bug (e.g. type error or dynamic error),
               | the guts of whatever is in the box spill out. Case in
               | point: all the implicit template parameters to std:: in
               | C++ these days. The only way to add configuration options
               | in a backwards-compatible way was to add default values
               | for template arguments. That's all fine and good when you
               | can't see them, but as soon as you have a nasty type
               | error, the types that come spilling out in error messages
               | have zillions of default arguments that get printed.
               | 
               | It's almost always the same with inference. I would be
               | very, very wary of doing so much type sophistry that it
               | requires heavy inference to be usable.
        
             | ghostwriter wrote:
             | at the end of the day architecture astronauts delegate pipe
             | laying to plumbers, and the plumbers benefit from existing
             | build plans created by architects with all their
             | sophisticated tooling.
        
             | wtetzner wrote:
             | An architecture astronauts in Java just use code generation
             | and runtime reflection, which are bigger nightmares than
             | type system features.
             | 
             | Architecture astronauts are going to be a problem
             | regardless of language features.
        
           | throwawaymaths wrote:
           | There will always be more things that _someone_ will want to
           | formally verify that don 't exist in the type system. How do
           | you decide what is and isn't worth putting into the type
           | system?
        
           | bsaul wrote:
           | If a typesystem has any impact on the popularity of a
           | language, then i assume rust doesn't want to copy too much of
           | haskell and needs to know where to stop..
        
             | kibwen wrote:
             | The whole design of GATs is predicated on the notion that
             | Rust _doesn 't_ want to go as far as Haskell does here. See
             | boats' comment:
             | https://news.ycombinator.com/item?id=33506540
        
             | gpderetta wrote:
             | C++ sorta has the equivalent of this feature in the form of
             | nested templates. C++ is kind of popular and rust wants to
             | be a direct replacement.
             | 
             | Next step: template template parameters!
        
             | pjmlp wrote:
             | Typescript.
             | 
             | As for Haskell, their motto is "avoid success at any cost"
             | for a reason.
        
         | valenterry wrote:
         | Well, it's not academic, it solves real problems. More so for
         | library developers than for application developers, but a
         | thriving and great ecosystem of libraries is also good for
         | application developers.
        
         | exceptione wrote:
         | Well, in this realm Haskell
         | 
         | - allows you to write these things with less effort
         | 
         | - is more expressive
         | 
         | I keep an eye on Nim as well. Their syntax for algebraic data
         | types could use some syntactic sugar, but it looks like a
         | powerful and fast language, and the syntax allows you to do
         | away with all the c-style braces, curly braces and semicolons
         | that pesters so many languages and gives me RSI.
         | 
         | More upsides I found: macros on the AST-level and fast
         | compilation; it is a language that deserves more attention.
        
         | [deleted]
        
         | pjmlp wrote:
         | Actually I find C++ concepts much easier to grasp than GATs.
         | 
         | Zig still has to win the love of the giants that ship C++, and
         | increasingly Rust, in their products.
        
           | gpderetta wrote:
           | Aren't GATs equivalent to nested template classes? I.e. they
           | are really orthogonal to concepts.
        
             | pjmlp wrote:
             | Might be a bit of job deformation, knowing C++ since 1993,
             | and I still need to get myself used to those patterns.
             | 
             | Maybe I should delve again into GADTs in OCaml, before
             | having a second look.
        
               | Rusky wrote:
               | GATs are completely unrelated to GADTs.
        
               | pjmlp wrote:
               | From my point of view they look quite similar from
               | language type theory point of view, plus the machinery
               | for affine types.
        
               | Rusky wrote:
               | The only thing similar about them is the acronym:
               | 
               | GATs are generic _type aliases_ which are defined inside
               | traits.
               | 
               | GADTs are generic _sum types_ with extra constraints on
               | which variants may be used with which type arguments.
               | 
               | There is no reasonable way in which they are "quite
               | similar," nor do either of them have anything at all to
               | do with affine types.
        
               | pjmlp wrote:
               | Where is the type theory for Rust's GATs for us to
               | educate ourselves?
        
               | Rusky wrote:
               | The RFC might be a good place to start: https://rust-
               | lang.github.io/rfcs/1598-generic_associated_typ...
        
         | kibwen wrote:
         | GATs are an essential part of Rust's long-term async/await
         | story. It's not language-wankery, this is driven by actual
         | real-world use cases. Rust's own stdlib would have benefited
         | from this in many places had this feature existed years ago.
        
           | nrc wrote:
           | No, they're not. GATs are used internally in the compiler to
           | implement async methods, but there is absolutely no need for
           | them to be in the language for that (just like generators are
           | used to implement await but are not part of the (stable)
           | language). GATs may or may not be useful, but IMO the actual
           | real-world use cases are pretty weak.
        
             | josephcsible wrote:
             | Even if the use cases outside the standard library were
             | weak, given that the work needs to be done anyway for the
             | standard library, why wouldn't you expose them?
        
               | nrc wrote:
               | To be clear, there are not use cases in the std lib. The
               | use case is as an intermediate representation for the
               | compiler, and that is no motivation for inclusion in the
               | language (in the same way that we don't have vtables or
               | register allocation in the surface syntax)
        
               | wtetzner wrote:
               | Being able to have a generic "Mappable" trait seems like
               | a good use case for the standard library. It's something
               | I've wanted in the past, and was disappointed that it
               | didn't exist.
        
             | kibwen wrote:
             | Yes, I've read the stabilization thread and I'm aware of
             | your stance. :P Whether or not it's exposed, the mechanism
             | still needs to exist in the compiler, and enough library
             | authors are clamoring to use it that I think the argument
             | in favor of exposing them is stronger than you're giving it
             | credit for.
             | 
             | You mention generators, but a lot of people are clamoring
             | for those as well, and I fully expect them to be exposed to
             | end-users someday.
        
               | nrc wrote:
               | My point is that there or may or may not be reasonable
               | justification for adding GATs, reasonable people can
               | disagree on that. But async methods are not part of that
               | justification. Similarly, generators may or may not be
               | good for the language (IMO they would be good to add, if
               | they can be made to work etc.) but they didn't need to be
               | part of the language to add await.
        
       | jeremysalwen wrote:
       | Although the "LendingIterator" example is apparently done to
       | death, I'm going to try to explain it in a way that is actually
       | appropriate for "ELI5".
       | 
       | In Rust, there are many different ways to have a list of things
       | that come one after another. But sometimes we want to write some
       | code that doesn't care about the details like whether the list is
       | a list of words, or a list of numbers, it just cares about the
       | fact that there are a list of things in order.
       | 
       | Right now, we call these general lists of things in order
       | "Iterators", with code like this:                   trait
       | Iterator {             type Item;             fn next(&mut self)
       | -> Self::Item;         }
       | 
       | This tells us what an "Iterator" means. "Item" is the type of
       | thing that is in the list, for example it could be "number" or
       | "word". "next" is a function, which means it is something we can
       | do. When we do the "next" function, we get the next thing in the
       | list. So if we keep doing the "next" function over and over, we
       | can get everything in the Iterator one by one.
       | 
       | However, sometimes we want to have more complicated lists, where
       | the "next" function doesn't actually get us the next thing in the
       | list, it just tells us the address of the next thing inside the
       | computer. We would write this code like this:
       | trait StaticIterator {             type Item;             fn
       | next(&mut self) -> &'static Self::Item;         }
       | 
       | However, this requires the thing in the list to _always_ be at
       | the address (that 's what "&'static" means). But a lot of the
       | time the thing stays at the address while we are looking at the
       | list, and later it will go away.
       | 
       | We could write code saying that the things only have to be at the
       | address for a certain period of time. It would look like this:
       | trait ReferenceIterator<'a> {             type Item;
       | fn next(&mut self) -> &'a Self::Item;         }
       | 
       | This solves that problem, because now we are saying the address
       | only has to have the thing at it for the the time period "a" (we
       | call "a" the lifetime, because it tells us how long the things
       | will live at the address). The problem is, that when we write
       | "Iterator<'a>" we have to decide _up front_ what the lifetime
       | will be. So we can 't use the same code for lists with different
       | lifetimes, we would need to write the same code twice, once for
       | each lifetime.
       | 
       | This problem is what GATs help us with. GATs are a way to say
       | that we are going to have a list of addresses, where the
       | lifetimes could be different for each list. We would write the
       | code like this:                   trait LendingIterator {
       | type Item<'a> where Self: 'a;             fn next(&mut self) ->
       | Self::Item<'_>;          }
       | 
       | This allows us to write code which doesn't decide up front what
       | the lifetime of the addresses will be. Instead, the same code can
       | happily work with lists of addresses with _any_ lifetime. GATs
       | may seem scary at first, but as you can see, they are not doing
       | anything fancy, just allowing us to do what we _should_ be able
       | to do: write one piece of code that can work with lists of
       | addresses with different lifetimes.
        
       | aslak wrote:
       | No doubt they'll needlessly spread to code that doesn't require
       | them - happened already with async and that is much, much simpler
       | concept (than one of the most complex things, GATs).
        
         | jgilias wrote:
         | This is different though. Async colors functions, so once
         | you've got something that has an async interface, you either go
         | async too, or have to do shenigans to use it in a blocking way.
         | GATs will just let library authors make more flexible
         | interfaces.
         | 
         | If there's anything to worry about then that may be compile
         | times of complex codebases. I haven't measured anything but
         | just as a rule of thumb more generic code means longer compile
         | times in Rust-land.
        
         | yakubin wrote:
         | Personally, I consider GATs a lot easier to understand than
         | async. It doesn't transform my code into something completely
         | different than I'm seeing. It's just the ability to put a
         | generic parameter in one more place, paradoxically reducing the
         | number of traits.
         | 
         | It's something that you naturally try to write when you don't
         | know it's not supported, rather than an additional thing to
         | learn.
        
       | karmakaze wrote:
       | Is there any way to use GATs to achieve a particular use case of
       | ah-hoc/anonymous enums[0]? Say if library L1 returns enum A|B, L2
       | operates on enum A|B, and main wants to pass enum A|B from L1 to
       | L2, with the kicker being A, B are concrete in main.
       | 
       | I'm not interested in distinguishing between multiple appearances
       | of the same type as in the RFC example, so rather like:
       | let foo: (~str|int) = (_|666);       match foo {         (s:
       | str|_) => println(fmt!("string: %?", s)),         (_|n: int) =>
       | println(fmt!("int: %?", n))       }
       | 
       | The most disappointing thing I've encountered with Rust is not
       | seeing anonymous enums or structural typing other than for
       | tuples. I read somewhere that structural typing is only for
       | tuples because the word struct is in structural(?!).
       | 
       | [0] https://github.com/rust-lang/rfcs/issues/294
        
         | celeritascelery wrote:
         | These are called sub-types or pattern types, and they are not
         | in the language. But they are something I would love to have.
         | You can't emulate them with GAT unfortunately.
         | 
         | https://cohost.org/oli-obk/post/165584-ranged-integers-via
        
           | karmakaze wrote:
           | I've not heard this called sub- or pattern types before.
           | Perhaps the rfc syntax is complicating the discussion.
           | 
           | We have struct and tuple in Rust where tuple is an ad-
           | hoc/anonymous struct where only the number and order of
           | member types matter. We have enum which are nominal types. We
           | don't have the ad-hoc/anonymous version of them where only
           | the set of possible types matter.
           | 
           | In TypeScript, F#/OCaml they would be like x: TypeA|TypeB
        
             | celeritascelery wrote:
             | I first heard about this feature in OCaml. I got the name
             | subtype or pattern type from this thread:
             | 
             | https://internals.rust-lang.org/t/thoughts-on-pattern-
             | types-...
        
               | karmakaze wrote:
               | Thanks for the reference. I can see how values of type A
               | could be considered as a subset of values of type A|B,
               | but I think the 'any value of type A' subset of type A|B
               | is more related to types and less about restricting the
               | range/set of values for a type.
        
         | hn92726819 wrote:
         | You might be interested in Rust's either crate (not built-in):
         | https://docs.rs/either/latest/either/
        
       | [deleted]
        
       | mseepgood wrote:
       | I seriously doubt a 5 year old can understand this explanation.
        
         | hitpointdrew wrote:
         | I am over 30 and I don't understand the explanation. Probably
         | because first and foremost I don't even know what a "GAT" or
         | "GATs" are/is.
        
           | kibwen wrote:
           | "Generic Associated Type".
           | 
           | Explained in other terms, a generic is just a parameter for
           | types, analogous to how `x` is a value parameter for the
           | function call `foo(x)`.
           | 
           | Meanwhile, an associated type is just a type alias that's
           | associated with a trait (a trait is a class, but with no
           | state), similar to how traits can have associated functions
           | (which are also known as "methods").
           | 
           | GATs, then, "just" let you use generics in associated types,
           | which was previously unsupported in Rust.
        
         | lsllc wrote:
         | There's another (better) example down in the comments using a
         | PointerFamily trait; someone asked "ELI4" and the answer is
         | pretty good:
         | 
         | https://old.reddit.com/r/rust/comments/ynvm8a/could_someone_...
        
         | royjacobs wrote:
         | I mean, a GAT is just a monoid in the category of endofunctors,
         | after all.
        
           | kibwen wrote:
           | GATs don't actually let you go full monad, so not quite. :P
        
         | jerf wrote:
         | It's arguably in the category of "pet peeve" more than anything
         | else, but I really hate the ELI5 meme and suspect most people
         | asking for it and/or trying to provide explanations that fit it
         | have never had a five-year-old. Unless your target five year
         | old is a five year old Terence Tao, and honestly even then,
         | most of the time these are terrible.
         | 
         | ELI12 would make a lot more sense; old enough to have had
         | enough life experience to hang some of these explanations off
         | of, young enough to need simple explanations.
        
         | [deleted]
        
         | fleur-de-lotus wrote:
         | Don't insult me !
        
       | stephc_int13 wrote:
       | There is a simple but fundamental observation that many
       | experienced software engineers discover sooner or later:
       | Reading/understanding code is much more difficult than writing
       | it.
       | 
       | Because of that, programming languages should be designed to make
       | the reading part as easy as possible.
       | 
       | What is the point of those high-level abstract features if they
       | make reading/understanding the code too difficult?
        
         | kibwen wrote:
         | But this feature makes reading code easier? It allows library
         | authors to absorb the complexity into themselves, allowing them
         | to present easier and more convenient APIs for the users of
         | those libraries. Because programs that consume libraries
         | outnumber the libraries themselves, that results in a net
         | reduction in complexity.
        
           | jstimpfle wrote:
           | I disagree that this makes reading code easier. It is
           | tremendously more complex to understand a generic API
           | precisely because it is generic - it must cater to a huge
           | variety of use cases, and consequently has more nuts and
           | bolts, and knobs to turn.
           | 
           | Now this can be the right choice in many situations, when the
           | higher cost in understanding the API is amortized over a
           | larger number of uses. But in general I much prefer simple
           | and specialized APIs and don't see much of a need for
           | genericity. This includes error handling and resource
           | allocation.
        
         | mrkeen wrote:
         | Because there's no correct level of abstraction. There's only
         | appropriate abstractions for different situations.
        
           | jstimpfle wrote:
           | Maybe there are incorrect situations, though.
        
       | gpderetta wrote:
       | Let me try to explain GAT from a C++ programmer perspective:
       | 
       | 'int' is a type. 'vector<int>' is also a type
       | 
       | but 'vector' itself can be understood as a function from type to
       | type (for example, invoking vector with an int produces
       | vector<int>)
       | 
       | Now let's say you want a function that given a type produces a
       | vector of pairs of that type. In C++ you could do:
       | template<class T>        struct pair_vector : vector<pair<T, T> >
       | {};
       | 
       | But using inheritance for this is not great, the result of
       | pair_vector<int> is really not a vector<pair<int,int> >. You can
       | use an associated type:                  template<class T>
       | struct pair_vector { using result = vector<pair<T, T>>; };
       | 
       | now 'pair_vector<int>::result' is exactly 'vector<pair<T,T> >'
       | [1].
       | 
       | So these are first order type functions. Let's say you want
       | higher order type functions, i.e. functions that take other type
       | functions as parameters or return them.
       | 
       | For example let's say you want a to build a pair<X,Y> piece wise:
       | you want a function pair_1st that given a type X returns another
       | function that takes a type Y and finally returns a pair<X,Y>:
       | template<class X>         struct pair_1st {
       | template<class Y>           struct apply { using result = pair<X,
       | Y>; };        };
       | 
       | So: 'pair_1st<int>::apply<double>::result' is 'pair<int,
       | double>'; 'pair_1st' is an higher order function from type => (to
       | a function form type => type).
       | 
       | Apply here is an associated type, (like result before), but is
       | generic, hence an associated generic type.
       | 
       | So, what if we also want to abstract over pair in 'pair_1st',
       | i.e. an higher order function that takes two parameters, the
       | first a function from type => to type, and the second a type:
       | template<template<class T1,class T2> F, class X> struct bind_1st
       | {           template<class Y>           struct apply { using
       | result = F<X, Y>; }         };
       | 
       | Now 'bind_1st<pair, int>::apply<double>::result' is again
       | pair<int, double>;
       | 
       | I think that rust doesn't allow higher order (i.e. template
       | template) parameters, but that's not a big problem:
       | template<template<class F, class X> struct bind_1st {
       | template<class Y>           struct apply { using result =
       | F::apply<X, Y>::result; }  [2]        };             struct
       | pair_fn {               template<class X, class Y> struct apply {
       | using result = pair<X, Y>; };        };
       | 
       | By lifting pair into into a pair_fn class with a GAT we can solve
       | the issue: bind_1st<pair_fn, int>::apply<double>::result is again
       | the same as pair<int, double>. In fact in C++ for a long time,
       | before variadic templates, even if template template parameters
       | were available from the beginning, GATs were the preferred way to
       | encode higher order type functions.
       | 
       | The big difference between rust and C++ of course is that the
       | type level functions in C++ are untyped other than maybe being
       | able to specify the arity, while rust has a proper type system
       | for generics, which I assume make all of this more complicated to
       | implement from a compiler point of view.
       | 
       | Now why would you want all this nonsense outside of hardcore type
       | level programming? Well, there is a continuum from simply writing
       | a generic class to actual metaprogramming, so even if you do not
       | want to do the latter, you might end up using on some of this
       | stuff even for relatively simple generics.
       | 
       | [1] we can use template using to hide ::result, but that's a c++
       | quirk which is not important in the grand scheme of things.
       | 
       | [2] template typename noise removed for sanity
        
       | jeremysalwen wrote:
       | This comment here serves as a gravestone for a long form, well
       | written (I think) explanation of GATs that I thought was actually
       | ELI5 (unlike every other ELI5 response which always immediately
       | starts dropping tons of jargon), which Firefox lost when somehow
       | it randomly activated the back button ;_;.
       | 
       | I could potentially rewrite it if anyone was interested, but I am
       | under no illusions that's likely ;)
        
       | npigrounet wrote:
        
       | lucasyvas wrote:
       | The top Reddit example is tremendous. I'm passed being a Rust
       | beginner, but I have a note to the Rust community:
       | 
       | Your official examples are overcomplicated and bad and you should
       | also feel a little bad.
       | 
       | Stick to things like apple, orange, pear and you'll see much
       | easier adoption than if you go with something like
       | LendingIterator! Through the GAT process all I have seen is the
       | same hyper-specific example used that confuses the hell out of
       | people that just need the "Hello World" version.
       | 
       | If you want, I will volunteer to be the test subject "dumb" Rust
       | programmer to vet the quality of future examples.
       | 
       | Tangentially related - here's a WASM example that goes from
       | "Hello World" to Conway's Game of Life (I shit you not, that's
       | actually how the tutorial progresses):
       | 
       | https://rustwasm.github.io/docs/book/
        
         | stonemetal12 wrote:
         | >Stick to things like apple, orange, pear
         | 
         | While I am no pedagogical psychologist, I have witnessed
         | mutiple times, how poor unrealistic examples give people a bad
         | understanding of the thing they are trying to learn. Going with
         | Apple, Orange, Pear would give people an idea of what the
         | keywords did, but no understanding of when it was a good idea
         | to apply the technique.
         | 
         | I think that is one of the primary reasons OO sucks so badly
         | today. Everyone was trained on Fruit, Shapes, and Cars so they
         | know the keywords, but don't have the foggiest clue of when it
         | is a good idea to use them.
         | 
         | Not that I am suggesting LendingIterator! is a super wonderful
         | example either. A good motivating example like 95% of the
         | difficulty, and reward of programming tutorials.
        
           | lucasyvas wrote:
           | The solution to this is to have two examples: Beginner and
           | Practical/Advanced
        
             | Rusky wrote:
             | The post you're complaining about starts with "because
             | everyone likes to use that LendingIterator example I would
             | offer a bit non-trivial example." It sounds like they are
             | already doing that.
        
             | andrepd wrote:
             | I think the point is that even for beginners these examples
             | are dumb. If you want to explain OOP and inheritance you
             | can use simple but realistic examples, like `class Shape {
             | fn draw() }`, `class Rectangle extends Shape`, `class
             | Circle extends Shape`, rather than `class Dog extends
             | Animal` or some dumb stuff like that.
        
               | lucasyvas wrote:
               | Bingo
        
             | insanitybit wrote:
             | At only twice the cost! A bargain.
        
               | cercatrova wrote:
               | Maybe we should be able to parametrize over difficulty :P
        
         | dathinab wrote:
         | GATs are fundamentally a higher-level abstraction similar to
         | higher-kinded types (you could see them as a very small subset
         | of HKT).
         | 
         | This means:
         | 
         | - they are not trivial no matter how much you bend it
         | 
         | - you should rarely use them in your day to day work (use as in
         | "create traits with GATs")
         | 
         | - they allow you to abstract over certain things in a much
         | nicer way. This thinks matter a lot for certain kinds of
         | libraries. Which is why we do include them even through they
         | are not simple (i.e. once some of the current limitations are
         | lifted I expect there is a high chance for them to be used in
         | the next major version of all of actix-web, axume, tower and a
         | bunch of async libraries).
         | 
         | Through I agree that the example on the blog announcement and
         | on reddit aren't grate. If GATs would just be for the problems
         | described there adding them to the language wouldn't be worth
         | it. (I mean adding a different more complicated and harder to
         | use iterator trait which only matters for a small handful of
         | use-cases really isn't enough reason to add such complicated
         | language feature, on the other hand being able to abstract over
         | async functions properly is a major boon).
         | 
         | Another problem is that some of the "better" examples currently
         | can't be implemented in a straight forward way to the
         | limitations which GATs still have for now.
        
         | rq1 wrote:
         | > Because everyone likes to use that LendingIterator example I
         | would offer a bit non-trivial example
        
         | leetrout wrote:
         | This is exactly why open source communities need more than
         | programmers.
        
         | gpderetta wrote:
         | The example is indeed very good, but I think it is more about
         | faking HKT with GAT and I'm not sure that was the primary
         | motivation for GAT in rust.
        
           | Macha wrote:
           | I thought that was pretty much the motivation. HKTs are hard
           | - hard to implement with Rust's low overhead goals, and hard
           | to teach to newbies. My understanding is that GATs came about
           | as some simpler lower hanging fruit to try first.
        
         | kibwen wrote:
         | I think this is a failure of messaging rather than a failure of
         | example-writing. Despite all the hullabaloo, GATs are not a
         | "feature", they're just the lifting of an arbitrary restriction
         | that used to exist in the typechecker. There doesn't need to be
         | a section on GATs in any Rust book, for example. It's just
         | generics in associated types. The examples you've seen like
         | LendingIterator are not intended to be pedagogical, they're
         | intended to showcase some specific cool things that people (who
         | are presumed to already know Rust) will now be able to do, and
         | preview things that might someday find their way into the
         | stdlib as a result of this new ability.
         | 
         | For sure, if people _want_ to learn how to use generics in
         | associated types, then there can be examples catering to those
         | users. But I think that framing this as a  "feature" makes
         | people feel like they _need_ to go out of their way to  "learn"
         | them, when in reality users will either try to use them
         | naturally (as a result of prior knowledge about generics and
         | traits) and it will "just work" and they'll never think twice
         | about it, or they'll never try to use them because they see no
         | need to based on what they're trying to write.
        
         | float4 wrote:
         | I agree that this can be simplified further but at the same
         | time I disagree with the "it needs to be understandable to
         | beginners!" attitude.
         | 
         | Some concepts aren't for beginners. This includes most things
         | that have to do with more advanced type theory or for example
         | category theory.
         | 
         | ELI5 GATs? ELI5 Monads? If we could we would.
        
         | tracker1 wrote:
         | As a Rust beginner, have to completely agree... A lot of the
         | examples are overtly complex. Beyond this, sometimes finding
         | appropriate use case examples for libraries harder still.
         | Mostly in that you have to have a very deep knowledge base to
         | even begin in a lot of ways. I mean, I get it, but it's still
         | much harder to get your feet wet beyond "Hello World" type
         | examples in practice.
         | 
         | Note: been a rust beginner for a while, as I play with it,
         | (re)read a couple books I have on Rust, but then set it aside
         | again as I'm unable to justify using it vs. just getting
         | something done. I really like a lot of the concepts but digging
         | in has been repeatedly difficult.
        
       | deafpolygon wrote:
       | I'm not familiar with Rust, but this seems pretty similar to how
       | C# handles generics. Am I misunderstanding?
        
         | Smaug123 wrote:
         | C# does already allow you to create interfaces whose methods
         | are generic, yes. The way they are _implemented_ is entirely
         | different: Rust monomorphises all generically-typed functions
         | (I believe this remains true for GATs), whereas the CLR
         | monomorphises only struct-generics (and it uses the JIT to do
         | so, unless you 're using the fairly new AOT compilation).
        
       | Georgelemental wrote:
       | In my experience, GATs seem like arcane magic until you need
       | them, and then they make perfect sense.
        
       | _448 wrote:
       | Okay, so this is like so in C++:
       | template<template <typename> T> class C{};
       | 
       | Am I right, Rustaceans? :)
        
         | Rusky wrote:
         | You can do similar things with it, but GATs can't live on their
         | own like that. Instead they are always a member of some trait,
         | so a closer comparison would be with something like this:
         | class C {             template <class>             using T =
         | ...;         };
        
       | jstx1 wrote:
       | As a beginner (having finished the book and not much more) the
       | example in the top comment is very difficult to parse and hold in
       | my head. At a glance I have no idea what we're doing and why.
        
         | unsafecast wrote:
         | Basically, before this you couldn't have generic types in
         | traits, like this:                   trait X {             type
         | Y<Z>;         }
         | 
         | You couldn't express "a thing that contains another thing" in
         | one of those type expressions before, and also use the
         | contained type. It's needed for Mappable, because it's an
         | abstraction over the concept of modifying a value inside a
         | container.
         | 
         | IMO the implementation that they have is a bit obtuse, but I
         | don't know if you can fit something better in this language.
        
           | wisnoskij wrote:
           | I really wonder about the usefulness of implementing a
           | language feature to reduce the need to copy paste in an
           | editor.
        
             | cercatrova wrote:
             | Isn't that basically all language features? I could also
             | write                   print("Hello")
             | print("Hello2")              print("Hello3")
             | print("Hello4")              print("Hello5")
             | 
             | (which I partially copy-pasted above) but I use a for-loop
             | instead.
        
             | wtetzner wrote:
             | Really? You don't see the problem with trying to maintain
             | code that's been copy-pasted everywhere with slight
             | differences? Doesn't it seem easier to maintain one
             | implementation of something than N implementations?
        
             | mcronce wrote:
             | Generics are a language feature to reduce the need to copy
             | and paste in an editor. For that matter, _functions_ are a
             | language feature to reduce the need to copy and paste in an
             | editor.
        
             | pyrale wrote:
             | As a library maintainer, you can't copy-paste a line for a
             | type that isn't written yet.
             | 
             | As a library user, you can't copy-paste a line for a type
             | you just created in a library you don't maintain.
             | 
             | Being able to do what you describe lets a maintainer create
             | an interface for all the future types that satisfy given
             | requirements.
        
         | NDizzle wrote:
         | As a non-beginner, I feel the same way. I'm not too worried
         | about it at the moment, but it's early in the day (and week)
         | and DST happened yesterday, sooo...
        
         | Smaug123 wrote:
         | More concretely, it's a pain to have to implement the same
         | functions over and over for slightly different types, even when
         | you're basically doing the same thing every time. Specifically,
         | `Option` and `Result`, for example, both have `map` implemented
         | for them (that's one lot of duplication), but also the
         | _consumers_ of the data structures need to know whether they
         | 've got an `Option` or a `Result` (and that's another lot of
         | duplication if you want to allow consumers to use either an
         | `Option` or a `Result` depending on what happens to be locally
         | convenient to them). Sometimes you really do want to do
         | different things depending on whether you've got an `Option` or
         | a `Result`, but much of the time you don't care; you're only
         | using it to signal errors, and you have no particular opinion
         | on how you do so.
         | 
         | The example in the top comment allows you to implement what it
         | means for a type to "have a `map` function". So it doesn't get
         | rid of the duplicated effort on the "supply side" - we still
         | have to implement the `Mappable` trait for each of `Result` and
         | `Option` individually - but it allows the consumer to not care
         | whether it's got an `Option` or a `Result`, by using only the
         | `Mappable` trait instead. That is newly possible because the
         | GAT feature allows the definition of a `Mappable` trait which
         | contains fresh generics, and `map` is inherently a generic
         | function.
        
           | tialaramex wrote:
           | Result and Option both implement Try, so if you actually just
           | want to know whether to keep going or give up, the Try trait
           | will give you a ControlFlow which says exactly that
           | regardless of whether your input is an Option or a Result
        
         | pyrale wrote:
         | It is totally ok to not understand stuff, and it's totally ok
         | to not want to learn it as long as you don't need it.
         | 
         | Rust is a language with plenty of things to learn, some of
         | which take some time to get used to. And often, having a
         | concrete need for something makes it easier to learn the
         | associated technique.
        
       | the__alchemist wrote:
       | I'm filing this one along with Monads and Haskell as programming
       | concepts that I'll never understand. Rust is my favorite
       | language, but that explanation, lauded directly below it as _This
       | is personally the easiest to understand example I 've seen of
       | GATs._, was incomprehensible.
        
         | kitd wrote:
         | Yeah. I'd like to meet the 5-year-old that understood that.
         | 
         | edit: the "What are GATs" paragraph in the link posted by
         | @leetrout is much more usable.
        
         | nicoburns wrote:
         | I think the easiest way to understand it is this:
         | 
         | Suppose you have a concrete type like
         | Vec<i32>
         | 
         | Regular generics enable you to make the contained type generic,
         | so you have:                   Vec<T>
         | 
         | where T can be i32, u32, String, etc. GATs allow you to make
         | the _container_ generic. So you can have:
         | T<i32>
         | 
         | where T might be Vec, Option, Box, etc.
        
           | aarobot wrote:
           | Awesome example.
        
           | fallat wrote:
           | THIS is literally the best explanation. My god people have
           | forgotten how to explain anything anymore. Thank you.
        
             | agluszak wrote:
             | It is easy to understand because it is an
             | oversimplification :)
        
               | jstx1 wrote:
               | Which part is inaccurate/incomplete?
        
               | GolDDranks wrote:
               | The part that says `T<i32>`. GATs don't allow you to do
               | that, not directly. GATs allow you, as said by Boats in
               | https://news.ycombinator.com/item?id=33506540, have a
               | generic type as an associated type, nothing less, nothing
               | more. If you want to express `T<i32>`, GATs _enables_ you
               | to do that as a kind of a distorted encoding, but it's
               | not as simple as
               | https://news.ycombinator.com/item?id=33505810 makes it
               | seem.
        
               | howinteresting wrote:
               | Bring able to abstract over Rc/RefCell and Arc/Mutex with
               | marker types seems pretty useful. It's not directly HKT
               | but it enables use cases that would otherwise be
               | supported by HKT.
        
               | dllthomas wrote:
               | Yes, but that doesn't make it somehow accurate to explain
               | HKT and pretend you're explaining GAT...
        
               | howinteresting wrote:
               | That is definitely true.
        
               | gpderetta wrote:
               | The Associated bit of GAT I guess.
        
           | philzook wrote:
           | What you're describing sounds to me like higher kinded types,
           | which are of some relationship to GATs (GATs enable a better
           | encoding of higher kinded types as I understand it), but are
           | not GATs. https://docs.rs/higher/latest/higher/
        
             | gunapologist99 wrote:
             | > as I understand it
             | 
             | You could be 100% right (I have no idea, but it sounds
             | reasonable), but this entire question really hits home that
             | Rust has tons of basically incomprehensible design
             | features.
             | 
             | The worst part about features like this are that a few
             | smart people will actually use them (whether because
             | they're powerful features, or just because _they_ feel
             | powerful when using them), and then render their code
             | incomprehensible to the mere mortals who have to support
             | their code in the future.
             | 
             | The KISS principle
             | (https://en.wikipedia.org//wiki/KISS_principle) is
             | sometimes ignored because people don't want to be stupid,
             | and why wouldn't you want to use a new toy(tool) once
             | you've figured it out?
        
               | idontpost wrote:
        
               | kibwen wrote:
               | This is quite uncharitable. Rust's features aren't there
               | for no reason, they're there to address the hard problems
               | of how to achieve C++-level performance while providing
               | the level of abstraction that modern developers expect
               | and remaining totally type-safe.
               | 
               | Furthermore, the amount of hand-wringing over this
               | feature is disproportionate to its actual impact. It
               | feels like people see "Haskell" mentioned in the same
               | breath and immediately lose all sense of reason. This
               | feature is not basically incomprehensible, it's a
               | straightforward addition that allows generics to be used
               | with associated types, both of which are features that
               | already exist in the language.
               | 
               | If you're asking yourself, "when would I ever need to use
               | these?", this answer is that you may never find call to
               | use these. Traits are mechanisms for creating
               | abstractions, which are mostly relevant to library
               | authors. If you're not a library author, you may never
               | have a good reason to write a trait. Even for people who
               | _are_ writing traits, they may have no reason to ever use
               | associated types. And even for people who _are_ writing
               | traits with associated types, they may have no reason to
               | ever want to use generics with those associated types.
               | 
               | However, all of these features are extremely useful for
               | the people _consuming_ libraries, and far from making
               | Rust code _more_ complicated, from what I 've seen so far
               | it makes Rust code in the wild _less_ complicated.
        
               | soggybutter wrote:
               | Things like GATs also allow those smart people to add
               | sorely needed features, like async traits, which the rest
               | of us plebeians can benefit greatly from
        
               | philzook wrote:
               | The problem is people who are smart enough to use fancy
               | features, but not wise enough to show restraint.
               | 
               | The question is do we inhibit the wise to protect us from
               | the unwise? The answer might be yes. I enjoy learning
               | about power features so I hope not.
        
           | dllthomas wrote:
           | The ability to just use T for Vec, Box, etc, anywhere you
           | might want to is "higher kinded types" - GATs only allow it
           | specifically for "associated types" on a trait.
        
           | brap wrote:
           | Thanks, this was much easier to understand than the original
           | article. What will I use this for? Usually for the "T" to be
           | useful it needs to have some interface. In Java I'd just use
           | Collection<Integer> or something more abstract.
        
             | orblivion wrote:
             | I forget what exactly a GADT is (even though I've used
             | Haskell a bit before). But just going by the linked example
             | from Reddit, you could make a generic `map` function that
             | worked on any container type. You'd have to implement the
             | map function _separately_ for each type. But then you could
             | make a bunch of utility functions that you 'd implement
             | _once_ that use the generic `map` under the hood, and all
             | of those container types would be able to use those utility
             | functions.
             | 
             | Not a Rust guy but I think that's the idea.
        
               | kibwen wrote:
               | Just so that we're clear on acronyms for everyone out
               | there, "GADT" is a computer science term for "generalized
               | algebraic data type", which has no relation to "GAT",
               | which is just Rust terminology for generics in associated
               | types (even in Rust, I doubt that anyone will bother to
               | refer to "GAT" in the coming years, rather than just
               | lumping them in with the concept of associated types in
               | general; there's really no need to teach them
               | separately).
        
             | mrkeen wrote:
             | T keeps its type info statically. So if you put a Set in,
             | you can get a Set out.
             | 
             | Casting to Collection loses the static type info, so you
             | couldn't write foo(bar(set)) if bar returned a Collection
             | but foo accepted a Set.
        
           | inatreecrown2 wrote:
           | this sounds almost too easy to understand, will have to check
           | with the documentation. (thank you!)
        
             | insanitybit wrote:
             | I think 90% of the reason why people have a hard time with
             | GAT is because there aren't a ton of use cases. From what I
             | can tell, GAT is quite simple - it is just like all other
             | Rust generics except that now the generic can be in a new
             | place.
             | 
             | Rust has made it _years_ without needing this (granted, a
             | few things have been less than ideal because of it)
             | because, for the most part, people have not needed to reach
             | for this type of abstraction. But it 's really just
             | "Generics in a new place".
             | 
             | Personally, I've accidentally written the GAT syntax years
             | before I ever heard the term GAT because I _thought_ a
             | generic could go there.
        
               | Jweb_Guru wrote:
               | It's needed for a lot of features people desperately
               | want. Async functions in traits, closures that return
               | futures where you need to run the future in a loop, and
               | array indexing that doesn't necessarily return a
               | reference, are three examples of things that essentially
               | need GATs in order to be implemented without allocating.
               | You may not feel like you missed it but not having that
               | leads to a ton of ugly workarounds in Rust code. Also, in
               | a lot of cases where people would otherwise use GATs,
               | people instead work around them by making _traits_
               | generic and have super complicated towers of generic
               | types, you 've probably seen interfaces like this when
               | using hyper for instance. GATs would simplify most of
               | those interfaces.
        
               | insanitybit wrote:
               | The vast majority of users do not desperately want async
               | functions in traits, they just use async_trait and go
               | "it'll be cool when GATs are done". Besides, no one is
               | going to use GAT for async traits, that will be done _for
               | them_ by the compiler.
               | 
               | Obviously there are use cases for GAT, what I'm saying is
               | that they're going to be in very generic library code,
               | which I don't think most users are writing.
               | 
               | For libraries like Hyper, which prioritize being
               | extremely generic (it's split into a ton of reusable
               | crates), which aims to serve as a very low level set of
               | generic primitives for HTTP, yes it will come up.
               | 
               | Hyper is a great example of a use case that 99.99% of
               | developers are not going to be able to relate to because
               | they aren't building a set of completely generic http
               | primitives. It's an important use case, it's just not
               | relatable.
        
           | howinteresting wrote:
           | Note that this isn't directly what GATs are, but you can use
           | marker types to achieve this effect.
        
         | kibwen wrote:
         | You mention that you use Rust. In that case, you're almost
         | certainly familiar with generics, and you're likely familiar
         | with associated types. If you've ever found yourself struggling
         | to write an associated type that needed a generic parameter,
         | this feature is for you. And if you haven't ever needed to do
         | such a thing, that's totally fine, and you can happily ignore
         | it and content yourself with benefiting from libraries that are
         | now able to expose simpler and more powerful APIs by leveraging
         | this feature under the hood.
        
           | KallDrexx wrote:
           | I've yet to figure out why to use associated types instead of
           | generic parameters, and every time I've seen an explanation I
           | nod my head in confusion and carry on.
        
             | kibwen wrote:
             | Associated types serve much the same role as generic
             | parameters in the context of traits, but with the crucial
             | distinction that associated types are specified by the
             | person implementing the trait for their type, whereas
             | generic parameters are specified by the person calling the
             | methods defined by the trait.
             | 
             | In practice you _could_ basically just have generic
             | parameters and not bother with associated types, but it
             | would be much less nice to use these APIs.
             | 
             | Here's an example of how an API would change if Rust didn't
             | have associated types:                   let x = [1,2,3];
             | // an array of i32         let mut y = x.iter(); // an
             | iterator over it         y.next(); // pull an item from the
             | iterator
             | 
             | If Rust didn't have associated types, then that last line
             | would have to look like this:
             | y.next::<&i32>(); // if Rust didn't have associated types
        
             | jamincan wrote:
             | The Rust book has a pretty good explanation of the
             | difference. https://doc.rust-
             | lang.org/book/ch19-03-advanced-traits.html
        
             | Nullabillity wrote:
             | You can view generic parameters as "inputs", they are
             | controlled by the consumer of the type/trait (you construct
             | a `Vec<String>` because you want to store a bunch of
             | `String` values).
             | 
             | Associated types are "outputs", they are controlled by the
             | impl (`<Vec<String> as IntoIterator>::into_iter()` will
             | always return a `std::vec::IntoIter<String>`, because that
             | is how the trait is implemented for `Vec<T>`).
             | 
             | You can often use generic parameters for "outputs" as well
             | (you could have a world where you had `impl<T>
             | IntoIterator<IntoIter<T>> for Vec<T>`). However, this
             | prevents the compiler from inferring the "output" type
             | since there is no longer a guarantee that it is unique for
             | a given set of "inputs".
        
               | gpderetta wrote:
               | You can generalize this:
               | 
               | A: type level functions from types to (associated) types
               | (i.e. normal generics)
               | 
               | B: type level functions from types to type level
               | functions (either As or Bs) (GAT).
               | 
               | with generic parameters (i.e. higher kinded types, or
               | template template parameters in C++) you could have type
               | level functions that map type functions to (types or type
               | functions), but once you have GATs you can fake them by
               | passing to first order functions a type that has an
               | associated type.
        
           | the__alchemist wrote:
           | I appreciate the explanation. I've been more of a firmware or
           | application developer in Rust than library developer, so will
           | keep this in the hip pocket.
        
             | jackmott42 wrote:
             | Yeah basically if the day comes when you need these, you
             | will just naturally use them and it will work and you will
             | never think anything of it.
             | 
             | Whereas I stumbled upon a use for these ~4 years ago and
             | was just mad that what I naturally wanted to do didn't work
             | =)
             | 
             | But yeah in certain programming domains, probably most, it
             | just doesn't come up.
        
               | mcronce wrote:
               | Yeah - it's almost better to think of GATs as less of a
               | feature and more of a weird restriction being removed.
               | That's obviously super reductive knowing the amount of
               | work that went into it, but from an end developer
               | standpoint, it's an explanation that helped make it make
               | sense to me.
        
         | atemerev wrote:
         | I can program in Scala and Haskell relatively easily. I still
         | struggle with Rust's lifetime abstractions and things like
         | this:                 impl<I, F> LendingIterator for Map<I, F>
         | where        I: LendingIterator,        F: for<'a> Mapper<'a,
         | <I::Item as GivesItem<'a>>::Item>,       {        type Item =
         | dyn for<'this> GivesItem<         'this,         Item = <F as
         | Mapper<'this, <I::Item as GivesItem<'this>>::Item>>::Output,
         | >;             fn next(&mut self) -> Option<<Self::Item as
         | GivesItem<'_>>::Item> {         self.iter.next().map(&mut
         | self.mapper)        }       }
         | 
         | Haskell is so much easier.
        
           | insanitybit wrote:
           | I feel you, this is some particularly rough Rust code,
           | combining quite a lot of stuff that I don't think I directly
           | interacted with for my first few years.
           | 
           | You have `for<'a>`, which I believe is probably the most
           | complicated part of the language. That's a Higher Ranked
           | Trait Bound, https://doc.rust-lang.org/nomicon/hrtb.html
           | 
           | It just means "for all possible lifetimes 'a" as opposed to a
           | single specific instance of a lifetime. Honestly, my brain
           | kinda knows when to use it and I've never bothered to push
           | past that point and into "and I understand why it knows when
           | to use it".
           | 
           | You have nested types, which always makes things a bit messy,
           | but then you have the "I have a type, it implements a trait,
           | and I want to refer to the associated type for that trait's
           | implementation for that type".
           | 
           | ie: <MyType as ATraitItImpls>::Associated
           | 
           | Oof. But once you know what it is it's quite clear and
           | explicit. You wouldn't want to do `MyType::Associated`
           | because what if MyType implements multiple traits that have
           | associated values named Associated? Rust prefers explicit.
           | 
           | I actually use this all the time now because it makes
           | refactoring so easy to only refer to types through generic
           | paths. If I change "Associated" I don't have to update
           | anything at all.
           | 
           | When you nest these it gets extra messy, but it's just the
           | same thing. Mapper's second generic parameter is just the
           | associated type of some other thingy. I wonder if some type
           | Aliasing would help, idk.
           | 
           | Then there's `'_`, which I've never bothered to use because I
           | learned rust before it was a thing and so I just don't really
           | care. The irony is that `'_` exists to make things _clearer_
           | - it 's just an annotation that says "there's a lifetime here
           | that we don't actually have to write down, but I'm writing it
           | so that you know what it is and that it's not some other
           | lifetime".
           | 
           | https://dev-doc.rust-lang.org/beta/edition-
           | guide/rust-2018/o...
           | 
           | So yeah, I get it. Rust is _very_ explicit, and when you have
           | a lot of nested stuff like this that explicitness can look a
           | bit overwhelming. But I 'll say that, knowing all of this, I
           | can look at that code and trivially parse it because there's
           | no ambiguity whatsoever.
        
         | kibwen wrote:
         | As was noted at the beginning of the referenced explanation:
         | 
         |  _" I would offer a bit [of a] non-trivial example"_
         | 
         | Which is to say, this comment is deliberately trying to go
         | above and beyond the ELI5 request from the OP. You may find the
         | answer below that one a bit easier to understand:
         | https://old.reddit.com/r/rust/comments/ynvm8a/could_someone_...
        
         | Hamuko wrote:
         | I still haven't fully figured out how lifetimes work.
        
         | Ygg2 wrote:
         | Wait. Why are Monads complicated? Yes the math definition is
         | complicated, but so is math definition of number 1 in set
         | theory
         | 
         | You have a Rust-like enum that contains data like Option or
         | Maybe, you want to abstract over it. There you have a
         | rudimentary Monad.
        
           | jerf wrote:
           | No, monad is more complicated than that. There is a specific
           | method that it has to have, which does not make sense on all
           | types, and can also have more than one sensible definition
           | for a type. "You want to abstract over it" gets you to a
           | "trait", not a "monad".
        
           | _448 wrote:
           | > you want to abstract over it.
           | 
           | What does that mean? Any example you could point to?
        
             | howinteresting wrote:
             | Are you familiar with flat_map? It's the ability to take,
             | for example, an iterator, then map over each element which
             | itself can return an iterator.
             | 
             | Similarly, you have Option::and_then, which takes a
             | callback that takes T and returns an Option<T>. Or
             | Result::and_then.
             | 
             | "Monad" is simply the umbrella term for all flat_maps, and
             | things like it, in existence.
             | 
             | Why would you want to abstract over it? For one, it's a
             | surprisingly common design pattern in programming. You also
             | quickly notice, for example, that filter_map is actually
             | just a special case of flat_map since Option implements
             | Iterator.
             | 
             | You can also study the properties of how such patterns
             | behave. Monads are particularly interesting for a number of
             | reasons. Monads are a kind of "most general" and least
             | constrained form of sequencing, and it turns out that if
             | your large-scale system is monad-like it can have a
             | significant performance impact!
             | 
             | You don't need to have a thing called "Monad" in your type
             | system to do any of the above. Having a thing called
             | "Monad" in your type system does let you write code that
             | abstracts over all flat_maps (and things like it) in
             | existence, which has been used in the Haskell world to
             | build a number of interesting libraries. But "monads" as a
             | concept are useful to everyone in programming.
        
               | howinteresting wrote:
               | By the way, as a simple example of what I mean when I say
               | that monads are the least constrained form of sequencing:
               | flat_map is strictly more general than map. In Rust, if
               | an iterator I implements ExactSizeIterator, then Map<I,
               | F> implements ExactSizeIterator:
               | 
               | https://doc.rust-lang.org/std/iter/struct.Map.html#impl-
               | Exac...
               | 
               | However, there is no such implementation for FlatMap<I,
               | F>:
               | 
               | https://doc.rust-lang.org/std/iter/struct.FlatMap.html
               | 
               | This intuitively makes sense! If a single input can
               | generate zero, one or more than one outputs, then there's
               | no way to know the exact size of it.
               | 
               | The simple map is more constrained -- it has to generate
               | exactly one value -- which means that it can support more
               | operations. The monad-like flat_map is less constrained,
               | and it can do more, which means that it can't support as
               | many operations. There's a catchphrase in the programming
               | language community to describe this sort of thing:
               | "constraints liberate, liberties constrain".
               | 
               | This can make a huge difference in production systems.
               | For example, if you have a distributed system that walks
               | over an execution graph, and nodes in the graph can
               | create new nodes (flat_map/monad-like), the properties of
               | your system are very different from if nodes can't do
               | that.
        
         | withoutboats2 wrote:
         | I hope you don't. To establish my credentials for this comment
         | - GATs were my idea.
         | 
         | The entire point of GATs was to carve out a design space that
         | solved peoples' problems without being so high-minded as monads
         | and HKT and so on. Unfortunately, a lot of people who like
         | monads like to talk about GATs in the same way. But its really
         | as simple as this: when you add an associated type to a trait,
         | you can make that type generic, the same way you can make a
         | type alias generic when its not in a trait. The whole point of
         | specifying GATs and not a more general "HKT" is that it's an
         | obvious extension of the existing language.
         | 
         | There are lots of useful traits that you can't define if you
         | can't make an associated type generic, so people are excited to
         | have this feature.
         | 
         | It turned out that implementing this in rustc took a very long
         | time, because refactoring the typechecker of rustc to support
         | this was challenging. But that doesn't mean the feature itself
         | is complicated or difficult to use. The whole point of GATs is
         | that it shouldn't really feel like a feature once it's done:
         | naturally, if you are writing a trait with a method that
         | returns `Self::Foo`, but you need it to be `Self::Foo<T>` or
         | `Self::Foo<'a>`, you can just do that, rather than the compiler
         | telling you that it's not supported.
        
           | flippinburgers wrote:
           | Not trying to make you feel like you have failed, but
           | whenever I encounter language features on this level - aka
           | not comprehensible for a mortal like myself - I do tend to
           | want to shamefully turn away from this profession entirely.
           | The good news is that I am most likely a total dunce.
           | 
           | I wish I could become reasonable with rust because I do like
           | the concepts that I think I grasp.
        
             | root_axis wrote:
             | GATs seem easily understandable to me and I'm a "mere
             | mortal". Like most things in software, the utility becomes
             | clear when you actually have a practical purpose for its
             | use. Don't get ahead of yourself.
        
             | agentultra wrote:
             | There are other people, who are also mere mortals, that can
             | understand them. I think you could understand them if you
             | tried. You're not a dunce!
        
             | stouset wrote:
             | Traits can have associated types.                   trait
             | Foo {             type Bar;         }
             | 
             | Until now, those types had to be concrete, as in the above
             | example. You can implement Foo with `Bar = u32` and `Bar =
             | String` and even `Bar = Vec<bool>`.                   trait
             | Foo {             type Bar<T>;         }
             | 
             | That `Bar` type above can now be generic over some other
             | type `T` so you can implement it for `Bar<T> = Vec<T>`,
             | `Bar<T> = Result<T>`, `Bar<T> = Option<T>`, and any other
             | generic type. That's it. That's the whole thing. Use it if
             | it's useful to you.
             | 
             | If you've never needed to write a trait that incorporated
             | an associated type (.AT) which needed to be generic (G..),
             | then this won't mean much to you and that's fine. But you
             | might be using libraries that could be somewhat more
             | ergonomic if they were able to use this feature. The good
             | news is that now that it's been released to stable, a
             | future version of that library can be written more
             | ergonomically.
        
             | sowbug wrote:
             | When I find a language feature I don't understand even
             | after reading what's out there, I ignore it. Then I plod
             | along with my own code. Eventually I'll notice I've been
             | writing the same boilerplate code again and again, and I'll
             | finally see the use for the feature. At that point it's
             | less a matter of "understanding" in a grand intellectual
             | sense, and more just fitting my needs to the syntax. After
             | a few such instances, I know the feature well enough to
             | unblock someone else who doesn't get it. That's a good
             | practical test of understanding.
             | 
             | TL;DR: don't worry about it. When you need it, you'll learn
             | it.
        
               | nicoburns wrote:
               | Indeed, when I was very first learning to code I did this
               | with arrays! Couldn't understand the advantage of writing
               | foo[0] and foo[1] over foo0 and foo1. And in some cases
               | there isn't really one. But of course arrays are very
               | useful in general.
        
             | withoutboats2 wrote:
             | You're begging the question when you say that GATs are "not
             | comprehensible for a mortal like myself." My entire point
             | is that they were designed not to be incomprehensible.
             | 
             | What is "not comprehensible for a mortal" in this situation
             | is _why_ adding a generic to an associated type is a long-
             | anticipated feature that took years of development to
             | support. This is related to the  "incomprehensible"
             | discussion that occurs around this feature, talking about
             | type functions and higher kindedness and all of these
             | mathematical formalisms. But this discussion is just
             | happening among practitioners and enthusiasts schooled in a
             | certain jargon and area of arcane knowledge. It doesn't
             | mean you need to understand any of this to just use the
             | feature.
             | 
             | It's like people saying the internet is "not comprehensible
             | for a mortal like myself" because most people do not have
             | the background to properly understand IP/TCP/HTTP and how
             | things like this undergirdle the technology that they use.
             | And yet they use the internet just fine. If the design has
             | succeeded, you should not need to even hear words like
             | "higher kindedness" before you can make your associated
             | type generic.
             | 
             | Possibly the system design is imperfect and the
             | abstractions leaks and you as a user have to learn more
             | than one would hope in order to successfully use the tool
             | to accomplish your goals. Rust doesn't have a perfect track
             | record here.
        
               | Twirrim wrote:
               | > My entire point is that they were designed not to be
               | incomprehensible.
               | 
               | It's frustrating that the documentation / announcement
               | always seems to go for the most complicated way to
               | explain just about everything, in what I assume is an
               | attempt to ensure it's the most comprehensive
               | explanation, in the smallest number of lines. It's fine
               | to provide different levels of explanation/examples.
               | 
               | Numerous times I read stuff and think "Nope. Absolutely
               | no idea why I'd use that, and I'm not entirely sure I
               | have even half a clue what it's trying to do" until much
               | much later when I see more simplistic practical
               | applications of it and comprehension slowly dawns about
               | what was being explained.
        
           | avgcorrection wrote:
           | > The entire point of GATs was to carve out a design space
           | that solved peoples' problems without being so high-minded as
           | monads and HKT and so on.
           | 
           | High-minded or snobbish or whatever other deragotory words
           | that one wants to use: the benefit of something like HKT is
           | that it encompasses one thing that Rust now currently ends up
           | catching up to by implementing dozens of "X with Y" ("generic
           | const in associated lifetimes positions"... to use a made up
           | feature) that are just about lifting limitations, and that
           | end up sounding "complicated" and "featureful" (kitchen sink
           | accusations) to anyone who isn't knee deep in building an
           | async runtime library or whatever.
           | 
           | Meanwhile a Haskell programmer might go years and never think
           | about HKT as a feature. It's just "kinds" without artificial-
           | looking limitations.
        
             | withoutboats2 wrote:
             | Before writing a completely asinine comment like this, you
             | might consider that I, having been paid real American
             | dollars to design this language, might know more than you
             | about the relationship between GATs and HKTs and the design
             | of Rust as a whole. Your comment evinces a total ignorance
             | of the type theoretical issues that actually informed our
             | decision on this issue.
             | 
             | Fortunately, Niko Matsakis blogged about our design
             | discussions at the time. You can read more here and in the
             | linked predecessor posts (at the time we were calling GATs
             | "ATCs") https://smallcultfollowing.com/babysteps/blog/2016/
             | 11/04/ass...
        
               | avgcorrection wrote:
               | Yes, your attitude (shall we say) was apparent from the
               | start.
        
         | rufusroflpunch wrote:
         | Same here. I presume that one day I will have to write
         | something using a GAT, and then the understanding will click
         | with me, even if I don't speak the terminology.
        
           | kibwen wrote:
           | Both generics and traits are mostly for library authors
           | trying to make expressive APIs. If you're not creating
           | libraries but instead consuming libraries, then it may be
           | natural for you to never need to reach for this feature.
        
           | secondcoming wrote:
           | Many C++ developers have walked that path, never to be seen
           | again.
        
       | cryptonector wrote:
       | That thread links to https://www.fpcomplete.com/blog/monads-gats-
       | nightly-rust/ which is very nice. Bottom-line: Rust GATs are not
       | enough to get Haskell style monadic programming yet.
        
       | leetrout wrote:
       | GAT is Generic Associated Types
       | 
       | https://blog.rust-lang.org/2022/10/28/gats-stabilization.htm...
        
         | [deleted]
        
         | agentbellnorm wrote:
         | I've seen this concept being talked about for a couple of days
         | now. I even read the Reddit thread this morning.
         | 
         | But until now I didn't know what the letters stand for. Thank
         | you.
         | 
         | Death to abbreviations!
        
           | tialaramex wrote:
           | It's no different in everyday life. This was an appropriate
           | title for the Rust thread, but is too terse for Hacker News.
           | 
           | If I say to my mother "Hannah told me she'd be away for
           | Christmas" we both understand who is meant, my sister. If I
           | say that to my friend Dave it's unclear. Which Hannah? We
           | have a mutual friend called Hannah, I have a sister named
           | Hannah, or maybe I mean the Hannah he works with. So I should
           | be more specific in that context.
           | 
           | Being obliged to be fully specific all the time is no problem
           | for a machine but it's tiresome for humans, so no, I do not
           | agree with "death to abbreviations". Maybe HN rules should
           | encourage people to expand abbreviations when citing material
           | from elsewhere that could be unfamiliar to most readers. As
           | it is, hey, free Internet points for whoever first expands
           | the abbreviation in a comment.
        
       | wisnoskij wrote:
       | I feel like they are making languages too complicated. Most
       | projects should just be garbage collected, most don't need to
       | deal with pointers or anything advanced. We need variables,
       | functions, conditionals, loops, exception handling, struts, and a
       | library with a bunch of standard utilities (including simple I/O
       | and string managment). Everything else should be optional and
       | kept out of sight unless you are looking for it IMHO.
       | 
       | I am probably just old and set in my ways, but I have been
       | learning rust over the last few days, and it is really syntax
       | dense. Their is no reason it needs to be this complex. For
       | example I think "String" and "&static' str" are different types,
       | and no one explains what "&static'" is but I dont think it is
       | changeable. It is not like ' or static means anything and can
       | change, it is just basically random syntax you have to memorize.
       | OK, I am sure it means something like a reference to a static
       | character or something, if you dont even think it is a good idea
       | to inform people what it means, dont implement the syntax like
       | that.
        
         | Smaug123 wrote:
         | `&` and `'` do mean something: `&` indicates something that's
         | borrowed, and `'` indicates a lifetime. (`static` is a
         | specifically blessed identifier given to a certain lifetime.)
         | This is explained in https://doc.rust-
         | lang.org/book/ch10-03-lifetime-syntax.html#..., and the static
         | lifetime is explained in https://doc.rust-
         | lang.org/book/ch10-03-lifetime-syntax.html#... .
        
           | wisnoskij wrote:
           | It looks like I will just have to bite the bullet and
           | actually read the rust book back to front. I find static
           | particularly confusing because of the seemingly contradictory
           | meanings that are all jumbled up in my head. The English
           | meaning of immutable, the Java meaning of instance agnostic,
           | and this Rust meaning of immortal lifetime.
        
             | cercatrova wrote:
             | > _It looks like I will just have to bite the bullet and
             | actually read the rust book back to front._
             | 
             | I mean, there you have it, right? Not a diss on you
             | specifically, but I've often noticed that some people will
             | complain about something, but then it comes out that they
             | actually haven't learned it deeply enough, they're just
             | going on what they either learned so far, learned that
             | something in a haphazard way, or didn't learn it at all.
             | 
             | I used to do the same, so no shame, starting off writing
             | programs as practice and googling things along the way
             | because everyone on any programming thread says "the best
             | way to learn is by building, not doing tutorials" but
             | that's not necessarily true. Sure, I have learned a lot by
             | building, but I've often spent longer simply running around
             | googling and learning bits and pieces than the time I'd
             | spend actually learning something top to bottom, front to
             | back, from scratch. So I actually like and see the value of
             | tutorials now.
             | 
             | Once I read The Rust Book front to back like you said, I
             | understood a lot more of the language than my previous
             | attempts to learn it a few years ago. Doing Rustlings at
             | the same time was great too.
        
           | kibwen wrote:
           | It's also possible to have `&str`s with smaller lifetimes
           | than `'static`, in which case you will have a different
           | lifetime specifier there.
        
             | wisnoskij wrote:
             | Ah, so this probably tells the compiler where to insert the
             | free? If I gave it a local lifetime it would free the
             | memory on function return for example, even if I tried to
             | return a reference to it? Basically Rust just forces you to
             | code in the memory freeing at the definition state of
             | variable creation?
        
               | jhomedall wrote:
               | Not quite. Lifetime annotations prevent you from
               | accidentally using references after the value they refer
               | to has been freed. They _track_ how long things will
               | live, instead of _defining_ how long they will live.
               | 
               | Basically they turn use-after-free errors into compile
               | errors.
               | 
               | (I'm using 'free' here to mean cleaned up in general.
               | Lifetimes can track stack values.)
        
         | avgcorrection wrote:
         | > I feel like they are making languages too complicated.
         | 
         | Perhaps. But this topic is not a natural springboard for _that_
         | topic since GATs are about lifting a current restriction in
         | Rust. So you have the same features as yesterday, but with one
         | less (arbitrary-looking) restriction.
         | 
         | Maybe it makes the language more complicated to implement (?)
         | but it doesn't make it more complicated for the language user.
        
         | alkonaut wrote:
         | Most things don't _need_ to be written in Rust. But C /C++
         | exist for a reason, and Rust for effectively the same reasons.
         | 
         | Rust looks complicated but it's a complicated problem that's
         | why the solution is complicated.
         | 
         | If you want zero cost (e.g no GC!) abstractions and some
         | compile time guarantees about concurrency and memory safety
         | well then you have a complex problem requiring a complex
         | solution.
         | 
         | &str is a "slice" of a string, it doesn't own any characters it
         | just points to someone else's characters. String is a heap
         | allocated string so it has its own characters.
         | 
         | The ' indicates a lifetime and "static" just means "lives
         | forever".
         | 
         | In the end, lifetimes and multiple string types is the price we
         | pay for performance and safety. There is no world where Rust
         | just had ergonomic strings like Java _and_ safety /performance
         | unaffected.
        
         | jamincan wrote:
         | `&'static str` is an `&str` (string slice) with a `'static`
         | lifetime (ie. lasting the duration of the program). It is most
         | commonly encountered with string literals.
         | 
         | In some languages, it is appropriate to gloss over the
         | differences between `String`, `&str`, and `&'static str`, but
         | in Rust, all of those details are meaningful and important. If
         | distinguishing between them isn't important to you, there are
         | other languages that probably better reflect your needs.
        
         | insanitybit wrote:
         | Garbage Collection is extremely complicated. It is a whole
         | other program running to manage your memory using extremely
         | complex, hyper optimized algorithms that have lots of best and
         | worst case scenarios depending on which one you use.
         | 
         | GC implementations can very easily make code confusing.
         | Consider `finalize` in Java - a destructor that gets called at
         | a completely indeterminate period of time, making it a very
         | confusing tool for resource management.
         | 
         | I find Rust trivial by comparison. But isn't that interesting,
         | how we all view simplicity so differently?
        
           | wisnoskij wrote:
           | I did want to say Rust was complicated, I meant to say it was
           | complicated to learn. You never need to learn the Java syntax
           | finalize, it is invisible until you have a problem where that
           | is the solution. Similar to Java templates or overloading
           | they dont exist until they are the solution to a problem you
           | are having.
           | 
           | My critique is that Rust appears (and I am probably wrong) to
           | have a problem where syntax for complicated language features
           | appear in the simplest code that even a day 1 grade 8 student
           | needs to memorize, even if they wont understand it until the
           | second year of university.
        
             | insanitybit wrote:
             | > syntax for complicated language features appear in the
             | simplest code
             | 
             | IDK, I hear you but I'm of two minds.
             | 
             | 1. I think a lot of this is familiarity.
             | 
             | `public static void main(string[] args)`
             | 
             | That's a lot of stuff that I don't actually really need to
             | care about but is in every single Java program. It even has
             | 'static', which is honestly something I found very very
             | confusing when I first learned Java (technically my first
             | language since I took a course at a local community
             | college).
             | 
             | Compare that to c, `int main()`
             | 
             | That's a lot less in my face for sure.
             | 
             | But if I were used to Python? I'd just write my code
             | directly in the file and it would execute from top to
             | bottom. WTF is this `int` and `main` ???
             | 
             | In Java you have int and Integer, and Integer can be this
             | "null" thing? I just wanted to add 2 and 2 wtf??
             | 
             | Generics? `class Foo<T>` ? WTF is `<>`?
             | 
             | The point is that what you have to learn when you first use
             | the language is really going to depend on what you learned
             | before it. I don't consider that complexity, it's just new.
             | 
             | The thing is that there is no language that you're likely
             | to have used before that will prepare you for some bits of
             | Rust.
             | 
             | 2. Rust could be easier. It would be cool if there were a
             | way for beginners to think less about the differences
             | between String and &'a str and &'static str. At the same
             | time, abstracting over those would have its own downsides -
             | if you learn about strings in Rust in a way that abstracts
             | that all out, will you understand the underlying components
             | and how the abstraction works? It's tough.
             | 
             | I think the reality is that Rust is just not going to be as
             | easy to learn as the same exact language but where
             | lifetimes are managed at runtime. It probably shouldn't
             | even try too hard to be that easy, because being that easy
             | has costs, and there's a limit to how much you should
             | optimize for newcomers. Rust has balanced things pretty
             | well so far there - 2018 brought a lot of ergonomics wins
             | that I frankly don't care for very much or even just forget
             | about, but it helped tons of people pick the language up.
             | 
             | All this is to say, I think there's truth to what you're
             | saying but I also think you may be attributing _some_
             | issues to complexity where I believe it 's an issue of
             | familiarity.
             | 
             | I appreciate your opinion on this though, I do always find
             | it so interesting to hear about how others view complexity
             | and programming languages (when they're constructive about
             | it).
        
               | cercatrova wrote:
               | Highly agree with #1. I think it really does come down to
               | familiarity. I've learned all the languages you listed
               | and every time there was something that was confusing
               | only to then be cleared up the more I use it.
        
         | GolDDranks wrote:
         | I think that if you feel like that, you might be looking into
         | the wrong language. Go sounds like an exact match of what you
         | want.
        
         | timeon wrote:
         | Explicit knowledge if variable is copy/clone or reference is
         | useful also in GC context if the variable is mutable.
        
         | celeritascelery wrote:
         | I will agree with you that most languages should just be gc.
         | Rust is niche; targeted at applications where you need control
         | over memory and layout. It is not a good general purpose
         | language.
         | 
         | That being said, most of things you pointed out as "no reason
         | to be this complex" do have good reasons. String and &str could
         | be renamed to StringBuf and StringSlice. The String type lets
         | you manipulate the string value, but it is a bigger type than a
         | slice (&str). &'static just indicates that the string slice
         | will live for the entire program. As you continue to learn
         | Rust, more of these distinctions will make sense to you.
         | 
         | GAT's themselves are actually a great example of a feature that
         | adds capabilities without really adding complexity. They feel
         | like they should have always been there, and I feel like anyone
         | who has worked with the language for a while has implicitly
         | tried to do this and was surprised it didn't work.
        
           | oxff wrote:
           | > without really adding complexity
           | 
           | Pretty sure many relevant contributors have said it is the
           | most complex language feature since async ..
        
             | celeritascelery wrote:
             | certainly complex from a implementation standpoint, and
             | some of the rough edges can be a footgun around lifetimes
             | until those are ironed out. But being able to add generic
             | arguments to associated types is just a natural extension
             | of the language from a user standpoint. You could already
             | add generic arguments to functions, structs, type aliases,
             | enums, etc. This just allows it in one more place.
        
           | kibwen wrote:
           | Agreed, people should not assume that the design of Rust is
           | some sort of statement about how higher-level languages
           | should operate. It's a very targeted design with specific
           | goals. For instance, the fact that Rust eschews exceptions
           | should not be interpreted as "exceptions are bad", but rather
           | "we don't think exceptions are a fit for what we're trying to
           | achieve in this one specific domain".
        
           | Smaug123 wrote:
           | > I feel like anyone who has worked with the language for a
           | while has implicitly tried to do this and was surprised it
           | didn't work.
           | 
           | Just to lend some extra evidence to your assertion, I hit the
           | lack of GATs within two days of starting to learn Rust. In
           | the natural course of trying to solve a problem, I tried to
           | write `LendingInterator`, and discovered that I could not.
        
           | return_to_monke wrote:
           | String was indeed once named StrBuf: https://github.com/rust-
           | lang/rfcs/pull/60
        
       ___________________________________________________________________
       (page generated 2022-11-07 23:02 UTC)