[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)