5 ms·
> Nowadays, whenever D is publicly evaluated, the younger languages Go and Rust are often brought up as alternatives. Here, we need not go into an in-depth comp
by galadran 8y ago
> Nowadays, whenever D is publicly evaluated, the younger languages Go and Rust are often brought up as alternatives. Here, we need not go into an in-depth comparison of these languages because both Rust and Go lack one feature that we rely on heavily: nested functions with access to variables in their enclosing scope.
Maybe I've misunderstood, but aren't Rust's closures [1] exactly what the author is describing? Specifically, you can capture variables from the environment into functions.
[1] https://doc.rust-lang.org/book/first-edition/closures.html https://doc.rust-lang.org/book/first-edition/closures.html
- hmottestad 8y agoMaybe "access to variables" means write access. I know that in Java variables referenced by a lambda need to be final.
- oconnor663 8y agoHere's a tweak to the Go example above that demonstrates writing: https://play.golang.org/p/H9vCt93Qz2B https://play.golang.org/p/H9vCt93Qz2B Here's a Rust version that does the same (without heap allocation!): https://play.rust-lang.org/?gist=bb5406062970b62088d58fb8a9fe6d98&version=stable&mode=debug https://play.rust-lang.org/?gist=bb5406062970b62088d58fb8a9f...
- eptcyka 8y agoIn Rust's case, this becomes far less practical as soon as the closure's return type does not implement the Copy trait.
- oconnor663 8y agoHere's an example that does something similar with Vec's: https://play.rust-lang.org/?gist=7a4ed9098a53831195c5189ba9cd904f&version=stable&mode=debug https://play.rust-lang.org/?gist=7a4ed9098a53831195c5189ba9c... I think the real nastiness happens if you want to have more than one closure over the same variables, when those variables aren't Copy. Because then when you try to `move` them in, the compiler yells at you. At that point you have to resort to putting them in an `Arc<Mutex>` or something like that. But that's par for the course with Rust: the compiler is preventing you from creating multiple mutable references to the same object.
- steveklabnik 8y agoWrite access works. https://play.rust-lang.org/?gist=573c941b91657bb0dbac80c33d073a2c&version=stable&mode=debug https://play.rust-lang.org/?gist=573c941b91657bb0dbac80c33d0... Once "non-lexical lifetimes" lands, the inner scope won't be necessary. That may have tripped the OP up.
- oconnor663 8y agoGo also certainly supports this. Here's a playground example: https://play.golang.org/p/_BLguocGBRn https://play.golang.org/p/_BLguocGBRn
- codemac 8y agoDoes Go take a copy or a reference?
- rhencke 8y agoThe following example will provide your answer: https://play.golang.org/p/NeGuDahW2yP https://play.golang.org/p/NeGuDahW2yP
- mikepurvis 8y agoThe captured context persists between calls to the function, as the sibling example demonstrates, or this one: https://play.golang.org/p/drfx_3g84aE https://play.golang.org/p/drfx_3g84aE
- junke 8y agoThe only difference I see is if you need recursive local functions, which is harder (but not impossible) to do with anonymous closures.
- WalterBright 8y agoRecursive nested functions in D just work - the hidden parameter to the enclosing frame forms what's called a "static link". A "dynamic link" would be the usual pointer to the previous stack frame (not the enclosing stack frame).
- raphlinus 8y agoClosures are, but functions defined using "fn" syntax cannot capture from the environment. This is a useful feature, and I can see why people would want it, but it was considered ultimately not consistent with Rust's aims to be excellent at the low level. See [1] for more discussion of the tradeoffs and why Rust chose not to include the feature. [1] https://users.rust-lang.org/t/inner-functions-not-closed-over-their-environment/7696 https://users.rust-lang.org/t/inner-functions-not-closed-ove...
- oconnor663 8y agoI believe the story here is the same in C++. Functions are just a pointer to some compiled code. Closures are an object that represents all the captured variables, in addition to the code. That object has a size, and a destructor, and the variables it's holding might have pointer lifetimes that you need to worry about. In Rust, quite a lot of type system machinery gets invoked when you create a closure, to make sure it doesn't outlive any of the references it's closing over.
- raphlinus 8y agoYes, it's similar to C++. A Rust `fn()` type is similar to a C++ `(*foo)()` type and at the low level is just a pointer to code. A Rust `Fn` trait object (of which there are several variants) is roughly similar to a C++ std::function and is "fat" in that it can contain data as well as code. Most higher level languages don't make the distinction, buying conceptual simplicity at the cost of potential optimizations when a simple code pointer will do.
- ben0x539 8y agoThe interaction between borrow-checking and closures restricts what else you can do while you have a closure in scope that closes over some variables, so (like many rust features) they aren't a drop-in replacement for their counterpart in other langs.
- WalterBright 8y agoD nested functions don't 'capture' variables from their enclosing scope. They can access them just like other code in the enclosing function. This is achieved by adding a hidden parameter that is a pointer to the enclosing function's stack frame. This forms a threaded list so variables in nested enclosing functions can be accessed by walking that list. This is the same way Pascal does it. It also means that taking the address of a nested function produces a pair - a pointer to the function, and this hidden parameter to the enclosing stack frame.
- Rusky 8y agoThat's usually what "capture" means when talking about closures.
- tikue_ 8y agoAt least in Rust, capture granularity is per-variable, so I think closures don't contain a stack frame pointer? Though, I'm not sure why this would matter to a user evaluating Rust's closures vs. D's nested fns?
- steveklabnik 8y agoIt’s a pointer to the environment, and a pointer to the function. So yes, not a stack frame pointer. The environment is a struct containing what is captured; so for example, it would contain an &T if your closure captures a T by reference. You don’t need to walk a set of stack pointers to find it, just follow the reference directly there.
- WalterBright 8y agoThat's distinctly different from D's, which has a pointer to the enclosing stack frame. If it is the enclosing function, it's just a single pointer to it. The "walking" comes from if you're accessing the enclosing function's enclosing function's frame.