12 ms·
Rust's Poor Composability
- gpderetta 4y agoI 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 4y agoSmall nitpicks presented as huge flaws by angry man
- dang 4y agoCan you please not cross into personal attack, regardless of how wrong someone is or you feel they are? You may not owe Rust critics better, but you owe this community better if you're participating in it. https://news.ycombinator.com/newsguidelines.html https://news.ycombinator.com/newsguidelines.html
- majewsky 4y agoIt 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.
- dpbriggs 4y agoBoth 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 4y agoFor clarity it is called try_for_each: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.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 4y agoThe 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-generics.html https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-ge...
- dpbriggs 4y agoOh 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 4y agoNot 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.
- jamincan 4y ago> 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 4y ago> 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 4y agolet-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 4y agoIronically, 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.)
- binarymax 4y agoThe 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 4y agoReads 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 4y agoThe 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 4y ago> 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 4y agoRust 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.
- NoraCodes 4y ago> 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 4y agoIt was with deadpool, I don't recall the exact underlying reasons though: https://github.com/bikeshedder/deadpool/issues/104#issuecomment-879447081 https://github.com/bikeshedder/deadpool/issues/104#issuecomm...
- jmillikin 4y agoRust 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 4y agoHonestly 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.
- lang_agnostic 4y agoIs 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 4y agoI 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 4y agoIn 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 4y ago>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.
- timerol 4y agohttps://without.boats/blog/the-registers-of-rust/ 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.
- marcosdumay 4y ago> 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.
- coldtea 4y ago>Anything that can't be done gets exactly 0% of the usage. I don't know where you got 90% from. I'm not sure what the first sentence means. Plain iteration across all elements is the most common case for "for". That's where I get the 90% from (meaning, most of the time you'll be doing that). for/in without filters etc., is not something that's just for "hello world" style programs (only useful as a very crude example that you wont find in any real program) - as the author describes it. It's the very opposite: what you will most commony use in every program you write multiple times.
- overthrow 4y ago> 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.
- tempusr 4y agoIt 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.
- unshavedyak 4y agoYea, 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.
- mcronce 4y agoAlso, let res = iterator.iter().map(|x| x.foo()).collect::<Result<Vec<_>, _>>()?;
- fnordpiglet 4y agoI love rust because it satisfies my inner perl
- justinpombrio 4y agoThis 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 4y agoI'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?
- holmium 4y agoyou 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&edition=2021 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- Pxtl 4y agoWell, 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.
- MrBuddyCasino 4y agoThe Kotlin compiler can handle chains of potentially().nullable()?.invocations(). Is the Rust early-error returning not a similar case, or am I missing something?
- gpm 4y agoRust does the same.
- NoraCodes 4y agoYeah, 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&edition=2021 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- MrBuddyCasino 4y agoI 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 4y agoIt doesn't have to be specified explicitly in this case, it can be inferred. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7bf4c01c6cfe0a2b200d9a4f5133860e 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&edition=2021&gist=3b4b8cf497b8cdc31a479d40b4b0b72f https://play.rust-lang.org/?version=stable&mode=debug&editio...
- Alifatisk 4y agoLove this kind of articles, keep it up!
- zamalek 4y agoThe 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 https://github.com/rust-lang/rust/issues/79024 - an extension trait for iterators over results: https://crates.io/crates/iterr https://crates.io/crates/iterr
- NegativeLatency 4y ago> 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 https://stackoverflow.com/a/2325630
- pwdisswordfishc 4y agoThey do in Kotlin. Sometimes, anyway.
- anon291 4y agoit could also be resolved by a proper effect system.
- ohgodplsno 4y agoThey 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.
- firstlink 4y ago> Closures in any language do not affect the control flow of their containing function. This isn't true, and it's a damn shame that it is so close to being true. The biggest feature I miss from Common Lisp is that closures capture blocks and tags, and this is but the simplest form of cross-function control flow available in PL design. Algebraic effects are the next big thing.
- vlovich123 4y ago> 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/ 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-generics.html 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.
- xiphias2 4y agoRust'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.
- mustache_kimono 4y agoFrom 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();
- duped 4y agoEdit: 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 4y agoBoth for_each() and collect() into a Vec exhaust the iterator.
- sophacles 4y agoI 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).
- proto_lambda 4y agoFunny 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 4y ago[deleted]
- deleted 4y ago[deleted]
- stephc_int13 4y agoI 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.
- ModernMech 4y agoSystems 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.
- Capricorn2481 4y ago>All that specification comes with a lot of syntax CommonLisp can do this without that syntax
- sealeck 4y agoCommonLisp is not really suitable for systems programming (hence the lack of systems software written in it).
- jedisct1 4y agoZig is very readable, and doesn't use many sigils at all.
- omgtehlion 4y ago.{ zig has a lot of .{ } }
- darthrupert 4y agoWriting 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.
- ohgodplsno 4y agoPythonistas 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 4y ago> function(compositions(are(written(in(a(style(that(makes(me(want(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))))))))))))))
- tmtvl 4y agoOr even... (-> function compositions are written in a style that makes me want to go back to lisp)
- chrismorgan 4y ago> 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.)
- brigadier132 4y ago""" 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.
- wokwokwok 4y agoLook, 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 https://stackoverflow.com/questions/tagged/rust?tab=Frequent [2] - https://rust-lang.github.io/async-fundamentals-initiative/roadmap.html https://rust-lang.github.io/async-fundamentals-initiative/ro...
- anon291 4y agoWe 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.
- wokwokwok 4y agoI 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?
- littlestymaar 4y ago> 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?
- agumonkey 4y agof-rust rated
- nu11ptr 4y agoIt 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.
- t43562 4y ago> 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 :-)
- FpUser 4y agoSpeaking 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 4y agoThe 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 {}.
- waffletower 4y agoThe 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.
- rom1v 4y agoThe 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-generics.html#a-taste-of-trouble-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.
- kajaktum 4y ago> 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 4y ago> How a "hacker news" website not supporting code snippet is beyond me. Indent your code by two spaces.
- xiaodai 4y agoOne day Rust will be as hated as C++