[HN Gopher] Rust's Poor Composability
       ___________________________________________________________________
        
       Rust's Poor Composability
        
       Author : sinistersnare
       Score  : 94 points
       Date   : 2023-04-06 13:39 UTC (9 hours ago)
        
 (HTM) web link (thedav.is)
 (TXT) w3m dump (thedav.is)
        
       | jamincan wrote:
       | > But the teams working on language design need to SLOW DOWN and
       | focus on ergonomics and composability. Not adding new syntax
       | because some other language has it.
       | 
       | Genuinely when was the last time Rust actually added new syntax
       | to the language? I think the try operator and await syntax were
       | both added in 1.39 back in 2019. Const generics was added in Feb
       | 2021.
       | 
       | Generic associated types were added recently in November I think,
       | but that's not really added syntax so much as removing a
       | restriction on generics.
        
         | majewsky wrote:
         | > Genuinely when was the last time Rust actually added new
         | syntax to the language?
         | 
         | let-else and GAT were introduced in 1.65 (November 2022).
        
           | vlovich123 wrote:
           | let-else is simple and trivial. GATs have been baking for a
           | very long time which is the definition of moving slowly. The
           | author wants them to slow down everything else to speed up on
           | the area of the language that inconveniences him personally,
           | but that's not how engineering works and would just stall the
           | language.
        
         | pwdisswordfishc wrote:
         | Ironically, given Rust's design constraints, providing the kind
         | of composability features the author wants would require adding
         | new syntax. Because control flow out of map cannot work as long
         | as map is just a regular function taking a closure.
         | 
         | It's not much different from higher-order functions in
         | JavaScript, really. (Other than the fact that JavaScript has
         | exceptions.)
        
       | stephc_int13 wrote:
       | I don't get it.
       | 
       | Why most "modern" languages are using such weird/contrived and
       | hard to read syntax?
       | 
       | I can understand that C++ evolved over a long time, starting from
       | C backward compatibility, I think mistakes were made but I can
       | imagine the constraints.
       | 
       | But for Rust and Zig, they started from scratch, why do they have
       | to use so many sigils (magic symbols)?
       | 
       | System programming should not look like sed or Perl.
        
         | klibertp wrote:
         | There's no such thing as hard to read syntax[1]. There's only
         | syntax you're unfamiliar with, or syntax that isn't supported
         | by your editor yet. That's it.
         | 
         | Don't project your reaction to unfamiliarity as a general law.
         | Instead, either ignore the languages with syntaxes you don't
         | like, or just go and learn them before forming an opinion.
         | 
         | And ffs don't talk about syntax being "pretty" or "ugly".
         | That's the epitome of subjectivity, I have a hard time trying
         | to understand why would anyone out of their teens want to argue
         | about that. Ancient Romans knew that de gustibus non
         | disputandum est, yet here we are, "discussing" what is
         | beautiful and what's not.
         | 
         | [1] There are some constraints that stem from the optics and
         | mechanics of an eyeball. Other than that, it's purely
         | subjective.
        
           | stephc_int13 wrote:
           | I strongly disagree.
           | 
           | I think that style is a matter of taste, and some part of
           | syntax can be regarded as styling decisions, like with
           | placement of tabs vs spaces, braces alignment, snake_case vs
           | camelCase etc.
           | 
           | But there _is_ a continuum of syntax readability, brainfuck
           | is less easy to read than Perl that is still easier to read
           | than Sed.
           | 
           | And I think exactly the opposite about familiarity, the less
           | familiar you are with a syntax the better your judgment is
           | about its readability.
           | 
           | Of course that can be learned, but that is not the point.
           | 
           | Programming languages should really strive to be as easy to
           | read as possible, even at the cost of being more difficult to
           | write.
           | 
           | Code should never look arcane, especially to someone
           | unfamiliar with the language / codebase.
        
             | klibertp wrote:
             | Brainfuck is harder to read not because of the syntax, but
             | because it describes very low level semantics. Actually,
             | it's not even harder to read - it's harder to comprehend.
             | 
             | You're conflating comprehensibility with readability -
             | they're not the same. There's a reason why people get
             | tested for "reading comprehension" and not just "reading".
             | 
             | In other news, I now know that you have your own idea about
             | how programming languages should be. Good for you. That
             | idea is not grounded in any research, because there is no
             | research available, but you're welcome to have it, and when
             | you write your own language you're welcome to stick to it.
             | 
             | Logical conclusion of your last statement is this:
             | https://en.wikipedia.org/wiki/Inform#Example_game_2 I'll
             | leave you to think about why it's not the most popular
             | programming language on the planet.
             | 
             | I'd say more, but I don't want to inconvenience Daniel any
             | more than I already have the previous time, so let's just
             | strongly agree to strongly disagree.
        
               | hbrn wrote:
               | > You're conflating comprehensibility with readability -
               | they're not the same. There's a reason why people get
               | tested for "reading comprehension" and not just
               | "reading".
               | 
               | "Reading comprehension" is not the same as "reading" only
               | for natural languages, because we can "read" words by
               | transforming them into sounds.
               | 
               | In the context of programming, reading and comprehending
               | are synonyms. Nobody cares if you can "vocalize" the code
               | without comprehending it.
        
               | klibertp wrote:
               | Maybe nobody cares, but visually identifying a token and
               | comprehending its meaning are still distinct steps. What
               | I'm trying to say is that visual identification - the
               | thing that syntax is responsible for - is the easy part,
               | and the latter is so much harder that the former almost
               | doesn't count.
               | 
               | It's not that I think syntax doesn't matter at all. It
               | does, in some context it matters a lot - just not because
               | of readability. And most definitely not because it looks
               | pretty or ugly.
        
               | stephc_int13 wrote:
               | Easy to comprehend, easy to read, alright, in this
               | context I think they are close enough to avoid wasting
               | time on semantics.
               | 
               | I have some ideas about programming languages but I think
               | that in this field less is more, in an ideal world we
               | should have only a handful of programming languages, to
               | avoid the huge waste of resources.
        
         | ModernMech wrote:
         | Systems programming is about very precisely specifying all
         | aspects of the program down to the lifetimes and sizes of all
         | variables, so that the compiler can make advanced optimization
         | decisions that result in fast and compact binaries. All that
         | specification comes with a lot of syntax, and some of it can be
         | inscrutable considering the limited character set we're working
         | with. That's how you get things like the turbofish, but it's
         | there for a reason.
        
         | jedisct1 wrote:
         | Zig is very readable, and doesn't use many sigils at all.
        
           | omgtehlion wrote:
           | .{ zig has a lot of .{ } }
        
             | stephc_int13 wrote:
             | This part is ugly and annoying but not too difficult to
             | read.
        
           | stephc_int13 wrote:
           | Zig uses ? and ! in ways that are difficult to read IMHO, for
           | example.
        
         | pwdisswordfishc wrote:
         | What is hard to read about Rust and Zig?
        
         | Georgelemental wrote:
         | To give an example, the functionality of `?` in Rust used to be
         | provided by the `try` macro (https://doc.rust-
         | lang.org/core/macro.try.html). Dedicated syntax was later added
         | because it made a lot of code cleaner and easier to read, at
         | the expense of a minimal upfront learning cost.
        
         | anon291 wrote:
         | Many people complain about language's use of symbols, and then
         | fail to think of what it would look like without the symbols.
         | Realistically, humans are incredibly good at manipulating
         | symbols to reduce verbosity, it's why I just used an
         | apostrophe.
        
           | stephc_int13 wrote:
           | I don't mind a bit of verbosity. With words, on multiple
           | lines, not with an esoteric combination of sigils.
        
         | singularity2001 wrote:
         | Swift is modern and beautiful, but crippled by its ecosystem.
        
           | RazrFalcon wrote:
           | While I do like Swift and wish Rust would look as nice, it
           | doesn't have references and lifetimes. Those two alone make
           | code way nicer.
           | 
           | Anonymous closure arguments, aka $0, are probably the ugliest
           | part of its syntax.                   numbers.contains(where:
           | { $0 > 0 })
        
       | t43562 wrote:
       | > So instead we need to use the ugly for-loop manual collection
       | 
       | > let res = vec![]; > for x in &iterator { > res.push(x.foo()?);
       | > }
       | 
       | I'm obviously not a rust programmer because I think that's much
       | _less_ ugly :-)
        
       | binarymax wrote:
       | The first example code doesn't look like anything you should ever
       | do - use immutability and don't write rust like C.
       | 
       | The rest of the article is based on this use case, so I don't see
       | the point.
       | 
       | Composability can be done in a functional style without mutating
       | state. The outcome would likely be very different and easier with
       | this approach if the time were taken to grok it.
        
       | jmillikin wrote:
       | Reads like someone used to working in an abstract high-level
       | language, who doesn't like the verbosity mandated by a language
       | aimed squarely at embedded/systems programming.
       | 
       | Maybe one day there will be a language that has the expressive
       | type system of ML, the garbage collection of ML, and the
       | predictable performance of ML. Then people who don't need to care
       | about the stack layout differences of an async function can use
       | that language instead of Rust.
        
       | vkakade wrote:
       | The examples presented are not significant issues (in my
       | opinion).
       | 
       | If someone from the Rust language development community is
       | reading this, what I would really like to see is Rust supporting
       | default arguments to functions. Coming from C++/Java/Python
       | world, that is one language feature I sorely miss.
        
       | NoraCodes wrote:
       | > the for loop syntax should be simple syntactic sugar for
       | iterator.iter().for_each(BODY), not bespoke syntax. The fact this
       | breaks wrecks my mental model.
       | 
       | I'm sorry to be blunt, but this mental model is wrong. .for_each
       | runs a function, while for does not.
       | 
       | If what you want is to return early on an error, you can
       | .collect() an Iterator<Item=Result<T, E>> directly into a
       | Result<Vec<T>, E> and then ? that. That's the equivalent of for
       | and ?. Or, you can collect into a Vec<Result<T, E>>.
       | 
       | > You must use the for loop syntax if you want to use .await,
       | because iterators are not powerful enough to support real world
       | use-cases.
       | 
       | Given that what this requires is async closures, and async
       | closures are an active area of work, this seems like a somewhat
       | unhelpful comment.
       | 
       | I definitely agree about having better syntax for iter_mut,
       | though.
        
       | rendaw wrote:
       | Rust feels like a very patchwork language. There's all sorts of
       | issues (for loops, error handling, referencing/dereferencing,
       | type inference, etc) that they've worked around by adding another
       | very specific compiler behavior that completely falls apart in
       | any other usage.
       | 
       | Like having to do `&mut **` to pass a reference to a function
       | once you get out of the compiler's comfort zone.
        
         | jmillikin wrote:
         | Rust is a very industrial language. It tries to identify
         | specific useful patterns and then expose them to the programmer
         | in misuse-resistant forms.
         | 
         | In contrast, academic languages try to identify broadly-
         | applicable abstract primitives and expose those, allowing users
         | to implement the behaviors they need in libraries. The result
         | feels more consistent at a language level because you can tie
         | language features very closely to an underlying theory of
         | computation.
         | 
         | I've written a lot of C++ (industrial) and Haskell (academic).
         | Although I love how Haskell allows users to (for example)
         | define their own control-flow statements, that "bag of
         | primitives" nature leads to Haskell projects easily forming
         | their own dialect of the language[0] without even having to
         | resort to macros.
         | 
         | [0] This is also a common criticism of Lisp and Forth.
        
           | tensor wrote:
           | Honestly I'd put rust somewhere between Lisp and Haskell,
           | solidly in the academic language camp. It's full of all sorts
           | of neat tricks, with a great variety of ways to accomplish
           | any one thing. You also need to dip into 3rd party libraries
           | for nearly basic things like decent error handling.
           | 
           | In my mind an industrial language needs to be boring and
           | obvious. For instance, Go is very boring, and it actively
           | discourages by it's design "clever" programming. Java Go and
           | C are industrial languages. C++ is far too clever these days.
           | 
           | All that said rust is far better than Java and Go for
           | interfacing with low level systems, and of course the safety
           | improvements from the borrow checker are very valuable,
           | despite the frustrations it often causes.
           | 
           | I don't dislike rust, but it sure could use a lot of polish
           | in my opinion, making obvious things easy and discouraging
           | clever things.
        
         | NoraCodes wrote:
         | > Like having to do `&mut *` to pass a reference to a function
         | 
         | I'm curious about your use case for this. `&mut *` I've seen,
         | for instance to dereference a RwLockWriteGuard, but why the
         | double dereference?
        
           | rendaw wrote:
           | It was with deadpool, I don't recall the exact underlying
           | reasons though: https://github.com/bikeshedder/deadpool/issue
           | s/104#issuecomm...
        
       | littlestymaar wrote:
       | > Rust has a nice pretty syntax for iterating:
       | 
       | > `for x in &mut something {`
       | 
       | and a few paragraph later:
       | 
       | > So instead we need to use the ugly for-loop manual collection
       | 
       | > `for x in &iterator {`
       | 
       | So, is that syntax pretty or ugly?
        
       | lang_agnostic wrote:
       | Is this person actually asking for Monads and Higher Kinded
       | Types?
       | 
       | It would solve the problem they're raising but somehow I suspect
       | that's now the solution they want to hear. And also the rust
       | version would be quite hard to use due to the memory management
       | model.
        
         | jmillikin wrote:
         | I think the author wants something like Ruby's blocks, although
         | you're correct that monads would also be a good fit.
         | 
         | In Ruby, iteration can be performed by passing a sort-of-
         | closure called a block. Unlike passing a closure (function),
         | returning early from a block acts like returning from a for
         | loop.
         | 
         | If a language has a garbage collector and no user-visible
         | separation between stack and heap allocation, then there's lots
         | of interesting things that language can do in terms of building
         | "fluent" APIs.
        
         | anon291 wrote:
         | In my experience, when it comes to language design, most people
         | end up wanting monads and HKTs but refuse to admit it / are
         | scared of admitting it.
        
       | coldtea wrote:
       | > _I love Rust. I wish they would spend more time making it
       | actually work for non hello-world use-cases._
       | 
       | Considering Rust has been used for anything, from basic userland
       | utilities and databases, to high performance network stacks
       | serving billions, compilers, and even Linux kernel drivers, I'd
       | call this statement "not even wrong".
       | 
       | > _What 's the point of having the 'pretty' syntax if it only
       | works in the simplest of cases?_
       | 
       | That the "simplest of cases" are 90% of what you use 'for' for?
       | The syntax should "make simple things easy, and hard things
       | possible", and this is an example of exactly that!
       | 
       | > _I hate syntax that only works in hello world examples_
       | 
       | In which universe is basic iteration a "hello world" use case?
       | It's used in basically EVERY program, all the time. That's "bread
       | and butter", not "hello world" level.
       | 
       | And the rest also has a reason to be there, not that someone
       | "infuriated" would try to go deeper.
       | 
       | > _There should be one-- and preferably only one --obvious way to
       | do it._
       | 
       | That's a design goal for another language. Which failed much
       | worse than Rust at that, and for no good reason.
        
         | marcosdumay wrote:
         | > That the "simplest of cases" are 90% of what you use 'for'
         | for?
         | 
         | Anything that can't be done gets exactly 0% of the usage. I
         | don't know where you got 90% from.
         | 
         | The author clearly comes from a mindset of trying to write
         | Haskell code in Rust. But the criticism is fair, those are 3
         | very ugly aspects of the language, and it may be possible to
         | improve them without breaking the general behavior... And even
         | if it isn't, it is well worth it to be aware of them.
        
         | drewcoo wrote:
         | Taking everything someone says strictly literally does not make
         | other people "not even wrong." It's a reading comprehension
         | error and is uncharitable.
         | 
         | The author really likes Rust. He's not some naysayer. He'd just
         | like Rust to be even better. He provides constructive feedback.
        
         | timerol wrote:
         | https://without.boats/blog/the-registers-of-rust/ is pretty
         | transparent about Rust not having or aiming for "only one
         | obvious way to do it". Rust has more of a mindset that if
         | you're willing to mess with the fiddly bits, then you get
         | control of all of the effects.
        
         | lcnPylGDnU4H9OF wrote:
         | I'm not familiar with Rust.                 for x in &mut
         | something {           *x = (*x) * 2;       }
         | 
         | Here (above) it looks like the `&mut` is allowing the iterator
         | to be mutable (https://doc.rust-lang.org/std/keyword.mut.html).
         | for (i, x) in something.iter_mut().filter(|| {...}).enumerate()
         | {           *x = (*x) * i       }
         | 
         | The `mut` keyword isn't needed here because, I assume if the
         | above assumption is correct, "iter_mut()" is returning a
         | mutable iterator. Is there some reason the result of
         | "something.iter_mut().filter(|| {...}).enumerate()" can't be
         | saved to a variable and then used as the "something" in the
         | first example?
         | 
         | Anyway, this post reminds me of an article, "Why Programming
         | Language X is Unambiguously Better Than Programming Language
         | Y"[0]. Mostly talking about how [poorly written Y code] is why
         | Y is a bad language and [beautiful and elegant X code] is why X
         | is a good language.
         | 
         | [0] https://news.ycombinator.com/item?id=6960398; unfortunately
         | the link is dead and I can't find it on the domain
        
           | thethirdone wrote:
           | > Is there some reason the result of
           | "something.iter_mut().filter(|| {...}).enumerate()" can't be
           | saved to a variable and then used as the "something" in the
           | first example?
           | 
           | You absolutely can do something like `let iter =
           | something.iter_mut().filter(|| {...}).enumerate();` In Rust,
           | Iterators are just need to implement a Trait (kindof like an
           | Go or Java interface) so they can be put in normal variables
           | just fine.
           | 
           | > The `mut` keyword isn't needed here because, I assume if
           | the above assumption is correct, "iter_mut()" is returning a
           | mutable iterator.
           | 
           | Every for loop implicitly calls `.into_iter()` on the thing
           | to loop over; the docs are decent at explaining what IntoIter
           | is [0]. By doing `&mut something` instead of just `something`
           | you get a different IntoIter implementation which has very
           | different semantics. Typically `(&mut something).into_iter()`
           | would have the same behavior and might even result in the
           | same type as `something.iter_mut()`.
           | 
           | [0]: https://doc.rust-
           | lang.org/std/iter/trait.IntoIterator.html
        
       | overthrow wrote:
       | > BUT when you try to use it with iterators -- which are also
       | amazing, I love using iterators -- IT DOESNT WORK.
       | 
       | Yes it does                 let res: Result<Vec<_>, _> =
       | iterator.iter().map(|x| x.foo()).collect();       let res = res?;
       | 
       | > But this also happens elsewhere, because of this ugly
       | inflexability, we cant do:
       | 
       | Use and_then:                 let z = x.foo().and_then(|y|
       | y.bar())?;
       | 
       | I get that there's an upfront cost to learning this stuff, but I
       | think he's blaming the language a little too aggressively.
        
         | unshavedyak wrote:
         | Yea, it's a game i often play. "Boy it would be nice if i could
         | do X" where X is something like `and_then`, and then i go look
         | it up in the stdlib and.. it's there.
         | 
         | For a good while my X's were a nightly only experimental API,
         | but slowly and surely they make it into stable.
         | (Hash|Btree)Map's `.entry` methods (and friends) were another
         | example.
         | 
         | Generally speaking these methods are super helpful and someone
         | else already thought of it. From my experience at least.
         | 
         | .. also this article was infuriating hah.
        
         | WirelessGigabit wrote:
         | It's not the same.
         | 
         | Using the question mark in a closure in general is confusing.
         | 
         | Doing ? on the return value of a statement doesn't make sense.
         | Normally ? is used for: here is this Result (I forgot the
         | trait's name), and give me the Ok(), and if it is Err() then
         | RETURN to the caller.
         | 
         | So the function itself must be of type Result<_, _>.
         | 
         | With the lambda in the example it is unclear by reading the
         | code what the ? should do. Stop at the first error and return
         | Err? Or should the return value be Result<Vec<_>, _>?
         | 
         | One can use the try_ functions on an iterator.
        
         | sshine wrote:
         | It is a non-obvious Rust-ism that if you're doing something
         | with iterators and you don't know how, it's probably
         | .collect(). ;-)
         | 
         | Kind of like how the answer is always 'traverse' in Scala.
         | 
         | .collect() gets overloaded a lot to deal with the intricacies
         | of the return type, which usually says a lot about the
         | computation, e.g. Result or HashSet.
        
         | mcronce wrote:
         | Also,                 let res = iterator.iter().map(|x|
         | x.foo()).collect::<Result<Vec<_>, _>>()?;
        
           | tmtvl wrote:
           | And people complain about ))) in Lisp...
        
           | fnordpiglet wrote:
           | I love rust because it satisfies my inner perl
        
         | y7 wrote:
         | Does this early exit on error?
        
           | nimih wrote:
           | Yes. You can prove it to yourself with this program:
           | https://play.rust-
           | lang.org/?version=stable&mode=debug&editio...
        
         | linkdd wrote:
         | The problem with .map().collect()? is that it does not short-
         | circuit.
         | 
         | Consider:                 for item in iterable.iter() {
         | item.foo()?;       }
         | 
         | You will exit the loop as soon as there is an error. But with
         | .map().collect(), you iterate over the whole list, and then
         | exit if one of the items yield an error.
         | 
         | EDIT: Thank you for your answers, it seems I was wrong and it
         | does short-cricuit!
        
           | nynx wrote:
           | Collecting into a result will short circuit.
        
           | anon291 wrote:
           | Realistically, the 'problem' is that many programmers today
           | get scared away when you start mentioning monads or effect
           | systems. Realistically though, the solution is to have first
           | class monads or effect systems in rust and other languages. A
           | lot of language design today is 'wasted' in the sense that we
           | spend time implementing special cases (Promises in
           | javascript, ? syntax in rust, async syntax in rust) without
           | any thought given to the meta problem. In my opinion, the
           | lack of attention to the meta problem is due to programmers
           | being scared of such topics.
        
           | chrismorgan wrote:
           | Incorrect: collecting to a Result stops on the first error,
           | as documented explicitly in https://doc.rust-
           | lang.org/std/result/enum.Result.html#method....
        
           | NobodyNada wrote:
           | That's not true: https://play.rust-
           | lang.org/?version=stable&mode=debug&editio...
           | let v: Option<Vec<i32>> = (0..5).map(|i| {
           | print!("{i} ");             if i == 3 { None } else { Some(i)
           | }         }).collect();         // prints 0 1 2 3
        
             | remram wrote:
             | Short-circuit refers to stopping not skipping.
        
               | fnordpiglet wrote:
               | I don't understand. The example you replied to stopped,
               | not skipped.
        
           | tuetuopay wrote:
           | Map and friends are lazy, so the whole chain is run once and
           | in full for each item. Collect _will_ short circuit at the
           | first Err encountered. You can even try it out by using
           | `.inspect()` in the chain to see that the iterations don 't
           | go past the first error.
        
           | SuperCuber wrote:
           | Except it does! https://doc.rust-
           | lang.org/src/core/result.rs.html#2029
        
         | tempusr wrote:
         | It just reads better in terms of code.
         | 
         | `let res: Result<Vec<_>, _> = iterator.iter().map(|x|
         | x.foo()).collect();`
         | 
         | Left to right reading this statement is "iterate over the
         | values and map it to the output of foo for each value. Then
         | collect that map into a vector."
         | 
         | His other example
         | 
         | ``` for (i, x) in something.iter_mut().filter(||
         | {...}).enumerate() { _x = (_ x) * i } ```
         | 
         | can also be read from left to right as "iterate over something,
         | then filter and enumerate the values"
         | 
         | It's much more explanatory than top down structures with 10
         | lines to explain the same thing.
        
       | darthrupert wrote:
       | Writing Rust recently got a bit easier because a certain large
       | language model is able to work around all of these issues while
       | also explaining why Rust designers made those choices.
       | 
       | I certainly hope that the language designers keep making the
       | language easier to write for us totally fleshy humans that are
       | not robots at all as well.
        
       | agumonkey wrote:
       | f-rust rated
        
       | justinpombrio wrote:
       | This is a pretty low quality rant. Going example by example:
       | for (i, x) in something.iter_mut().filter(|| {...}).enumerate() {
       | *x = (*x) * i         }
       | 
       | you can this this in loop style if you want:
       | for i in range(something.len()) {             let x = &mut
       | something[i];             if ... {                 *x = (*x) * i;
       | }         }
       | 
       | or if you prefer, in iter style:                   something
       | .iter_mut()           .filter(|| {...})           .enumerate()
       | .for_each(|(x, i)| *x = (*x) * i);
       | 
       | The next two examples can be written:                   let res:
       | Vec<_> = iterator.iter().filter_map(|x| x.foo()).collect();
       | let z = x.foo().and_then(|y| y.bar());
       | 
       | There _is_ an ergonomics issue around the fact that if you stick
       | `?` or `.await()` in a closure, it returns to _that closure_
       | instead of the function containing it. But the way this issue
       | manifests itself is the standard library having a pile of
       | different variations on methods, like `.map()` vs.
       | `.filter_map()`. That 's the thing to complain about, not the
       | fact that closures work the same way they do in every other
       | language.
        
         | Pxtl wrote:
         | I've never used Rust - in a conventional language you'd just
         | have a mutable variable defined outside of the loop and used
         | and incremented inside of the loop. I assume Rust's for-loops
         | can't access the outside variable scope that way?
         | 
         | Like (pseudocode)                   var i = 0         for(item
         | in items) {           item.DoThing(i)           i+=1         }
         | 
         | Does rust not allow that kind of mutation and access of a
         | variable from outside the loop within the loop?
        
           | nu11ptr wrote:
           | Sure, you can do something like that, but it is unnecessary.
           | Like Python, you can use "enumerate" to give you a tuple that
           | that will give you `i` automatically.
           | 
           | https://play.rust-
           | lang.org/?version=stable&mode=debug&editio...
           | struct Item(u32);              impl Item {             fn
           | do_thing(&self, i: usize) {                 println!("The
           | value of `i` is: {i}");             }         }
           | fn main() {             let items = vec![Item(1), Item(2),
           | Item(3)];                  for (i, item) in
           | items.iter().enumerate() {                 item.do_thing(i);
           | }         }
        
           | sophacles wrote:
           | This works fine in rust too. Iterators are a place where the
           | power of the type system (etc) feel like magic and so you'll
           | find a tendency for solving things with the magic (especially
           | in newer programmers, but everyone does it at least a
           | little).
           | 
           | Compare all the crazy things people build out of channels in
           | go, or a similar tendency in python to over-do the generator
           | expressions.
        
           | holmium wrote:
           | you can do it that way if you wanted to                   let
           | mut i = 0;         for item in items {
           | item.method(i);           i += 1;         }
           | 
           | I don't know why you'd it that way, but Rust definitely isn't
           | stopping you from doing that.
           | 
           | e: and as running example: https://play.rust-
           | lang.org/?version=stable&mode=debug&editio...
        
             | Pxtl wrote:
             | Well, the complaint in the article is that the idiomatic
             | way is kind of inscrutable. I'm more than willing to let
             | pragmatism win in those cases, since so many languages make
             | this pattern a nuisance instead of providing a simple
             | "enumerate this array with index" out-of-the-box as part of
             | the standard library. IIRC you have to roll your own in
             | Java, C#, and Powershell.
        
               | lostmsu wrote:
               | In C# there's a .Select overload, that gives your lambda
               | both item and its index.
        
           | apendleton wrote:
           | No, you definitely can. You generally can't mutate things
           | that would cause the iterator itself to be invalidated (like,
           | you can't delete items from a vector while you're iterating
           | over it), but you can get an iterator over a vector that
           | gives you a mutable reference to each of its members, which
           | would be sufficient to do what you're suggesting here.
           | 
           | I think what they were pointing out (and I agree with) is
           | that the mixing of combinators and imperative-style for loops
           | that's going on in the post doesn't feel very natural.
           | Typically if you would _either_ do                 blah
           | .into_iter()           .filter(|x| x.some_condition())
           | .for_each(|x| x.do_something());
           | 
           | or                 for x in blah {           if
           | !x.some_condition() {               continue;           }
           | x.do_something();       }
           | 
           | But `for x in blah.into_iter().filter(...` is a bit unusual.
        
           | butt_____hugger wrote:
           | You could definitely do that with Rust, but using `enumerate`
           | rather than mutable variables is considered idiomatic.
           | Neither is more or less "conventional".
           | 
           | lol 3 people said the same thing at the same time. rust
           | strike force assemble
        
             | holmium wrote:
             | haha yes the discourse level thread is like perfect for all
             | us "we've read the Book" people to comment on.
        
           | [deleted]
        
       | nu11ptr wrote:
       | It sounds like the author is not aware of the `FromIterator`
       | trait which is implemented for `Result` which allows short
       | circuiting to a `Result` type.
       | 
       | Instead of collecting to:                   Vec<Result<T,Error>>
       | 
       | You collect to:                   Result<Vec<T>,Error>
       | 
       | as your explicit type and your iterator will short circuit on the
       | first error. I admit this was not obvious to me either and should
       | be talked about more as it is very handy.
        
       | Alifatisk wrote:
       | Love this kind of articles, keep it up!
        
       | ohgodplsno wrote:
       | Pythonistas when a language doesn't follow PEP8 and doesn't have
       | a GIL.
       | 
       | >EXCEPT when you need to do anything else to the iterator, then
       | its ugly
       | 
       | God forbid you use one (1) whole variable to write
       | let enumerated_something = something.iter_mut().filter(||
       | {...}).enumerate()        for (i, x) in enumerated_something {
       | ... }
       | 
       | >BUT when you try to use it with iterators -- which are also
       | amazing, I love using iterators -- IT DOESNT WORK.
       | 
       | Pythonistas when you can't throw in the middle of a closure to
       | end your loop. What is .map { } supposed to do ? Abort early but
       | still stay alive ? Abort early and just return the first two
       | elements that didn't fail ?
       | 
       | Or, you could define a Iterable<T>.map_or_none() that returns a
       | bunch of Result/Options as an output, and be done with it.
       | 
       | >Again, the absolute lack of composability is astounding.
       | 
       | Coming from someone using python where extensions methods are a
       | pipe dream and function(compositions(are(written(in(a(style(that(
       | makes(me(want(to(go(back(to(lisp)))))))))))))), that's rich.
        
         | jamincan wrote:
         | > function(compositions(are(written(in(a(style(that(makes(me(wa
         | nt(to(go(back(to(lisp))))))))))))))
         | 
         | I think you got that backwards. I'm pretty sure it's supposed
         | to be lisp(to(back(go(to(want(me(makes(that(style(a(in(written(
         | are(compositions(function))))))))))))))
        
       | MrBuddyCasino wrote:
       | The Kotlin compiler can handle chains of
       | potentially().nullable()?.invocations(). Is the Rust early-error
       | returning not a similar case, or am I missing something?
        
         | NoraCodes wrote:
         | Yeah, Rust does that as well. The issue the author is talking
         | about here is that when you write a closure:
         | fn foo(inputs: &[Input]) -> Result<Vec<Output>, Error> {
         | inputs             .iter()             .map(|input| {
         | input.something_fallible()? })             .collect()         }
         | 
         | that ? is an early return from the closure, not foo. The
         | correct way to do this is:                   fn foo(inputs:
         | &[Input]) -> Result<Vec<Output>, Error> {             inputs
         | .iter()             .map(|input| { input.something_fallible()
         | })             .collect::<Result<Vec<Output>, Error>>()
         | }
         | 
         | ref: https://play.rust-
         | lang.org/?version=stable&mode=debug&editio...
        
           | MrBuddyCasino wrote:
           | I wonder how come the type must be specified explicitly as
           | "collect::<Result<Vec<Output>, Error>>()" and can't be
           | inferred, given that the function return type is already
           | spelled out as "Result<Vec<Output>, Error>"? Is there a
           | specialisation for collect() for Result<> types that has to
           | be explicitly triggered?
        
             | gpm wrote:
             | It doesn't have to be specified explicitly in this case, it
             | can be inferred.
             | 
             | https://play.rust-
             | lang.org/?version=stable&mode=debug&editio...
             | 
             | Probably just habit from GP to write out the return type,
             | since collect so often can't be inferred. Or maybe to make
             | it easier to change it to an intermediate value which you
             | use ? with (at which point the type can no longer be
             | inferred).
             | 
             | https://play.rust-
             | lang.org/?version=stable&mode=debug&editio...
        
               | NoraCodes wrote:
               | Yeah, apologies, that's just habit on my part.
        
         | cormacrelf wrote:
         | Kotlin and also C# ? operators only "return" from the
         | expression being evaluated. Most documentation says "return" a
         | bit ambiguously but that's what they mean. AFAIK Rust is the
         | first language to use ? in such a way as to return from a
         | function, and that's quite different. Kotlin ? can technically
         | be emulated in Rust with an IIFE (immediately invoked function
         | expression) like so:                   // never do this, it's
         | rude         let x = (||
         | potentially().nullable()?.invocations())();
         | 
         | Or with Option::map, and_then and friends:                   //
         | this is the way         let x =
         | potentially().nullable().map(|y| y.invocations());
        
           | MrBuddyCasino wrote:
           | That made it click, thanks!
        
         | gpm wrote:
         | Rust does the same.
        
       | FpUser wrote:
       | Speaking of syntax:                 for x in &mut something {
       | *x = (*x) * 2;       };
       | 
       | Not Rust user but would not it make sense for compiler to
       | understand x instead of *x in this situation? I thought it is
       | considered higher level language than C.
        
         | estebank wrote:
         | The syntax discriminates between "changing the value where the
         | reference points to" and "changing where the reference points
         | to". The code is doing the former. The second use of * could be
         | made unnecessary if the standard library had an impl Mul<u32>
         | for &mut u32 {}.
        
       | kajaktum wrote:
       | > let res: Vec<_> = iterator.iter().map(|x| x.foo()?).collect();
       | 
       | This make sense to me? Why shouldn't it return early from the
       | lambda in `map`? Had it return the "highest level function" in
       | the scope then that would be bug prone imo. It is already
       | possible to do fallible iteration:
       | 
       | > let res: Vec<_> = iterator.iter().map(|x|
       | x.foo()).collect::<Vec<Result<_,_>>>()?;
       | 
       | However, I would like to mention that you should not try to force
       | the syntax. This mantra is very misleading imo
       | 
       | > There should be one -- and preferably only one -- obvious way
       | to do it.
       | 
       | Had Rust gone with your suggestion, imagine writing the
       | equivalent of this?
       | 
       | ``` fn foo() -> Result<String, String> {
       | Ok("hello".to_string())
       | 
       | }
       | 
       | fn main() {                   println!(                  "{:?}",
       | (0..2)                      .map(|_| {
       | foo()?;                          foo()?;
       | foo()?;                          foo()?;
       | foo()?;
       | Ok::<String,String>("world".to_string())                      })
       | .collect::<Vec<Result<_, _>>>()              );
       | 
       | } ```
       | 
       | How a "hacker news" website not supporting code snippet is beyond
       | me.
        
         | estebank wrote:
         | > How a "hacker news" website not supporting code snippet is
         | beyond me.                 Indent your code by two spaces.
        
       | chrismorgan wrote:
       | > _for x in &mut something { ... }_
       | 
       | The thing to realise is that this largely isn't how you'll use
       | the <&mut Something as IntoIterator> implementation. It's not
       | "'pretty' syntax" so much as something that just _incidentally_
       | worked in that case because it was powerfully useful for
       | something else. Rather, `something` will come from an argument or
       | such, and will not be of type `Something`, but rather of type `
       | &Something` or `&mut Something`, and so `for x in something { ...
       | }` will automatically make x be of type `&Item` or `&mut Item`,
       | as appropriate.
       | 
       | That is:                 for x in something {           // What
       | type is x? Depends on what type something is.           //   *
       | something: Something      = x: Item           //   * something:
       | &Something     = x: &Item           //   * something: &mut
       | Something = x: &mut Item       }
       | 
       | (Mind you, I'm not entirely disagreeing with the article here.
       | The limits of IntoIterator's sugarness _are_ annoying, and I'm
       | not convinced it was worth having in the language. Much of the
       | rest of the article hinges upon wanting closures to be Ruby-style
       | blocks (or procs or whatever they are, I can't remember) instead
       | of closures; quite apart from introducing its own conceptual
       | problems to balance those it solves, I don't believe it could
       | coherently be implemented in a language with Rust's constraints,
       | especially with async.)
        
       | zamalek wrote:
       | The reason is that `map`, `for_each` and the rest are _not_
       | syntax and take closures. Closures in any language do not affect
       | the control flow of their containing function.
       | 
       | These two annoyances could be resolved with:
       | 
       | - async iterators https://github.com/rust-lang/rust/issues/79024
       | 
       | - an extension trait for iterators over results:
       | https://crates.io/crates/iterr
        
         | NegativeLatency wrote:
         | > Closures in any language do not affect the control flow of
         | their containing function.
         | 
         | In ruby returning from a proc will return from where the proc
         | was defined, usually resulting in a bug
         | 
         | https://stackoverflow.com/a/2325630
        
         | ohgodplsno wrote:
         | They can! Kotlin provides the crossinline modifier on closures,
         | which are allowed to return in their parent scope. While making
         | Iterable<T>.map take a crossinline function is not the wisest
         | idea, it has values in cases like Option<T>, allowing you to
         | write something along the lines of
         | optionList.map { option ->             val result =
         | option.getOrElse {                 Log.d("Well that's fucked
         | up")                 return@map null             }
         | ...more logic         }
         | 
         | and further in, result will not only have early returned, but
         | will also have the proper, unwrapped type.
        
         | anon291 wrote:
         | it could also be resolved by a proper effect system.
        
         | pwdisswordfishc wrote:
         | They do in Kotlin. Sometimes, anyway.
        
       | waffletower wrote:
       | The Rust apologists in the comments just don't get it. The fallow
       | year is a great idea, particularly if you can somehow get Rust
       | contributors to learn a Lisp and use it for that entire year
       | before getting together and improving syntax matters.
        
       | xiphias2 wrote:
       | Rust's composibiliity inside a method / function doesn't really
       | matter because it's easy to fix.
       | 
       | For me composibility problems with Rust come up with lifetimes,
       | that's why I would like to give names to lifetimes of variables
       | inside functions: as a step before extacting functions from it
       | that isn't always possible.
        
       | rom1v wrote:
       | The main criticism expressed in this blog post is that in Rust,
       | transforming a language construct into a seemingly equivalent one
       | (typically using .map()) works in some cases (described as the
       | "hello world" cases, which might be a bit exaggerated), but
       | sometimes not (due to lifetime, fallibility, async...).
       | 
       | For example, this code (real world example from two days ago):
       | fn main() {             let value = None;             let
       | _processed_value = match value {                 Some(value) =>
       | Some(process(value)),                 None => None,
       | };         }         fn process(_value: u32) {}
       | 
       | can be transformed into this equivalent form:
       | fn main() {             let value = None;             let
       | _processed_value = value.map(|value| process(value));         }
       | fn process(_value: u32) {}
       | 
       | But in async Rust, this code works:
       | #[tokio::main]         async fn main() {             let value =
       | None;             let _processed_value = match value {
       | Some(value) => Some(process(value).await),                 None
       | => None,             };         }         async fn
       | process(_value: u32) {}
       | 
       | But this one does not work:                   #[tokio::main]
       | async fn main() {             let value = None;             let
       | _processed_value = value.map(|value| process(value).await);
       | }         async fn process(_value: u32) {}
       | 
       | It fails with the following error:
       | error[E0728]: `await` is only allowed inside `async` functions
       | and blocks          --> src/main.rs:4:64           |         4 |
       | let _processed_value = value.map(|value| process(value).await);
       | |                                          -------
       | ^^^^^^ only allowed inside `async` functions and blocks
       | |                                          |           |
       | this is not `async`
       | 
       | This is basically what is described as the sandwich problem:
       | https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-ge...
       | 
       | I have to admit that I often need to refactor parts of code while
       | I'm writing Rust code due to such issues, so I understand the
       | author's point of view.
        
       | mustache_kimono wrote:
       | From the first few graphs:
       | 
       | > Rust has a nice pretty syntax for iterating:
       | for x in &mut something {             *x = (*x) * 2;         }
       | 
       | > EXCEPT when you need to do anything else to the iterator, then
       | its ugly:                   for (i, x) in
       | something.iter_mut().filter(|| {...}).enumerate() {
       | *x = (*x) * i         }
       | 
       | This is just so goofy. Who writes Rust like this? Wouldn't
       | everyone write:                   something
       | .iter_mut()             .filter(|| {...})
       | .enumerate()             .for_each(|(i, x)|) {                 *x
       | = (*x) * i             });
       | 
       | > There should be one -- and preferably only one --obvious way to
       | do it.
       | 
       | Overhead-wise, you can't expect everyone to take to the iterator
       | model right away. Sometimes you want a for loop, or you want a
       | for loop for right now.
       | 
       | I think Rust being multi-paradigm is a strength, with the
       | understanding that the preferred approach/model is the more
       | functional, more immutable iterator model for 95% of your use
       | cases.
       | 
       | > Again, the absolute lack of composability is astounding. Whats
       | the point of even having iterator methods if you can't use them
       | for real world usecases, where code is regularly fallable, so you
       | need to return a Result.
       | 
       | You can, you just haven't figured out how yet. This is
       | frustrating, but, gosh, you'll learn how sooner or later.
       | let y: Result<Vec<_>> = iterator.map(|x| {
       | x.some_fallible_method()         }).collect();
        
         | chrismorgan wrote:
         | > _This is just so goofy. Who writes Rust like this? Wouldn 't
         | everyone write: something.iter_mut()....().for_each(|(i, x)|) {
         | ... })_
         | 
         | No. As a user for just short of a decade, I have probably used
         | .for_each() less than a dozen times in total (grep over the
         | current state of most of the code I've ever written: three
         | matches). I know it's there, but it doesn't often feel right,
         | and I have certainly regularly used for loops where closure
         | capture rules mean that I _couldn't_ have used .for_each(),
         | which is strictly less expressive. Perhaps if I had come from a
         | language like Ruby I might use it more, but I came more from
         | Python and JavaScript.
         | 
         | I don't think the article showed a good _formatting_ , however;
         | I'd write it more like this:                 for (i, x) in
         | something           .iter_mut()           .filter(|| {...})
         | .enumerate()       {           *x = (*x) * i;       }
        
           | mustache_kimono wrote:
           | > I know it's there, but it doesn't often feel right, and I
           | have certainly regularly used for loops where closure capture
           | rules mean that I couldn't have used .for_each(), which is
           | strictly less expressive.
           | 
           | I think this is fine. I think Rust's strength is as a multi-
           | paradigm language. I would say for _this specific code_ , a
           | for_each() makes more sense than the alternatives (on the
           | margin), but for more complex use cases, where something like
           | a try_for_each() might be used, where you may need to return
           | 3 different error types, which for some reason one type can't
           | be easily coerced out of the closure, a for loop makes tons
           | of sense. But I think that's a 5% use case, and in the other
           | 95% of cases the iterator approach is to be preferred. For
           | many sound technical reasons, as well as for more squishy
           | code readability reasons (maps and folds and filters are
           | simply more readable to my eye).
           | 
           | Again I strictly don't have a problem with anything in your
           | comment, or anyone using for loops, but if I was writing your
           | for loop and my concern was the author's re: pretty syntax,
           | I'd write it as:                   let y_iter =
           | some.iter().built().up().like().this();              for x in
           | y_iter {             ...         }
        
         | sophacles wrote:
         | I write `for x in some.iter().built().up().like().this()` a
         | lot.
         | 
         | I think of the iterator building as mapping values from an
         | iterator to values to consume - the for loop consumes them.
         | for_each with side-effects just confuses me later in a lot of
         | those cases - it has it's place but so do for loops. (also,
         | closure issues around variable capture, moves, etc come into
         | play with for_each).
        
           | mustache_kimono wrote:
           | Mostly agree with the points made in your comment, but I
           | think the complaint of the article is "What's the point of
           | having the 'pretty' syntax if it only works in the simplest
           | of cases?"
           | 
           | If this is your concern, and you really must write it like
           | that, I'd break it up, into your iter and your loop.
           | let y_iter = some.iter().built().up().like().this();
           | for x in y_iter {             ...         }
        
             | tomjakubowski wrote:
             | yeah you can often come up with a good descriptive name for
             | that y_iter too, like `selected_widgets`
        
         | duped wrote:
         | Edit: this is totally wrong
         | 
         | No, because that doesn't evaluate the iterator.
         | let it = something             .iter_mut()
         | .filter(|item| ...)             .enumerate();              for
         | (i, x) in it {             ...         }
        
           | proto_lambda wrote:
           | Both for_each() and collect() into a Vec exhaust the
           | iterator.
        
       | vlovich123 wrote:
       | > But the teams working on language design need to SLOW DOWN and
       | focus on ergonomics and composability.
       | 
       | Except his concerns have already been explored.
       | 
       | > Rust experimented with all of these concepts at some point in
       | its history, it wasn't out of ignorance that they were excluded.
       | 
       | https://without.boats/blog/the-problem-of-effects/
       | 
       | I think there are people still broadly exploring how to make it
       | easier to write code that is generic over sync+async so that, for
       | example, the standard library only needs one implementation of
       | closures that can be either async or sync and work correctly. I
       | can't recall if they're focusing on just async effects or also
       | making it generic to Result. I can't find the docs page
       | describing this effort for some reason but I swear I read
       | something about it recently. The only other languages that have
       | it are
       | 
       | * Go via Goroutines: doesn't fit Rust's execution model of not
       | having GC / controlling threading / no-std environments
       | 
       | * Zig: not explicit enough which gives up optimality for the sake
       | of user convenience.
       | 
       | The Rust team _is_ moving slowly and carefully here to make sure
       | the solution they find balances ergonomics, complexity, and speed
       | that people would expect of a "Rusty" solution.
       | 
       | The author is basically complaining "drop all other work and
       | focus on MY inconvenience" which is short sighted. Different
       | people have different interests and capabilities. It's likely not
       | helpful to throw more people at the problem given that it's well
       | known, studied, and people are indeed trying to tackle it.
       | 
       | Edit: found it by way of remembering that I came to it via the
       | effing-mad crate which kind of gives you the composability albeit
       | only working on nightly. Rust is calling this keyword generics
       | https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-ge....
       | If they pull it off, this will let you write generics over
       | multiple effect systems transparently (failable, const, async,
       | etc) for composability. I definitely want to see them try at the
       | broader vision and deliver a high quality thing like they did
       | with GATs but recognize this is a huge l language feature that
       | will take time to get all the design and the fiddly
       | implementation bits correct. Hopefully there will be short term
       | features that can be delivered along the way to the full thing.
        
       | brigadier132 wrote:
       | """ for (i, x) in something.iter_mut().filter(||
       | {...}).enumerate() { _x = (_ x) * i } """
       | 
       | uh, what's the alternative in other languages that's superior for
       | what your trying to do here? Also I do not think this is ugly in
       | any way.
        
       | gpderetta wrote:
       | I don't think it is surprising. Nested functions do not capture
       | the return (and yield) continuation of their containing function,
       | so they can't invoke it. Rust is in good company here and I think
       | it is the right default for lambdas.
       | 
       | In principle you could have a variant scoped function syntax that
       | does that, but that will complicate both the language
       | implementation and syntax
        
       | awestroke wrote:
       | Small nitpicks presented as huge flaws by angry man
        
       | proto_lambda wrote:
       | Funny how the author both demands vague new ergonomics features
       | (with no explanation how they might actually work), and at the
       | same time demands the language team to stop adding new ergonomcis
       | features.
        
       | [deleted]
        
       | majewsky wrote:
       | It would help to have more complete examples here. Also, at least
       | one of the examples that are supposed to be correct clearly does
       | not compile (the loop calling push will fail because the Vec is
       | not declared as mut), which makes me doubt the assertions about
       | the other examples, especially because of the lack of context.
        
       | [deleted]
        
       | wokwokwok wrote:
       | Look, it's easy to beat up on an article like this and feel good
       | and smug about it.
       | 
       | ...but, let's not beat around the bush. Rust has some sharp edges
       | (1).
       | 
       | Why can't you clone a boxed function? I get it, Box<dyn Foo>
       | isn't sized, so you can't clone it. Ok! ...but you _can_ clone a
       | closure because a _raw_ closure is clone if the contents is
       | clone. Not when it 's boxed though.
       | 
       | Why _cant_ you express that a struct can contain an object _and a
       | reference to the object_ and have a  'static lifetime?
       | 
       | Yes, you can collect::<Result<...>>, but why can't you just use
       | the `?` sugar for it? There's only one _possible_ reason to end a
       | collect() with ? surely?
       | 
       | Yup. I get it.
       | 
       | Lot of reasons, writing languages is hard.
       | 
       | ...but, I dunno. I read this:
       | 
       | > the teams working on language design need to SLOW DOWN and
       | focus on ergonomics and composability.
       | 
       | ...and I think about `trait ?async` and I read the roadmap (2),
       | and I have to say, I'm sympathetic. More sympathetic then I
       | should be, perhaps, given how ragey the article is... but still.
       | 
       | I feel that pain too.
       | 
       | I use rust because it's nicer than C++. If it turns into a trash
       | can of features (like C++), why bother?
       | 
       | [1] -
       | https://stackoverflow.com/questions/tagged/rust?tab=Frequent [2]
       | - https://rust-lang.github.io/async-fundamentals-initiative/ro...
        
         | anon291 wrote:
         | We have plenty of languages which have slowed down. I recommend
         | the author use one of them. I will never understand the idea
         | that 'thing X doesn't work for me, thus those behind X need to
         | stop their work to accommodate me, despite the desires of
         | thousands of others'.
         | 
         | If you don't like Rust, don't use it. No one is forcing anyone.
         | In particular, languages such as C are incredibly slow moving
         | and well understood.
        
           | lenkite wrote:
           | > If you don't like Rust, don't use it. No one is forcing
           | anyone.
           | 
           | Actually, there is a lot of "force" and "forcing" behind
           | Rust.
        
           | wokwokwok wrote:
           | I think the point being made is that rust has _lots_ of users
           | now, and, perhaps, the "thousands of users" who want new
           | features now now now are not the majority of users any more.
           | 
           | Those are the demands of passionate early adopters.
           | 
           | There are *a lot* of people who don't like things that move
           | that quickly.
           | 
           | Are the rust teams sure they're serving their user base? Or
           | are they actually pandering to a vocal minority?
           | 
           | Are they actually addressing real pain points? Or just
           | working on cool stuff?
           | 
           | Have they even asked?
        
             | sophacles wrote:
             | Did you know you don't have to use every feature? You can
             | write perfectly valid and useful rust code without async or
             | const fn or GATs or ? or whatnot. Rust is really good at
             | additive features - the new features tend to be a strict
             | superset of what was already there, rather than a "you must
             | do it this different way now" set of changes.
             | 
             | You might not get to use the latest and greatest versions
             | of libraries that way, but that's the explicit goal of not
             | changing things anyway.
        
               | t8sr wrote:
               | But that's not true, extra features in the language have
               | a complexity cost. They slow down the compiler, they make
               | certain things ambiguous, add bugs, etc. It's not the
               | case that if I don't use GATs they have zero negative
               | impact on me.
        
               | sophacles wrote:
               | What things do gats make ambiguous? What bugs did they
               | expose to you that affected non gat use?
               | 
               | The compiler hasn't slowed down for them, in fact it was
               | sped up recently.
               | 
               | So I'm curious if you can point to instances of your
               | hypotheticals, sure that's all possible, but did it
               | actually happen or are you arguing in the sense of "if
               | you just write perfect code..." and similar grasping at
               | strawmen styles.
        
               | t8sr wrote:
               | If you're claiming that adding complexity to any software
               | project comes without trade-offs then I don't know what
               | to tell you.
               | 
               | I will not spend time researching and writing up specific
               | instances where existing complexity made something
               | harder, slower or more bug-prone. If you think that's not
               | a fact, then you're either not arguing in good faith or
               | don't have much experience with real software.
        
           | t8sr wrote:
           | > If you don't like Rust, don't use it. No one is forcing
           | anyone. In particular, languages such as C are incredibly
           | slow moving and well understood.
           | 
           | Rust's goal seems to be replacing C++, which kind of means
           | people will have to deal with it. If it's going to be in all
           | these domains, people have to be willing to take criticism
           | constructively.
           | 
           | Saying "you don't have to use all these features" doesn't
           | work either - just look at C++. It's the mess that it is
           | precisely because the language designers never learned to say
           | no to the next cool thing someone wanted to add in.
           | 
           | At the moment, Rust is opinionated and polarizing. Some
           | people who really like it try to push it everywhere. If and
           | as that happens, people who disagree with a lot of the design
           | choices Rust is making are going to have to have a voice in
           | the community.
        
           | netbioserror wrote:
           | >If you don't like Rust, don't use it. No one is forcing
           | anyone.
           | 
           | To be immediately followed by
           | 
           | >Marginal systems language (D/Nim/V/Go/Odin/Vale) is useless
           | bikeshedding and has no corporate backer, why would anyone
           | waste their time with this, they should use an established
           | well-known language.
           | 
           | PL debates are whiplash.
        
             | [deleted]
        
         | bsder wrote:
         | > Why can't you clone a boxed function? I get it, Box<dyn Foo>
         | isn't sized, so you can't clone it. Ok! ...but you can clone a
         | closure because a raw closure is clone if the contents is
         | clone. Not when it's boxed though.
         | 
         | And closures in Rust can do things that "fully named functions"
         | cannot. Let that one sink in.
        
       | dpbriggs wrote:
       | Both iterator examples have alternatives where you don't need a
       | for loop. Try using for_each and you can collect an iterator of
       | results into a single Result<_> you can then early return with.
       | 
       | Both of those are made cleaner by not using for loops.
        
         | cormacrelf wrote:
         | For clarity it is called try_for_each: https://doc.rust-
         | lang.org/stable/std/iter/trait.Iterator.htm...
         | 
         | The ? operator can be used both inside and after it like so:
         | iter.try_for_each(|_| { ... xxx?; ... })?;
        
           | vlovich123 wrote:
           | The author is right that this doesn't compose though. What
           | happens when the closure is async? Do we need an
           | tier.async_for_each? What if we want both? Does the library
           | author need to provide a try_async_for_each? This is a
           | combinatorial explosion for each additional effect added
           | which means it's not composable.
           | 
           | https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-
           | ge...
        
           | dpbriggs wrote:
           | Oh neat, I didn't know that existed. That's a combination of
           | what I was trying to say above.
           | 
           | If your for loop body is infallible then for_each is good
           | enough.
           | 
           | If you're trying to 'collect' some iterator into some data
           | structure T, you can collect into Result<T, _> and question
           | mark on that.
        
         | seanhunter wrote:
         | Not a rust person, but both of the for loop examples seem
         | extremely contrived.
         | 
         | If you're going to do .filter , presumably you can do a .map or
         | something and just supply a small lambda which does what was in
         | the body of the for loop in the example?
         | 
         | Alternatively you could just do the filter part using an if
         | statement in the for loop. Either of those syntaxes would be
         | much more straightforward than doing half of one and half of
         | the other. It just doesn't make sense to do that and point at
         | the result as being unergonomic.
         | 
         | Secondly the fact that for and for_each have different
         | characteristics is as someone else has pointed out
         | fundamentally just that blocks and functions are different
         | things. ie it breaks the author's mental model because their
         | mental model is just wrong.
        
       ___________________________________________________________________
       (page generated 2023-04-06 23:02 UTC)