5 ms·
It looks like I'm on the minority here, but I generally like Rust's syntax and think it's pretty readable. Of course, when you use generics, lifetimes, closure
by bilkow 4y ago
It looks like I'm on the minority here, but I generally like Rust's syntax and think it's pretty readable.
Of course, when you use generics, lifetimes, closures, etc, all on the same line it can become hard to read. But on my experience on "high level" application code, it isn't usually like that. The hardest thing to grep at first for me, coming from python, was the :: for navigating namespaces/modules.
I also find functional style a lot easier to read than Python, because of chaining (dot notation) and the closure syntax.
Python:
array = [1, 0, 2, 3]
new_array = map(
lambda x: x * 2,
filter(
lambda x: x != 0,
array
)
)
Rust:
let array = [1, 0, 2, 3];
let new_vec: Vec<_> = array.into_iter()
.filter(|&x| x != 0)
.map(|x| x * 2)
.collect();
I mean, I kind of agree to the criticism, specially when it comes to macros and lifetimes, but I also feel like that's more applicable for low level code or code that uses lots of features that just aren't available in e.g. C, Python or Go.
Edit: Collected iterator into Vec
- Too 4y agoI want to like rust but the fact that there are replies with 9 different ways to write that loop posted here alone, and no consensus on which one is the idiomatic, is not a good sign.
- rajman187 4y agominor point but your python code creates a generator here not an array, you'd have to wrap it in a `list()` to get the same data type and be able to for example assert its length (of course you can just iterate over the generator)
- xigoi 4y agoYou can only do this chaining when everything happens to be a method. Compare to Nim or D, where you can chain arbitrary procedures like this.
- klodolph 4y agoThere are people who write Python code like that, but it's an extreme minority. Here's the more likely way: array = [1, 0, 2, 3] new_array = [x * 2 for x in array if x != 0] Just as a matter of style, few Python programmers will use lambda outside something like this: array = [...] arry.sort(key=lambda ...)
- bilkow 4y agoI guess you're right, list/generator comprehensions are the idiomatic way to filter and map in python, with the caveat of needing to have it all in a single expression (the same goes for lambda, actually). I still feel like chained methods are easier to read/understand, but list comprehensions aren't that bad.
- dralley 4y agoEven in Rust I don't like chains that go beyond ~4 operations. At some point it becomes clearer when expressed as a loop.
- Iwan-Zotow 4y ago> with the caveat of needing to have it all in a single expression (the same goes for lambda, actually). one could use multiple expressions in lambda in (modern) Python
- vgel 4y agoDo you mean using the walrus operator? Because unless I missed a recent PEP, I don't know of a way to do this without something hacky like that.
- Iwan-Zotow 4y agoyes x = 1 y = 2 q = list(map(lambda t: ( tx := tx, ty := ty, tx+ty )[-1], [1, 2, 3])) print(q)
- foolfoolz 4y agoi have always felt the backwards nature of list comprehensions makes them very hard to read
- zanellato19 4y agome too. Its one of the things that I kinda dislike in Python.
- the__alchemist 4y agoI think part of this comes down to: Does your Rust code make heavy use of generics? I find myself deliberately avoiding generics and libraries that use them, due to the complexity they add. Not just syntactic noise, but complicated APIs that must be explicitly documented; rust Doc is ineffective with documenting what arguments are accepted in functions and structs that use generics. See also: Async.
- klabb3 4y ago> See also: Async. Ah, the good ole generic, nested, self-referencing, unnameable state machine generator.
- nemothekid 4y ago1. I don't think your Python example if fair. I think new_array = [x*2 for x in array if x != 0] is much more common. 2. In your example, `new_array` is an iterator; if you need to transform that into an actual container, your rust code becomes: let new_array = array.into_iter() .filter(|&x| x != 0) .map(|x| x * 2) .collect::<Vec<_>>(); And there your generic types rear their ugly head, compared to the one liner in python.
- bilkow 4y agoOh, yeah, you're right! If you want to collect into a Vec you may need to specify the type, but usually, you can just call `.collect()` and the compiler will infer the correct type (as I suppose you're collecting it to use or return). If it can't infer, it's idiomatic to just give it a hint (no need for turbofish): let new_vec: Vec<_> = array.into_iter() .filter(|&x| x != 0) .map(|x| x * 2) .collect(); I don't think that's ugly or unreadable. About the Python list comprehension, I answered your sibling, I think you're both right but it also does have it's limitations and that may be personal, but I find chained methods easier to read/understand.
- saghm 4y agoI generally just write it like this: let new_array: Vec<_> = array.into_iter() .filter(|&x| x != 0) .map(|x| x * 2) .collect(); I'm actually a bit confused by the `&x` given that `into_iter()` is used, which would take ownership of the array values, but assuming that it was supposed to be just `iter()` (or that it's an array of &i32 or something I guess), you're going to be copying the integer when dereferencing, so I'd probably just use `Iterator::copied` if I was worried about too many symbols being unreadable: let new_array: Vec<_> = array.iter() .copied() .filter(|x| x != 0) .map(|x| x * 2) .collect(); There's also `Iterator::filter_map` to combine `filter` and `map`, although that might end up seeming less readable to some due to the need for an Option, and due to the laziness of iterators, it will be collected in a single pass either way: let new_array: Vec<_> = array.iter() .copied() .filter_map(|x| if x == 0 { None } else { Some(x * 2) }) .collect(); This is definitely more verbose than Python, but that's because the syntax needs to disambiguate between owned and copied values and account for static types (e.g. needing to annotate to specify the return type of `collect`, since you could be collecting into almost any collection type you want). It's probably not possible to handle all those cases with syntax as minimal as Python, but if you are fine with not having fine-grained control over that, it's possible to define that simpler syntax with a macro! There seem to be a lot of these published so far (https://crates.io/search?q=comprehension https://crates.io/search?q=comprehension), but to pick one that supports the exact same syntax in the Python example, https://crates.io/crates/comprende https://crates.io/crates/comprende seems to do the trick: let array = [0, 1, 2, 3]; let new_array = c![x * 2 for x in array if x != 0]; println!("{:?}", new_array); // Prints [2, 4, 6] I'm not trying to argue that Rust is a 1:1 replacement to Python or that if Python suits your needs, you shouldn't use it; I think it's worth pointing out that Rust has more complex syntax for a reason though, and that it has surprisingly good support for syntactic sugar that lets you trade some control for expressiveness that can alleviate some of the pain you might otherwise run into.