I had a hard time with closures when doing async programming. Is that something one should avoid?
Async code is hard to reason about. Async closures is what I like most about JavaScript (JS) though. Because JS pass all variables by value they get frozen in time when passed to a function. This make async code easier to reason about, as all variables are immutable.
A guide to closures in Rust
31–40 of 102 posts
Re: A guide to closures in Rust
#32Earlier quoted context omitted.
Sum types let you have default values without pervasive nil. That’s not new either, it’s been done since at least ML. Separate address spaces are trivial when you have a runtime, just look at Erlang. Go could’ve also copied Erlang’s supervision trees to ensure safe and bounded goroutine lifetime. For all their impressive achievements, Go’s creators suffer from extreme NIH syndrome. Just look at Plan 9.
When you see the performace of Erlang, you understand why Go did not want to go this way.
Go even shipped with segmented stacks! Also separating goroutine heaps would've been cheap by comparison. It would've also made it easier to achieve good garbage collector throughput, which Go still struggles with to this day.
Go could've also had the `go` keyword work differently, by requiring/returning some kind of handle that must be waited to potentially panic, or explicitly ignored. It would've made it impossible to incorrectly use WaitGroups, without any additional runtime overhead.
Don't get me wrong, Go is still a useful language. It's just frustrating that it failed to be great language for no good reason.
Re: A guide to closures in Rust
#33Earlier quoted context omitted.
It would’ve been trivial to make the language disallow nil, separate goroutine address spaces, crash the process on unhandled panic in any goroutine, etc. The reason Go annoys people is the unforced errors. Sure, it gets a lot right. But what it gets wrong had solutions long before Go existed and those solutions were wilfully ignored.
> It would’ve been trivial to make the language disallow nil Which would interfere with Go's philosophy that zero should be a valid (usable) state for types. What would be the default value of a reference type without nil? > separate goroutine address spaces This is not trivial to implement. The only way I can think of is to start each goroutine in a new process, which brings along other downsides - everything must b…
Re: A guide to closures in Rust
#34Earlier quoted context omitted.
> And sure, plenty of people will tell if you do it "right" you'll have no issues. Go is inspired a lot by CSP, and allows you to apply CSP calculus when using goroutines and channels. It doesn't make it impossible to make mistakes, but it does give you tools needed to reason about your concurrent program, which is more than can be said of most other languages' concurrency support.
And yet goroutines share the same address space, don’t have a good way for the parent to wait for them and even panic silently. There’s a reason many projects ban the go keyword in favour of a wrapper that fixes some of this. Go requires constant vigilance, sadly.
Re: A guide to closures in Rust
#35> I'll explain what the move keyword does later in the article. For now just trust me that it is needed for the code to compile. That does not take a lot of trust, after a few rounds with that compiler. At least error messages are good with the Rust compiler. I've got very limited experience with Rust, but it does seem like a language with a massive threshold for beginners.
Let me give you my opinion who just started learning 3 weeks ago. And I needed around 1 week until I could write a simple parser by hand and around 2 more until I am now being quite productive in this new language.
It is actually very simple go get into, depending on your experience. What do mean by 'threshold for beginners', what are beginners for you. Do you mean someone learning their first programming language or someone already proficient with one or multiple trying to learn Rust now? Because a lot of the features of Rust are present in other language and your mental models will be similar enough for them in Rust accelerating your learning curve. Of course someone new to programming will have problems as the features that are unique to rust (borrow, move) in addition to everything else leads to a lot of overhead sure.
My experience is with Java, Python and Javascript/Typescript as I know them sufficiently well to create programs and I also dabbled with many other languages just trying out how they feel (Scheme, Racket, Nim, Go, Haskell, C, C++, Ruby, Smalltalk). When starting with Rust it took very little time to get comfortable. The language feels very well designed and Option, Result type are very logical to use for return types. Many languages have these so depending with what you are familiar with this might be new to you but it is surely not more complicated than explaining try/catch blocks. Of course dealing with Option/Result is a bit uncomfortable in the beginning but If you just browse a bit through the standard library and learn about the `?` operator you see many patterns how to deal with them in an efficient manner (map, map_or_else, or, or_else, flatten, unwrap, unwrap_or,...). Rust has closures but now you find them in almost any language so it is not really surprising (maybe only nuances that come up with the borrow checker). Most of the standard traits and enums are quite straightforward such as `Default`, `From`, `FromStr`, `Iterator`, `Clone`, `PartialEq`, all the operator traits.
Also the borrow checker and some lifetime compiler errors are for sure a thing that can lead to thresholds and for my time spent with the language I would say there are many things I will need to learn to fully be able to grok the language. However, most of the time you can circumvent these problems by just not using lifetimes in your own code and by cloning variables. So for people that want to write really efficient code you can do this without cloning variables if you have a better understanding of them but as a beginner you have a simple escape hatch most of the time and allows you to gradually gain a better model of lifetimes/borrowing rules.
I think the only really threshold is the trait object problem. If you want to build any kind of nested structure or if you want some kind of "polymorphism" which is a natural reaction depending which programming language you have used. You will need to deal with understanding them and this might take some time.
Re: A guide to closures in Rust
#36Earlier quoted context omitted.
And yet goroutines share the same address space, don’t have a good way for the parent to wait for them and even panic silently. There’s a reason many projects ban the go keyword in favour of a wrapper that fixes some of this. Go requires constant vigilance, sadly.
Can’t the parent wait by simply using the waitgroup sync functionality? That’s what I’ve been doing in my greenfield Go project at work.
In practice, where I work we disallow the go keyword in review and require use of a wrapper that takes in a closure and also is a wait group. It’s much harder to get wrong.
Re: A guide to closures in Rust
#37Earlier quoted context omitted.
Sum types are optional. And if you make them obligatory, they become just syntax sugar for nil. What would be the default value of a non-sum reference type, without nil? Erlang's "address space isolation" is not real address space isolation, only semantic. Erlang "processes" are all still in the same address space, but the language semantics doesn't allow them to interfere with one another. You can implement bounded…
An Optional defaulting to None when created can still fail to compile if you don't check whether it's None when you use it. Same with a Result defaulting to an Error. The problem with nil for any pointer or interface is that you can compile code dereferencing it without first checking if it's nil.
Yes, Go doesn't stop you from making mistakes. I think we've already rehashed that a couple of times...
Re: A guide to closures in Rust
#38Re: A guide to closures in Rust
#39> let add_closure = |a, b| a + b; let sixty_six = add_closure(42, 24); Woah woah, how did it not occur to me that I could just…construct a closure that directly in Rust? This feels like one of those “incredibly obvious and reasonable in hindsight” things. I don’t know why I was labouring under the assumption they could only be invoked in very special and specific places, but it’s good to know I was wrong.
Re: A guide to closures in Rust
#40> let add_closure = |a, b| a + b; let sixty_six = add_closure(42, 24); Woah woah, how did it not occur to me that I could just…construct a closure that directly in Rust? This feels like one of those “incredibly obvious and reasonable in hindsight” things. I don’t know why I was labouring under the assumption they could only be invoked in very special and specific places, but it’s good to know I was wrong.