I feel most things in Rust are harder to do, at least at first sight. A compromise for the enhanced safeness I guess. Could give it a try again sometime.
It seems to me that it's not that Rust is hard, it's that all programming is hard but Rust exposes the complexity so we can make a more informed decision about the trade offs of safety and performance etc, while other languages tend to hide the complexity (null reference, race conditions, etc).
Why Rust closures are somewhat hard
11–20 of 92 posts
Re: Why Rust closures are somewhat hard
#12The fact that you can even do stuff like this in Rust is amazing. It’s simultaneously: relatively clean code, very efficient and type-safe. I like it.
Not sure I agree with "relatively clean code" when one of the examples show "fn compose (f1: impl Fn(T)->T, f2: impl Fn(T)->T) -> impl Fn(T)->T {" which is just a mix-match of keywords and other things, with tons of syntax embedded in just one line. But as always, depends on where you come from. I mostly deal with lisp languages nowadays, so guessing it's just my view that the line quoted above seems complex enough t…
The equivalent in Haskell would be "compose :: (a -> a) -> (a -> a) -> a -> a", which lacks "impl" because it boxes all closures (each closure in Rust has its own type), and lacks "Fn" because it doesn't care about what kind of access the closure needs to its context (Rust distinguishes between Fn, FnMut and FnOnce). Still, the Rust is only a tiny bit more complicated than the Haskell.
Re: Why Rust closures are somewhat hard
#13I don't understand the intuition of closures and they turn me off to languages immediately. They feel like a hack from someone who didn't want to store a copy of a parent-scope variable within a function. The idea that I can touch variables that have gone out of scope (and that have ostensibly been GC'd) makes me feel that it is impossible to reason about variable lifetimes when dealing with closures. Is there some p…
It's sort of a contrary opinion but I prefer to be explicit about state, and closures are sort of silently bundling up state for you behind the scenes. It's not very apparent from the syntax.
I think languages should have a lighter-weight syntax for classes and that would subsume many use cases for closures.
-----
While I've never programmed in PHP, it appears PHP actually has a pretty nice solution for this problem! You declare the variables captured using 'use':
https://stackoverflow.com/questions/1065188/in-php-what-is-a...
$callback =
function ($quantity, $product) use ($tax, &$total)
In other languages the capture of 'tax' and 'total' are implicit.Re: Why Rust closures are somewhat hard
#14The fact that you can even do stuff like this in Rust is amazing. It’s simultaneously: relatively clean code, very efficient and type-safe. I like it.
Not sure I agree with "relatively clean code" when one of the examples show "fn compose (f1: impl Fn(T)->T, f2: impl Fn(T)->T) -> impl Fn(T)->T {" which is just a mix-match of keywords and other things, with tons of syntax embedded in just one line. But as always, depends on where you come from. I mostly deal with lisp languages nowadays, so guessing it's just my view that the line quoted above seems complex enough t…
#![feature(trait_alias)]
trait Map = Fn(T) -> T;
fn compose (f1: impl Map, f2: impl Map) -> impl Map {
|n| f1(f2(n))
}Re: Why Rust closures are somewhat hard
#15I don't understand the intuition of closures and they turn me off to languages immediately. They feel like a hack from someone who didn't want to store a copy of a parent-scope variable within a function. The idea that I can touch variables that have gone out of scope (and that have ostensibly been GC'd) makes me feel that it is impossible to reason about variable lifetimes when dealing with closures. Is there some p…
As with any other syntactic sugar, it's easy to reason about the behavior once you know what the syntax desugars into.
One classic example I see with beginning programmers all the time is the order of the parts of a for loop.
e.g.
for(initialization statement; bounds check expression; increment statement) {
...
}
is just syntactic sugar for: {
initialization statement;
while (bounds check expression) {
...
increment statement;
}
}
But once you understand what a for-loop desugars into, it's trivial to use and reason about.Closures aren't much different. In Rust, if anything, they're less magic, because you have to be explicit about the lifetime of the closure relative to the references it's borrowing.
Re: Why Rust closures are somewhat hard
#16I don't understand the intuition of closures and they turn me off to languages immediately. They feel like a hack from someone who didn't want to store a copy of a parent-scope variable within a function. The idea that I can touch variables that have gone out of scope (and that have ostensibly been GC'd) makes me feel that it is impossible to reason about variable lifetimes when dealing with closures. Is there some p…
Re: Why Rust closures are somewhat hard
#17I feel most things in Rust are harder to do, at least at first sight. A compromise for the enhanced safeness I guess. Could give it a try again sometime.
Re: Why Rust closures are somewhat hard
#18I don't understand the intuition of closures and they turn me off to languages immediately. They feel like a hack from someone who didn't want to store a copy of a parent-scope variable within a function. The idea that I can touch variables that have gone out of scope (and that have ostensibly been GC'd) makes me feel that it is impossible to reason about variable lifetimes when dealing with closures. Is there some p…
#include
#include
auto foo(double a) -> std::function {
return [&a](auto x) {
return x + a;
};
}
int main() {
auto f = foo(2.0);
std::cout
This code is bad because the function returned by `foo` depends on a reference to `a`, which is freed at the end of `foo`. Running this gives: 3
1
which is bad. Rust has the advantage of finding such errors. For example, the following Rust code: fn foo(a: f64) -> impl Fn(f64) -> f64 {
|x: f64| -> f64 { x + a }
}
fn main() {
let f = foo(2.0);
println!("{}", f(1.0));
println!("{}", f(1.0));
}
fails to compile with the error: error[E0597]: `a` does not live long enough
--> test01.rs:2:27
|
1 | fn foo(a: f64) -> impl Fn(f64) -> f64 {
| ------------------- opaque type requires that `a` is borrowed for `'static`
2 | |x: f64| -> f64 { x + a }
| --------------- ^ borrowed value does not live long enough
| |
| value captured here
3 | }
| - `a` dropped here while still borrowed
That forces us to correct the code by adding the move annotation to the the closure, which moves ownership of a to the closure itself: fn foo(a: f64) -> impl Fn(f64) -> f64 {
move |x: f64| -> f64 { x + a }
}
fn main() {
let f = foo(2.0);
println!("{}", f(1.0));
println!("{}", f(1.0));
}
This gives the correct result: 3
3
Of course, all of this nuance can be avoided in a garbage collected language. For example, in OCaml, we can write: let foo a =
fun x -> x +. a
in
let f = foo 2.0 in
Printf.printf "%f\n" (f 1.0);
Printf.printf "%f\n" (f 1.0);
which also returns the correct result: 3.000000
3.000000
More generally, closures are nice because it's a useful feature to return a function from a function. For example, we may want call a function parameterized on some parameters repeatedly and we don't want to have to pass all of the parameters each time we invoke the function. Functional programming and closures allow for a variety of other programming techniques as well.Beyond that, my point is that a garbage collector can track the use of variables and memory extremely well, which makes the process of constructing closures moderately easy. We can have closures without garbage collection, but then we must be careful about who and what owns this memory. C++ does not enforce this, but Rust does, which is one of its benefits.
Re: Why Rust closures are somewhat hard
#19I don't understand the intuition of closures and they turn me off to languages immediately. They feel like a hack from someone who didn't want to store a copy of a parent-scope variable within a function. The idea that I can touch variables that have gone out of scope (and that have ostensibly been GC'd) makes me feel that it is impossible to reason about variable lifetimes when dealing with closures. Is there some p…
In the article they discuss that Rust closures are just sugar for structs that work exactly as you describe. As with any other syntactic sugar, it's easy to reason about the behavior once you know what the syntax desugars into. One classic example I see with beginning programmers all the time is the order of the parts of a for loop. e.g. for(initialization statement; bounds check expression; increment statement) { ..…
There's just a subtle detail to keep in mind here: both "continue" and "break" are syntactic sugar for "goto", and when converting a "for" loop into a "while" loop, you have to also convert "continue" to a "goto" to a label just before the increment statement.
Re: Why Rust closures are somewhat hard
#20I don't understand the intuition of closures and they turn me off to languages immediately. They feel like a hack from someone who didn't want to store a copy of a parent-scope variable within a function. The idea that I can touch variables that have gone out of scope (and that have ostensibly been GC'd) makes me feel that it is impossible to reason about variable lifetimes when dealing with closures. Is there some p…