https://old.reddit.com/r/rust/comments/ynvm8a/could_someone_explain_the_gats_like_i_was_5/ jump to content my subreddits edit subscriptions * popular * -all * -random * -users | * AskReddit * -funny * -gaming * -pics * -worldnews * -movies * -mildlyinteresting * -news * -todayilearned * -explainlikeimfive * -Music * -Jokes * -videos * -aww * -dataisbeautiful * -LifeProTips * -science * -OldSchoolCool * -TwoXChromosomes * -books * -nottheonion * -IAmA * -askscience * -space * -Showerthoughts * -tifu * -gifs * -Art * -Futurology * -sports * -DIY * -food * -nosleep * -history * -InternetIsBeautiful * -Documentaries * -EarthPorn * -WritingPrompts * -gadgets * -UpliftingNews * -philosophy * -GetMotivated * -photoshopbattles * -creepy * -announcements * -listentothis * -blog more >> rust rust * comments * other discussions (3) Want to join? Log in or sign up in seconds.| * English [ ][] [ ]limit my search to r/rust use the following search parameters to narrow your results: subreddit:subreddit find submissions in "subreddit" author:username find submissions by "username" site:example.com find submissions from "example.com" url:text search for "text" in url selftext:text search for "text" in self post contents self:yes (or self:no) include (or exclude) self posts nsfw:yes (or nsfw:no) include (or exclude) results marked as NSFW e.g. subreddit:aww site:imgur.com dog see the search faq for details. advanced search: by author, subreddit... this post was submitted on 06 Nov 2022 245 points (97% upvoted) shortlink: [https://redd.it/ynvm] [ ][ ] [ ]remember mereset password login ATF Submit a new link Submit a new text post Get an ad-free experience with special benefits, and directly support Reddit. get reddit premium rust joinleave205,916 readers 1,244 users here now Please read The Rust Community Code of Conduct --------------------------------------------------------------------- The Rust Programming Language A place for all things related to the Rust programming language--an open-source systems language that emphasizes performance, reliability, and productivity. --------------------------------------------------------------------- Rules Observe our code of conduct * Strive to treat others with respect, patience, kindness, and empathy. * We observe the Rust Project Code of Conduct. * Details Submissions must be on-topic * Posts must reference Rust or relate to things using Rust. For content that does not, use a text post to explain its relevance. * Post titles should include useful context. * For Rust questions, use the stickied Q&A thread. * Arts-and-crafts posts are permitted on weekends. * No meta posts; message the mods instead. * Details Constructive criticism only * Criticism is encouraged, though it must be constructive, useful and actionable. * If criticizing a project on GitHub, you may not link directly to the project's issue tracker. Please create a read-only mirror and link that instead. * Details Keep things in perspective * A programming language is rarely worth getting worked up over. * No zealotry or fanaticism. * Be charitable in intent. Err on the side of giving others the benefit of the doubt. * Details No endless relitigation * Avoid re-treading topics that have been long-settled or utterly exhausted. * Avoid bikeshedding. * This is not an official Rust forum, and cannot fulfill feature requests. Use the official venues for that. * Details No low-effort content * No memes or image macros. * Details --------------------------------------------------------------------- Useful Links Latest Megathreads * Announcing 1.65.0 * Got an Easy Question? * What's Everyone Working On? * Who's Hiring? Jobs Thread for 1.65.0 * This Week in Rust 467 * We'll do our best to keep these links up to date, but if we fall behind please don't hesitate to shoot us a modmail. Official Resources * Official Website * Official Blog * This Week In Rust * Installers * Source Code * Bug Tracker Learn Rust * The Rust E-Book * Stdlib API Reference * Rust By Example * Rustlings * Online Playground Discussion Platforms * Official Users Forum * Official Discord * Community Discord * Mozilla Matrix Chat * Stack Overflow Chat a community for 11 years BTF MODERATORS * message the mods * Moderator list hidden. Learn More discussions in r/rust <> X 125 * 20 comments Does C benefit from LLVM changes intended for Rust? 26 * 2 comments This Month in Rust OSDev: October 2022 179 * 11 comments rust-analyzer changelog #154 102 * 11 comments IntelliJ Rust Changelog #182 35 * 44 comments Experienced Rust developers, do you feel powerful? 16 Scaling PostgresML to 1 Million Requests per Second 10 * 4 comments Hurl 1.8.0, text based integration tests for REST APIs and web sites 38 * 21 comments benchmarking imperative vs functionnal programming 21 * 10 comments Looking for people interested in implementation of Pixar's USD format 78 * 5 comments Played around with nannou this weekend Welcome to Reddit, the front page of the internet. Become a Redditor and join one of thousands of communities. x 244 245 246 Could someone explain the GATs like I was 5? (self.rust) submitted 1 day ago by LyonSyonII I've read the examples and documentation, but I'm having a difficult time thinking on practical applicacions. What can be done now that couldn't before? * 75 comments * share * save * hide * report all 75 comments sorted by: best topnewcontroversialoldrandomq&alive (beta) [ ] Want to add to the discussion? Post a comment! Create an account [-]Zde-G 278 points279 points280 points 1 day ago[klvxk1wggf] [5izbv4fn0m][71v56o5a5v] (47 children) Because everyone likes to use that LendingIterator example I would offer a bit non-trivial example, maybe it would help to show you that GATs lie in the middle between normal generics and higher kinded types which you may have in C++ or Haskell. The first observation is that many functions make sense and are, actually, implemented for various containers. E.g. map is implemented for Option and map is implemented for Result but they can not be implemented in common trait before GATs because they need to abstract over type of generic: one of them returns Option and the other one returns Result. What you really want is to use Option or Result as type, generic argument... but you can't. Name of generic type in not a type in Rust! GATs alllow you to express what you want in a slightly roundabout way. You can declare trait Mappable like this: trait Mappable { type Item; type Result; fn map U>(self, f: P) -> Self::Result; } And then implement it like this: impl Mappable for Option { type Item = T; type Result = Option; fn map U>(self, f: P) -> Option { self.map(f) } } impl Mappable for Result { type Item = T; type Result = Result; fn map U>(self, f: P) -> Result { self.map(f) } } And then, finally, you get the ability to implement function which is generic over generic like this: fn zero_to_42>(c: C) -> ::Result { c.map(|x| if x == 0 { 42 } else { x }) } This functions kind of "unpacks then type", works with it's internals and then "packs everything correctly". And you can expand it to work with vector, too It's similar to how you can deal with functions which are generic-over-pointer-types, but kinda more expansive, it should, hopefully, be obvious that GATs cover many more cases than just simple Arc or Rc. GATs are very useful even without lifetime GATs (in fact lifetime gats are pretty complicated right now). * permalink * embed * save * report * give award * reply [-]IceSentry 71 points72 points73 points 1 day ago* (28 children) This is personally the easiest to understand example I've seen of GATs. * permalink * embed * save * parent * report * give award * reply [-]Zde-G 27 points28 points29 points 23 hours ago* (27 children) Also that this same example also easily shows why designing higher-kind traits would be a very painful exercise in Rust. Note how I've used FnMut in the example above. I did that to make it possible to implement Mappable for vector. But both Option and Result only need FnOnce! So... what's the best to use? FnMut and support vector or FnOnce and only support single-element containers? Or maybe both? Bikeshedding of untold proportions! APIs in languages like C++ are easier to design because metaprogramming language there is dynamic (but you pay high price with crazy and almost incomprehensive error messages... there are no free lunch). I wanted to add that to original explanation but feared it would detract from the main point. * permalink * embed * save * parent * report * give award * reply [-]1vader 2 points3 points4 points 13 hours ago (2 children) FnNum Really confused me for a second here but I guess that's supposed to be FnMut? * permalink * embed * save * parent * report * give award * reply [-]Zde-G 4 points5 points6 points 12 hours ago (1 child) Yeah, FnMut vs FnOnce. Sorry for a typo. * permalink * embed * save * parent * report * give award * reply continue this thread [-]epicwisdom 1 point2 points3 points 15 hours ago (6 children) I think this is a more fundamental limitation of Rust, right? Types in Rust can't generally be parametric over mutability; at best there are wrapper types which automatically handle conversions. I don't foresee this ever being resolved in Rust since it'd be quite a huge change to introduce this particular form of parametricity. * permalink * embed * save * parent * report * give award * reply [-]idevthereforeiam 9 points10 points11 points 15 hours ago (1 child) I don't think mut is mentioned explicitly, but some people seem to be exploring this kind of idea. https://blog.rust-lang.org/inside-rust/2022/07/27/ keyword-generics.html * permalink * embed * save * parent * report * give award * reply continue this thread [-]Zde-G 4 points5 points6 points 12 hours ago (3 children) It's not about mutability, it's logic restriction. Difference between FnMut and FnOnce is not in mutability, but in the fact that FnOnce can be called only once (as the name implies). Option and Result contain one element thus they only need FnOnce, while vector may contain many elements thus FnOnce wouldn't work. C++ solution is: we don't care about that during compilation of templates, but if you would try to use FnOnce with vector, you would get 200 limes of errors. Rust guarantees that during instantiation there would be no such problems, but that means that you can not leave the decision for later. You have to decide between "FnOnce and no vector support" vs "FnMut and vector support" upfront. * permalink * embed * save * parent * report * give award * reply continue this thread [-]Ford_O 1 point2 points3 points 14 hours ago (6 children) Can't you use GATs to be parametric over Fn? * permalink * embed * save * parent * report * give award * reply [-]Zde-G 2 points3 points4 points 12 hours ago* (5 children) You can not mix FnOnce and vector. That's fundamental. This comes from the definition of problem: vector have many elements (at least we don't know before runtime) thus we need FnMut and FnOnce wouldn't work. Rust doesn't give you means of enabling complex relationships between arguments of generics. You can only ever support full carthesian product for arguments of generics. And that's now what we have here: three out of four combinations are allowed. P.S. And yes, they could have used GATs to make things generic over types of Fns but since they needed them years ago they just did a compiler magic: you can use Fn in place of FnMut and FnMut in place of FnOnce. That's not done with GATs, that's just compiler knows that it can do that. But in users types yes, you can use GATs to do similar things. * permalink * embed * save * parent * report * give award * reply continue this thread [-]Sharlinator 0 points1 point2 points 10 hours ago (2 children) And if you want to support eg. HashSet then the U in type Result will have to impl Eq + Hash... but you can't just add the constraints to the U of the specific HashSet [DEL:functor instance:DEL] Mappable impl because that's not compatible with the trait interface. And there's no way to say impl Mappable for HashSet where U: Eq + Hash because there's no U in the trait itself! * permalink * embed * save * parent * report * give award * reply [-]Zde-G 1 point2 points3 points 9 hours ago (1 child) The big problem is not that you can not add Eq + Hash restriction. You can. What you can not do is to place conditional restrictions in the impl block. This places you in ridiculous place: either your Mappable would work with Option and HashMap but would only be able to process keys in hash or values (not full tuples like one may expect), or your Mappable would work naturally with HashMap but then it may not accept closure which accepts i32 for Option. You probably may get away with passing i32 as (i32,()) but that looks supremely ugly. That's why my gut feeling is that GATs turned Rust in turing tarpit for the metaprogramming: everything is possible, yet nothing of interest is easy. We really need Chalk to resolve that mess, I'm afraid. Because before GATs that simple model where "generic arguments of a type are input arguments while associated type in trais are output arguments" looked somewhat sensible, but with GATs it really falls apart. With GATs you really want to specify restrictions on the implementation level, but then resolver must be able to resolve constructs which it never was designed to resolve. * permalink * embed * save * parent * report * give award * reply continue this thread [-]pjmlp 0 points1 point2 points 10 hours ago (3 children) but you pay high price with crazy and almost incomprehensive error messages... there are no free lunch Not since C++17, where you can use a mix of constexpr and static_asserts to give more friendly error messages, or with concepts in C++20. I really find the GATs gimmicks much harder to understand. * permalink * embed * save * parent * report * give award * reply [-]gmnash 1 point2 points3 points 8 hours ago* (1 child) With C++ concepts, the error message is friendly, but as it's similar to generic constraints, would it also not fail to compile? * permalink * embed * save * parent * report * give award * reply continue this thread [-]Ruskyrust 1 point2 points3 points 6 hours ago* (0 children) C++ has had this same functionality (in the duck-typed context of templates) for a very long time. Here's the example ported to C++11 ( Godbolt link): template struct Option { // Option implementation... // The "GAT" itself: template using MapResult = Option; template Option map(F f) { // Apply f to the contents of `this` } }; template typename T::template MapResult zero_to_42(T t) { return t.template map([](int x) { return x == 0 ? 42 : 0; }); } You can even sort of write a concept for this: template concept Mappable = requires { typename T::template MapResult; }; GATs are not some sort gimmick alternative to concepts. They are nothing more than member templates, and the new functionality here is simply the ability to describe them as trait members. * permalink * embed * save * parent * report * give award * reply [-]tel 0 points1 point2 points 7 hours ago (2 children) I'm sure this has been explored, but what about an impl trait Mappable { type Item; type Result; fn map U>(self, f: P) -> Self::Result; } trait MappableOnce: Mappable { fn map_once U>(self, f: P) -> Self::Result; } We don't get universal, overlapping generality and we're introducing a new name map_once which is unneeded on the type-specific impls, but it's kind of acceptable to users. If I know I'm going to need to map a FnOnce then I can ask for only types which impl MappableOnce. If I only need FnMut then I can allow for a more general set of applicable types. * permalink * embed * save * parent * report * give award * reply [-]Zde-G 1 point2 points3 points 6 hours ago (1 child) That's what people who are interested in these designs have to explore. The issue here is that you get dozens (hundreds?) of different traits and it's unclear how many of these are usable. That's why I don't think we would get many traits added to std in the near feature. Maybe few which would make sense to add to the language itself. E.g. currently for requires Iterator and can not be used with LendingIterator which is quite inconvenient. That change can not be made in external crates thus it's one of the first candidates for stabilization. But most possible traits have significant issues like what we are discussing here. * permalink * embed * save * parent * report * give award * reply continue this thread [-]LqcyTjo8xBLLXA36cr 8 points9 points10 points 20 hours ago (3 children) This is a great answer. The natural next question: what can't you do with GATs alone that you can do in Haskell? Is it just the * -> * stuff or is there more "normal" stuff as well? * permalink * embed * save * parent * report * give award * reply [-]Sharlinator 2 points3 points4 points 10 hours ago* (0 children) Haskell's Functor typeclass requires that the input and output of map are the same functor, just parameterized differently. GP's Mappable allows impls to pick any Result whatsoever as long as it's "kind * -> *". You can add a trait bound that it must at least be Mappable, but there's no way to enforce it must be the same Mappable as Self. * permalink * embed * save * parent * report * give award * reply [-]Zde-G 0 points1 point2 points 11 hours ago* (1 child) I haven't explored them too deeply, just wanted to show what pieces from Haskell weren't available before GATs. From what I'm seeing GATs and HKTs in Haskell are equally capable by themselves, but Haskell offers many additional features which are not available in Rust. The guy which played with monads in Rust tried to create transform and failed. I'm not sure if that's generic limitation of GATs (highly unlikely) or just current limitation of Rust typechecker (quite likely). P.S. The biggest issue with GATs is the fact that they still don't make Option or Result into 2nd kind types. They, instead, offer kinda roundabout way to express that. I'm not sure if I can say whether they are as powerful as Haskell's 2nd kind types or not for real. * permalink * embed * save * parent * report * give award * reply [-]suggested-user-name 0 points1 point2 points 6 hours ago (0 children) Of those that the author failed to get working, I've gotten join to work, didn't have any luck with traverse though fn join(outer: MOuter) -> MOuter::Wrapped where MOuter: Monad = MInner>, MInner: Monad, { outer.bind::(|inner| inner) } * permalink * embed * save * parent * report * give award * reply [-]extensivelyrusted 2 points3 points4 points 13 hours ago (1 child) Why is 'C as Mappable' necessary in the response sig for 'zero_to_42'? * permalink * embed * save * parent * report * give award * reply [-]Zde-G 1 point2 points3 points 12 hours ago (0 children) It's not needed. Just my habit to use it since something Rust couldn't deduce. * permalink * embed * save * parent * report * give award * reply [-]DidiBear 6 points7 points8 points 18 hours ago (1 child) Thanks for calling it Mappable and not Functor ! * permalink * embed * save * parent * report * give award * reply [+]avanov 2 points3 points4 points 9 hours ago (0 children) the implementation isn't a valid functor, as explained above * permalink * embed * save * parent * report * give award * reply [-]boris_b1 1 point2 points3 points 6 hours ago (2 children) Since when C++ is having HKT? * permalink * embed * save * parent * report * give award * reply [-]Zde-G 1 point2 points3 points 5 hours ago (1 child) As usual, you have to keep in mind that while C++ uses static typing system for types it uses dynamic typing system for metatypes. That's why you don't need to define types for metatypes there. But you can pass template name as parameter even in S++98. I don't know history of C++ compilers well enough to tell when they were actually implemented in real compilers. * permalink * embed * save * parent * report * give award * reply [-]boris_b1 0 points1 point2 points 5 hours ago (0 children) Thanks * permalink * embed * save * parent * report * give award * reply [-]Apanatshka 0 points1 point2 points 9 hours ago (1 child) Maybe this is just my not so firm grip on the difference between generics and associated types speaking, but since all your example implementations of the trait are generic over the Item, can't you just drop that associated type? Then you can pull the generic U from the associated type to the trait level, and you get this, which still works. * permalink * embed * save * parent * report * give award * reply [-]Zde-G 1 point2 points3 points 8 hours ago (0 children) Well... you are talking about different question: whether we even need GATs at all. That's tough. Yes, if you always map from some type to the exact same type GATs are not needed. But how would you be able to replicate that example with your approach: fn zero_to_42, Item: Into, C: Mappable>(c: C) -> C::Result { c.map(|x: Item| { let x: Out = x.into(); if x == 0.into() { 42.into() } else { x }}) } fn main() { let o0: Option = Some(0); let o43: Option = Some(43); let o0: Option = zero_to_42(o0); let o43: Option = zero_to_42(o43); println!("{:?}", o0); println!("{:?}", o43); let r0: Result = Ok(0); let r43: Result = Ok(43); let r0: Result = zero_to_42(r0); let r43: Result = zero_to_42(r43); println!("{:?}", r0); println!("{:?}", r43); let v: Vec = vec!{0, 1, 2}; let v: Vec = zero_to_42(v); println!("{:?}", v); } You would need to pull that output type on the level of trait argument. But then you wouldn't have Mappable which can go from T to any U. You would have trait which would fix both T and U. And then everything becomes more complicated. I don't know where you reach the point which can not be expressed without GATs at all. Maybe many modes is that point, maybe GATs are just simplification which doesn't enable anything really new, I'm not so sure. But even you transformation shows the difference: you had to lift these types to trail level and. * permalink * embed * save * parent * report * give award * reply [-]Programming_Response 0 points1 point2 points 6 hours ago (2 children) So is Mappable::Result different than a Result Maybe better stated: can it be renamed to Mappable::Output or does it have some tie to Rust's Result type? * permalink * embed * save * parent * report * give award * reply [-]Zde-G 0 points1 point2 points 5 hours ago (1 child) It can be renamed. But Rust already does that trick in its standard library. And I kinda assumed only people used to Rust would want to read what I wrote. The only tie is that I define as Mappable>::Result as Result. * permalink * embed * save * parent * report * give award * reply [-]Programming_Response 0 points1 point2 points 5 hours ago (0 children) Oh yes I've been programming in rust for a year or so. I am aware of these type aliases, but was just confused because of the double use of Result. I have used the alised ones, but I would always qualify them or import them and never use std::result::Result. But I guess you did qualify them here, so just confirming. Thank you for the comment by the way. It's extremely useful * permalink * embed * save * parent * report * give award * reply [-]ondrejdanek 76 points77 points78 points 1 day ago (8 children) As the name suggests it simply allows associated types in traits to be generic (over types, lifetimes, consts). Take the example from the blog post that allows you to abstract over smart pointers: trait PointerFamily { type Pointer: Deref; // GAT here fn new(value: T) -> Self::Pointer; } struct ArcFamily; impl PointerFamily for ArcFamily { type Pointer = Arc; fn new(value: T) -> Self::Pointer { Arc::new(value) } } struct RcFamily; impl PointerFamily for RcFamily { type Pointer = Rc; fn new(value: T) -> Self::Pointer { Rc::new(value) } } struct Foo { bar: P::Pointer, } This wasn't possible before because the type Pointer was not allowed to be generic before 1.65. * permalink * embed * save * report * give award * reply [-]wwojtekk 29 points30 points31 points 1 day ago (1 child) This is a very nice example not involving lifetimes, it made it more clear for me so thanks! * permalink * embed * save * parent * report * give award * reply [-]ondrejdanek 18 points19 points20 points 1 day ago (0 children) You're welcome. I intentionally did not choose the classic LendingIterator example because it involves lifetimes and is much harder to understand in my opinion. * permalink * embed * save * parent * report * give award * reply [-]serg06 10 points11 points12 points 1 day ago (1 child) Eli4? * permalink * embed * save * parent * report * give award * reply [-]Shadow0133 27 points28 points29 points 23 hours ago* (0 children) You have type that uses Rc: struct Foo { bar: Rc, } To make it thread-safe, you can change Rc to Arc, but that might be slower in some cases. You want to offer both; you can manually write two types FooRc and FooArc but that will lead to duplicated code. You want to abstract over the Pointer type*: struct Foo { bar: P, } But that doesn't compile. You want to have P, where P is generic (sometimes called Higher-Kinded Type, HKT). Rust doesn't have those, but with GATs you can simulate them: // code simplified for example, you probably need add some trait bounds and other functions in real code trait PointerFamily { type Pointer; } struct ArcFamily; // Just a marker type; could also use e.g. an empty enum impl PointerFamily for ArcFamily { type Pointer = Arc; } Now PointerFamily::Pointer let's you be generic over PointerFamily: struct Foo { bar: P::Pointer, } You can even provide type aliases for easier use for the users: type FooRc = Foo; type FooArc = Foo; *You could technically use macros here to avoid duplication, but that would be harder to write, and limiting for users. * permalink * embed * save * parent * report * give award * reply [-]enabokov 3 points4 points5 points 22 hours ago (1 child) How did people write that code before GAT? * permalink * embed * save * parent * report * give award * reply [-]coderstephenisahc 12 points13 points14 points 21 hours ago (0 children) It was not possible. Usually you'd re-implement the exact same thing multiple times, either just with copy-and-paste or macros. In other words, if you wanted Foo to support using Rc or Arc you'd create two separate FooRc and FooArc types that manually implemented similar-looking APIs. But this is tedious, and not the same as it actually being generic over the type. * permalink * embed * save * parent * report * give award * reply [-]maboesanman 1 point2 points3 points 15 hours ago (0 children) This is the killer feature of GAT imo. It gives you the ability to make your api configurable by the user in some pretty powerful ways * permalink * embed * save * parent * report * give award * reply [-]pjmlp 1 point2 points3 points 10 hours ago (0 children) Thanks, this is a much better explanation. * permalink * embed * save * parent * report * give award * reply [-]OS6aDohpegavod4 32 points33 points34 points 1 day ago (5 children) It would be nice to have some other more common use cases for GATs other than LendingIterator of PointerFamily, which I've seen as basically the only examples given for every discussion. * permalink * embed * save * report * give award * reply [-]javajunkie314 17 points18 points19 points 1 day ago* (0 children) I recently decided to use a GAT in a type I was writing. I don't know that it would have been impossible without the GAT, but I think it made the API nicer. I wanted to define Action and Target traits for an action that can be performed to update a target, and applying the action would get a reference to an associated State type representing external, read-only information needed. Something like pub trait Action { type State; } pub trait Target where A: Action, { fn update(&mut self, action: A, state: &A::State); } This would work, but there were two cases where I thought I could do better. First the small one: Many Actions didn't need any external state. I could use State = (), but then I still needed to pass &() rather than just () -- not terrible, but a speed bump. Now the big one: I wanted to be able to compose Targets and Actions easily. Say I have two types, Ignition and GasPedal. And say I have two actions, TurnOn and SetSpeed. I can create Action and Target instances: impl Action for TurnOn { // There's a non-clone Key type needed to complete this action. type State = Key; } impl Action for SetSpeed { // We need a reference to some global speed limit. type State = SpeedLimit; } impl Target for Ignition { fn update(&mut self, action: TurnOn, state: &Key) { // ... } } impl Target for GasPedal { fn update(&mut self, action: SetSpeed, state: &SpeedLimit) { // ... } } All well and good. But say now I want to define an action TurnOnAndSetSpeed that can do both at once, and I have a type Car that has Ignition and GasPedal members. What should I do for the State? I could say State = (Key, SpeedLimit), but then I need to be able to construct a (Key, SpeedLimit) to pass a reference to, which is not easy because Key is not Clone and maybe I can't just take ownership. It would be very nice to be able to use (&Key, &SpeedLimit) as the State, but that's tricky. As written, if I want to say State = (&Key, &SpeedLimit) I need to give a lifetime for those &s. What are my options? I could use 'static, or I could maybe change the impls to target &'c mut Car and use 'c, or I could change Action and Target to have a lifetime parameter -- but they all have limitations. Enter GATs. I can redefine Action and Target as follows: pub trait Action { type State<'s>; } pub trait Target where A: Action, { fn update(&mut self, action: A, state: A::State<'_>); } Now State isn't a single type -- it's parameterized by a lifetime. So I can say, "A State whose lifetime is as long as this method call." Now I can redefine the impls above: impl Action for TurnOn { // There's a non-clone Key type needed to complete this action. type State<'s> = &'s Key; } impl Action for SetSpeed { // We need a reference to some global speed limit. type State<'s> = &'s SpeedLimit; } (The Target impls don't change, because they still take &Key or & SpeedLimit.) And now I can implement TurnOnAndSetSpeed: impl Action for TurnOnAndSetSpeed { type State<'s> = (&'s Key, &'s SpeedLimit); } impl Target for Car { fn update(&mut self, action: TurnOnAndSetSpeed, state: (&Key, &SpeedLimit)) { self.ignition.update(TurnOn, state.0); self.gas_pedal.update(SetSpeed, state.1); } } And just for completeness, I can create an action Stop with no associated state: impl Action for Stop { type State<'s> = (); } impl Target for GasPedal { fn update(&mut self, action: Stop, state: ()) { // ... } } Which I can call as gas_pedal.update(Stop, ()), no & required. (Again, it's a little thing, but it's nice.) So yeah. GATs are about being able to define stuff about an associated type at the use site -- e.g., when using it as a parameter type -- rather than at the definition site in the trait. * permalink * embed * save * parent * report * give award * reply [-]Zde-G 5 points6 points7 points 1 day ago (1 child) You can read an old blog post which gives you monads, monad transformers and so on. We kinda always knew all these tricks are possible in nightly, but they are non-trivial to develop, thus no one investigated how feasible they are. Because designing these things takes a lot of time and when you have no idea when would you be able to use thing in production, stable Rust... you tend to avoid these. I hope in a year or two we would know whether these are good for real or just something geeks would continue to use as intellectual excercise. Short-term I really hope for some PointerFamily trait which would allow me to write generic code for normal references, Box, Rc, and Arc, at least. That one looks very possible, feasible and useful. But it needs to be self-receiver which is only possible in std, I think. * permalink * embed * save * parent * report * give award * reply [-]Badel2 2 points3 points4 points 1 day ago (0 children) One of the arguments against stabilization was the lack of real world use cases. And one of the arguments in favor of stabilization was that if the feature is stable, more people will find use cases for it. * permalink * embed * save * parent * report * give award * reply [-]Icarium-Lifestealer 0 points1 point2 points 11 hours ago (0 children) Abstracting over generic collections with similar API but different performance characteristics. For example Vec and a persistent vector. * permalink * embed * save * parent * report * give award * reply [-]hombit 6 points7 points8 points 1 day ago (2 children) Follow-up question, will we see GAT usage in the std? For example it would be interesting to have Landing Iterator as a super-trait of Iterator (is it possible in reverse-compatible way?) * permalink * embed * save * report * give award * reply [-]Zde-G 2 points3 points4 points 1 day ago (0 children) I would think we would need to wait a few years for that. The issue here is the following: you can not, really, implement GATs themselves without changes in the compilers. But you definitely can create something like num outside of std. Designing good API around GATs is definitely non-trivial thus I'm pretty sure there would be few attempts to do that as crates. At some point, when we would understand better which ones are best one of them maybe added to std. But I don't see that happening any time soon, we need to collect enough experience with GATs. * permalink * embed * save * parent * report * give award * reply [-]Dreeg_Ocedam 1 point2 points3 points 1 day ago (0 children) This kind of stuff should probably first be experimented in crates. If useful APIs come out of it and get used, then it makes sense to bring it into the standard library. * permalink * embed * save * parent * report * give award * reply [-]toastedstapler 11 points12 points13 points 1 day ago (2 children) so it took me a while to get my head around the concept, i now understand (hopefully!) the LendingIterator example that commonly gets used trait LendingIterator { type Item<'a> where Self: 'a; fn next<'b>(&'b mut self) -> Option>; } in the article 'a was used for the GAT lifetime and the function lifetime. i have renamed the function's lifetimes to 'b to show that it is not related in any way the trait describes an iterator which gives you a mutable window across a slice. this cannot be done with the regular Iter trait because if you collected the windows into a vec of windows you'd have multiple mutable references to the same values and that violates rust's safety guarantees. you'll notice that the stblib window iterator only gives you a &[T], not a &mut [T] a GAT is just a generic value that's defined at the associated type level. in the case of the LendingIterator, a lifetime is introduced to ensure that the returned window's lifetime is shorter than the iterable's lifetime, which results in the windows only being allowed to exist one at a time. this ensures that only 1 mutable reference to a value exists at any one time i'm not yet fluent enough in GAT to know when i'd want to otherwise use them, i guess that's more of a library maintainer kinda thing * permalink * embed * save * report * give award * reply [-]arch_solnce 1 point2 points3 points 10 hours ago (0 children) Great answer, thanks! * permalink * embed * save * parent * report * give award * reply [-]WishCow 1 point2 points3 points 6 hours ago (0 children) in the article 'a was used for the GAT lifetime and the function lifetime. i have renamed the function's lifetimes to 'b to show that it is not related in any way I'm so glad you spelled this out, because it's such an important point, I wish this was stressed more in the examples. * permalink * embed * save * parent * report * give award * reply [+]Elegant-Bag-6789 comment score below threshold-7 points-6 points-5 points 17 hours ago (2 children) Hoping somebody well correct me if I'm wrong, but GATs don't give you any additional expressive power. Generic types are already higher-ordered and you can rewrite any code with GATs to be something without GATs fairly easily. Albeit, your code will be far less ergonomic generally worse The additional hubub around GATs seems to have been generated by a misguided portion of the Haskell community. Just think of GATs as "now associated types have type parameters too! " it's not that complicated and is pretty intuitive to use :) * permalink * embed * save * report * give award * reply [-]CocktailPerson 4 points5 points6 points 14 hours ago (1 child) It's kind of hard to say whether you're right or wrong, because it's not clear what you mean by "additional expressive power." I mean, you could also rewrite any Rust code to C; does that mean Rust gives you no additional expressive power over C? There are patterns that are significantly easier to express with GATs, like LendingIterator, and I'm curious to see whether you could implement it in pre-GAT Rust as easily as you think. That said, I do think that the type theorists have gotten very excited over the type-theoretical implications, leaving everyone else to scratch their heads and wonder what they're actually good for. I think it'll be easier to see the benefit they bring as we see things being (re)written to use them. * permalink * embed * save * parent * report * give award * reply [+]Elegant-Bag-6789 0 points1 point2 points 6 hours ago* (0 children) By additional expressive power, I mean that they can be removed by simple syntactic translation. I mean it in more of a mathematical/ semantic sense. That gets a little muddy in turing-complete languages though. I mean your code will not blow up 10x in size when you remove GATs and they don't give you access to some previously unreachable semantic notion of types. I'm pretty sure it's easy to implement lending iterator at least as your own type for a single type. This wouldn't be as good as a trait that all sorts of structs can implement though GATs are certainly more expressive in the sense that they convey a bit more developer intention. They are good, they make code better, and they're easy to use. But I think people are overhyping them from the type-theoretic/semantic POV * permalink * embed * save * parent * report * give award * reply [-]SkyMarshal 0 points1 point2 points 5 hours ago (0 children) Good discussion here: https://news.ycombinator.com/item?id=33505810 * permalink * embed * save * report * give award * reply * about * blog * about * advertising * careers * help * site rules * Reddit help center * reddiquette * mod guidelines * contact us * apps & tools * Reddit for iPhone * Reddit for Android * mobile website * <3 * reddit premium * reddit coins Use of this site constitutes acceptance of our User Agreement and Privacy Policy. (c) 2022 reddit inc. All rights reserved. REDDIT and the ALIEN Logo are registered trademarks of reddit inc. [pixel] p Rendered by PID 26013 on reddit-service-r2-loggedout-6cc4488857-slrx8 at 2022-11-07 23:02:31.130789+00:00 running 2faf6ad country code: US.