Live data from Hacker News

A guide to closures in Rust

hashrust.com

61–70 of 102 posts

Re: A guide to closures in Rust

#61

> A closure is like an anonymous function. I like the article overall, but I have a bit of a pedantic note about this opening line. Using "like" suggests that there is some similarity that can be used to understand one thing from the other, but in truth these are two distinct (though related) technical terms, and I think phrasing this relationship as a simile may actually obstruct understanding rather than enable it.…

In some language all functions capture their environment, making that original statement more true than you make it out to be. Personally I think the term closure should be banned, because it only adds confusion. There are just functions, some named some anonymous, some capturing the environment, some not, some being disallowed by some constraints from the host language. The term closure cuts through this space in a…

I don't think it's ad-hoc so much as implementation focused.

Re: A guide to closures in Rust

#62

> A closure is like an anonymous function. I like the article overall, but I have a bit of a pedantic note about this opening line. Using "like" suggests that there is some similarity that can be used to understand one thing from the other, but in truth these are two distinct (though related) technical terms, and I think phrasing this relationship as a simile may actually obstruct understanding rather than enable it.…

In some language all functions capture their environment, making that original statement more true than you make it out to be. Personally I think the term closure should be banned, because it only adds confusion. There are just functions, some named some anonymous, some capturing the environment, some not, some being disallowed by some constraints from the host language. The term closure cuts through this space in a…

> In some language all functions capture their environment

In some pieces of art all quadrilaterals are squares, but this doesn't mean the two terms are somehow equivalent and therefore one of the terms should be done away with.

There is a consistent technical distinction between "functions" and "closures".

> There are just functions, some named some anonymous, some capturing the environment, some not

No. Functions do not capture environments. Functions don't know what environments are. If you're talking about functions and environments together, you're talking about closures. That's really all there is to it.

Re: A guide to closures in Rust

#63
post #55

> A closure is like an anonymous function. I like the article overall, but I have a bit of a pedantic note about this opening line. Using "like" suggests that there is some similarity that can be used to understand one thing from the other, but in truth these are two distinct (though related) technical terms, and I think phrasing this relationship as a simile may actually obstruct understanding rather than enable it.…

I would further elaborate on your point by saying that as someone who likes to hang out on programming fora and help people as I was helped when I was young, this terminology confusion causes real confusion in the field as well. Many programmers-in-training find it confusing as to why whether or not a function has a name seems to be tied to whether or not it has an environment (not that they phrase it that way but th…

Oh absolutely, it's one of many such conflations that are common among the majority of real-world programmers. And you're right that a big part of the problem is poorly written (or sometimes just incorrect!) language documentation that doesn't go to the appropriate lengths to explain the distinctions in the terms.

Some other conflations I see frequently in the PL space:

- "argument" vs "parameter"

- "type coercion" vs "type casting"

- "static typing" vs "strong typing"

- "function" vs "method" (and we can throw in "procedure" and "subroutine")

Re: A guide to closures in Rust

#64
post #23

There's a lot to be said in favor of Rust's approach to memory management but closures in Rust suck compared to garbage collected languages.

True, but isn't the complexity of Rust closures essential rather than accidental? Fundamentally closures are easy in e.g. Go because you don't have to think about lifetimes, at all. As soon as you capture a variable, the GC guarantees it won't be dropped from underneath your feet. With non-GC'd languages that responsibility moves from the GC to the programmer. The trickyness of using closures in Rust seems largely to…

Not quite, there are non-GC ways to guarantee memory safety without borrow checking, see languages like Vale, HVM (more a runtime), and Inko.

With those in mind, Rust's complexity does indeed look accidental here. It has its other benefits, but it does make closures a bit more difficult.

Re: A guide to closures in Rust

#65
post #43

Earlier quoted context omitted.

If you want to do anything with the closure it gets a little tricker, for example if you did something naive like fn fn_that_takes_closure (f: fn (i32, 3i2) -> i32) { ... } let x = |a, b| a + b; fn_that_takes_closure(x); That would not compile, because `x` does not have the type `fn (i32, i32) -> i32` (that's reserved for "normal" functions). Every closure in Rust has a unique anonymous type, which means if you want…

> That would not compile It does compile. > because `x` does not have the type `fn (i32, i32) -> i32` (that's reserved for "normal" functions). Closures can cast to function pointers just fine as long as they don't capture anything.

Other comments are saying non-capturing closures are not closures. If we want to have this pedantic argument of definitions: proto_lambda is actually right, with Rust's definition of closures.

According to the Rust reference:

> Closure types > > A closure expression produces a closure value with a unique, anonymous type that cannot be written out.

https://doc.rust-lang.org/reference/types/closure.html

Even a non-capturing closure is written using a closure expression and produces a unique, anonymous type.

Re: A guide to closures in Rust

#66

Earlier quoted context omitted.

> That would not compile It does compile. > because `x` does not have the type `fn (i32, i32) -> i32` (that's reserved for "normal" functions). Closures can cast to function pointers just fine as long as they don't capture anything.

Closing over variables is the thing that makes it a closure. Otherwise, you just have an anonymous function. A closure is a function plus the captured environment. The difference is meaningful here. You have to allocate a closure (and deal with its lifetime and the lifetimes of the variables it references) but the anonymous function is just a pointer to static code in the binary. That's the entire difficulty with clo…

As per Rust reference, even a capture-less closure is a closure and distinct from an anonymous functions.

Also, your arguments only partly apply in Rust. Rust doesn't heap-allocate closures. And you also often don't have to deal with lifetimes - a closure that captures variables by move or copy is perfectly self-contained

The difference between a closure that captures and a closure that doesn't is like the difference between `(T)` and `()` - same kind of thing, so it adheres to the same terms and behaviors

Re: A guide to closures in Rust

#68
post #42

Earlier quoted context omitted.

The borrow checker rejects many otherwise valid programs. So if you do your job in correctly in C/C++, the borrow checker might accept the Rust equivalent or it might not.

Maybe that was true in 2017, but today borrow checker and compiler in general has covered so much of the Rust design space that the "correct programs" it rejects are more like rejecting Duff's Device kind of code. You are more likely to find yourself implementing low-level data structure or device interface where you need raw pointers tightly localized in the unsafe{} scope.

It rejects anything it can't prove. Sometimes that proof is trivial for the programmer. Consider a string table. You intern by passing an owned String, the string table sticks it in a HashMap and gives you back a &str with a lifetime matching the table. From a C/C++ perspective, that's fine; Rust doesn't know that you'll never be removing things from that table, but you certainly can (and encapsulation can help you enforce that).

Since 2017 it certainly admits quite a few more programs, but it's still pretty easy to find things that require a different approach than they would in C or C++.

Re: A guide to closures in Rust

#69

Earlier quoted context omitted.

> That would not compile It does compile. > because `x` does not have the type `fn (i32, i32) -> i32` (that's reserved for "normal" functions). Closures can cast to function pointers just fine as long as they don't capture anything.

Closing over variables is the thing that makes it a closure. Otherwise, you just have an anonymous function. A closure is a function plus the captured environment. The difference is meaningful here. You have to allocate a closure (and deal with its lifetime and the lifetimes of the variables it references) but the anonymous function is just a pointer to static code in the binary. That's the entire difficulty with clo…

> You have to allocate a closure

That's incorrect, a closure in Rust compiles down to a static function that takes its environment as an argument. None of that requires a heap allocation in the above code.

Re: A guide to closures in Rust

#70
post #47

Earlier quoted context omitted.

"And sure, plenty of people will tell if you do it "right" you'll have no issues." My answer to that is always that if you are able to do it right in C or C++ (and maybe golang) you should not run into any issues with Rust. Especially you should not ever have to fight to borrow checker because it does the job you should be doing as a C/C++ programmer in your head anyway.

>"...it does the job you should be doing as a C/C++ programmer in your head anyway." False. Too many false positives

"False" seems too strong. The job it does is track lifetimes. That's a job you should be doing anyway in C/C++.

It does so better than you can in that it doesn't have false negatives anywhere you're sufficiently playing by its rules.

You can also do so better than it can, in that you can see reasons things are okay that it can't understand.

I agree that there are too many false positives to assert that a competent C or C++ programmer doesn't have learning to do to appease (and leverage) the borrow checker.

Post reply on HN