4 ms·
Finding Closure in Rust
- Animats 11y agoIt's good to see that. Rust's closure/lambda system has been in flux as recently as a month ago. Rust ties closures closely to lambdas, but that's not inherently necessary. The article says "Syntactically, a closure in Rust is an anonymous function value defined a little like Ruby, with pipes: |arguments...| body." Actually, that's a lambda, not a closure. A lambda which references no variables from an outer scope is not a closure. |x| x*x is a Rust lambda that isn't a closure. On the other hand, a nested function in Python which references variables from the outer function is a closure that isn't a lambda. That's also true in Javascript. In Rust, you can nest functions, but you can't generate a closure that way. A reference to the outer function scope in a Rust nested function generates an error message. I was surprised by that; I tried to write a closure in Rust as a nested function, and there's an specific error message for that.
- eridius 11y agoFrom the classic computer science point of view, you are correct in saying that a lambda that does not capture in variables from its enclosing scope is not a closure. But I think that's a meaningless distinction here that would only serve to be confusing. Rust uses the terminology "closure" to refer to this pipe syntax, regardless of whether it captures variables, and that's fine. It's less confusing for people who aren't coming from a computer science background, and it's not really even strictly speaking wrong. A closure is a function coupled with an environment that allows it to capture values from its scope. A closure that captures no variables can still have an environment, the environment just happens to be null.
- blaenk 11y agoI think it would be useful if the distinction did exist in Rust, so that for example, "closures that have an empty environment" were indeed referred to as lambdas, and lambdas had the guarantee of being Sync, Send, Copy, Clone, etc., since they're more or less just like regular `fn` items as far as I know. As it is, a closure that captures nothing doesn't satisfy those bounds AFAIK, so their use is more restrictive than it needs to be. Instead, you're forced to write an actual `fn` definition, depriving you of the practicality and conciseness of closure syntax.
- eridius 11y agoThat sounds potentially confusing. The same syntax yields closures that sometimes are Send, Sync, Copy, Clone, etc but usually aren't? I'd rather stick with nested fn's. If you really want to be able to have inline functions that are lambdas but not closures, then maybe you should submit an RFC to add literal syntax of the form `fn(args) -> T { code }`
- blaenk 11y agoLike huon said, this is already the case, so I don't see how it could be confusing. The compiler would infer all of that and complain if the constraints are not satisfied. A literal fn syntax would be pretty cool, but I have doubts it'd ever be accepted, which is why I figured closure syntax could be adapted to allow this use case.
- dbaupp 11y agoA closure satisfies Send and Sync when it can, automatically. (I.e. with or without captures.) Satisfying Clone when the contents are Clone would be valuable for all closures, whether or not they capture anything. The most valuable special case IMO for captureless closure would be allowing them to coerce to (extern) fn types, useful for writing e.g. C callbacks directly inline.
- blaenk 11y agoYeah, I forgot about that last point, which was the original reason I got this idea.
- hermanradtke 11y agoRust figures out what parts of the environment you are using and makes a closure or lambda. The compiler figures it out for you. This is basically the Rust philosophy.
- blaenk 11y agoNot sure what you mean. I understand that the compiler figures out what to do. My point was that it might be useful to remove certain restrictions from empty-environment closures to allow them to be used in more contexts, such as wherever a regular `fn` could be used (like for FFI callbacks).
- Animats 11y agoThat's a distinction that does exist in Rust. A lambda can only be passed to a generic function argument, something that looks like this: fn some_function<F>(some_closure: F) On the other hand, you can write a nested function and pass it as a parameter to a non-generic function. It just can't make references that would make it a closure. That's because Rust closures are really a syntactic hack for traits. (All this complexity may turn off C programmers, and their management, for things that really need to be in something safer than C. The people who participate in language discussions do understand this stuff, but they're not the ones writing embedded code. Rust could go the way of C++, where metaprogramming became The Beast that Ate the Language.)
- steveklabnik 11y agoYou can also pass a fn to a function that takes a closure. fn foo<T: FnOnce(i32)>(x: T) { x(5); } fn bar(_: i32) { println!("Bar"); } fn main() { foo(bar); }
- eridius 11y agoA lambda is usually passed as a generic argument, because that's the only way to avoid virtual dispatch (and the only way to take a lambda by-value without allocation), but you can also use trait objects if you want: fn foo(x: &Fn(i32)) { x(5); } fn main() { foo(&|x| println!("x: {}", x)); } Or you can use std::boxed::FnBox in order to take a Box<FnBox(i32)> but that's currently marked as unstable (it exists because you can't take Box<FnOnce(..)> as you can't move out of the box in order to call the lambda as it does not have a statically-known size, but if you're ok taking Fn or FnMut then you don't need it).
- dbaupp 11y agoYou're taking about the distinction between named functions and anonymous one, whereas I believe eridius was talking about the distinction between anonymous functions with or without captures.
- dbaupp 11y agoThere is actually a footnote that discusses closures vs anonymous functions in more detail, exactly because I anticipated some pedantry like this. :)