Shadowing a binding with a binding of the same type can easily lead to errors, though. A better approach is to eliminate the meaningless bindings entirely via method chaining. You would also get better type safety and better autocomplete ergonomics.
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
struct Position(u32, u32);
impl Position {
fn rotate(self, angle: f32) -> Position { ... }
fn add_offset(self, offset: Offset) -> Position { ... }
}
let position = Position(5, 8).rotate(angle).add_offset(offset);
I'm surprised nobody mentioned closures. I loved them in other languages but found them horrible in Rust when I started Rust: Being unable to name their type, having to use generics to accept closures, issues with moving vs borrowing, etc.
I personally found the postfix syntax to be very intuitive, despite having a lot of exposure to prefix await in Javascript. It feels "Rusty" to me, and I'm glad it was chosen.
Are you sure? How can I translate the following trivial example to Rust:
void foo(auto f) {
f(4, 2);
f("4"s, "2"s);
}
int main() {
int called = 0;
foo([&called](auto x, auto y) {
std::cout << "Called " << ++called << std::endl;
std::cout << (x + y) << std::endl;
});
}
I'm not talking about the use of auto here (that may be a problem or an advantage depending on your POV), but about actual generic closures. I was under impression that generic closures are not possible in Rust, but maybe I just don't know something?
That one is funny: I have seen so many who dislike it at first, but appreciate it later.
Also: it's really strange to me that other languages, almost universally, pick the prefix form even if it's ideomatic to write foo.filter(…).map(…).convert(…) in these languages.
Isn't await similar to other “transformations”? Yes, I know, “under the hood” they are very different, but conceptually… filter/map/etc transform the value, await does that, too (except it does in a much more involved and “magical” way, but conceptually it's still transformation of promise to deliver some entity into an actual entity).
Your original comment said that in Rust you "have to use generics to accept closures" (i.e. fn foo(f: impl Fn())) and I said C++ has the same problem... Which you just demonstrated with void foo(auto f).
Note that it lists few problems but implies there are more.
You said “FWIW, you have all the same problems in C++” as “C++ closures have all the problems which Rust closures have”. That's definitely true about inability to name their types, and issues with moving vs borrowing are, probably, even worse in C++, but at least C++ have generic closures which makes situation symmetric (e.g. you can easily pass closures into other closures) while in Rust closures are much more limited.
You can pass closure into generic but not in normal function in C++, but you also can pass generic into closure (you just have to wrap it into trial closure), which is not true in Rust. Like this:
"self" in methods signatures. It was pushing me away quite hard initially, but later I've understood that it's not as ugly and pointless as I initially thought. It is still ugly, but I can't imagine better alternative (although I'm not saying that it's impossible to improve it).
The "&self is short for self: &Self" thing is a bit weird (when contrast with how patterns work), but I got used to it pretty fast. As for the length, I like four letters for an arguably silly reason: it works great with method chaining and a four-space indent.
At first, I was convinced this was going to be memory leaks / memory loops everywhere. Turns out to not have been a problem, and "breaking loops" in object hierarchies has also helped me clarify many things.
Now, I can't imagine losing RAII in exchange for GC.
Speaking of async: I've been really diving into Rust's async ecosystem this week and it definitely falls into the category of "awful at first, but appreciated it later". It seemed really confusing and messy, and I procrastinated going down the rabbithole of really understanding how async worked and the whole ecosystem around it. But I think the approach chosen gives the programmer lots of control, without sacrificing performance or safety. It's a lot to learn up-front, but so are many things in Rust like string handling. I think the team made the right trade-offs here.
I'm also confident that async will only get easier to use over time. One thing that would help a lot is simply having an up-to-date guide to the ecosystem. The Rust Async book could be better, as it seems very incomplete. [Edit: it seems this book does in fact have a chapter called "The Async Ecosystem."] I had to do a lot of Kagi'ing and consulting disparate resources.
I might change my mind later as I only yesterday feel like I've grokked async, and I apologize in advance for any inaccuracies in this post.
That type is useless. &mut T is not Copy, so you can't effectively do anything with a Cell<&mut T> except for constructing and destroying it (either by take()ing/swap()ping from it or by dropping it).
The entire point of Cell is that you can copy in and out of it, but with anything that contains a !Copy type such as &mut T or Option<&mut T> etc., that's impossible. Not being able to copy from it means that you basically lose all the additional capabilities you would use Cell for, and so you would just use the Option<&mut T> in the first place.
You can still replace(), swap(), set(), and take() the cell with a shared reference, even if the underlying type isn’t Copy. All of those operations require an exclusive reference if you have an unwrapped Option<_>.