5 ms·
From 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 cl
by eridius 12y ago
From 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 12y 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 12y 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 12y 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 12y 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 12y agoYeah, I forgot about that last point, which was the original reason I got this idea.
- hermanradtke 12y 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 12y 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 12y 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 12y 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 12y 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 12y 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.