Earlier quoted context omitted.
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…
While that may be an extreme example I agree. nothing clean about that code. I've only just begun to dive into rust, but I feel like I'd need a concordance to navigate the meaning of that snippet alone. Bt then again, maybe it depends on where you come from.
Why Rust closures are somewhat hard
81–90 of 92 posts
Re: Why Rust closures are somewhat hard
#82Earlier quoted context omitted.
This indictment makes little sense to me considering that, outside the Rust compiler itself, the largest early and still ongoing use case for Rust was/is Servo. A web browser engine, which is predominantly a thing that's dealing with multitudes of graph-shaped problems. :confused:
Have you looked at the issue tracker for Servo? The bugs and pain points are enlightening. Plus, Servo isn't even used in production! The strategy has been to move parts of it. AFAIU, the most complex piece so far has been the CSS engine. That's nothing to sneeze at, but still. I recently wrote a full PKIX X.509 DER decoder in LPeg in a couple nights of hacking; have fun doing that (or writing anything equivalent to…
Furthermore the challenge with concurrent GC is that max memory utilization is difficult to contain and difficult to make performance deterministic. So yes it has advantages. It also has severe disadvantages for the types of low-level efficient things Rust is meant for. Remember. Rust is meant to replace the existing problems that people reach for C/C++. That’s the space it’s competing in - embedded programming, operating systems, browsers, etc. There are problems that other languages are better suited for. Python, Ruby, Java are easier to work with not only because of GC. Java in particular pays an even larger cost due to not supporting value types and is still struggling afaik to integrate them properly into the GC which inhibits programmer-aided optimization of memory usage/layout.
Also, I’ll note that Swift and Objective-C are pretty easy to work with for memory management (on the level of Java) despite not having GC and get quite close to the performance of C/C++/Swift.
The challenges with GC that you handwave away has been what’s been said about GC ever since the beginning. Perhaps it’s time to admit that GC isn’t the silver bullet you think it is?
Re: Why Rust closures are somewhat hard
#83Earlier quoted context omitted.
> It is effectively a slow ... raw pointer. In my experience (unless I've misunderstood what you're trying to say) this is the fastest way to write graphs because of how much more cache-friendly it is than having to chase a load of pointers.
True, but you can do the same thing more efficiently with pointers. Suppose the parent maintains an array of children Node* Then each child has a Node** member which points to itself in that array. That's one dereference to get up to the parent context. In the Rust example, to do the same would take an extra few steps and probably miss the cache. I think speculative execution would favor the pointer method.
Re: Why Rust closures are somewhat hard
#84Earlier quoted context omitted.
Have you looked at the issue tracker for Servo? The bugs and pain points are enlightening. Plus, Servo isn't even used in production! The strategy has been to move parts of it. AFAIU, the most complex piece so far has been the CSS engine. That's nothing to sneeze at, but still. I recently wrote a full PKIX X.509 DER decoder in LPeg in a couple nights of hacking; have fun doing that (or writing anything equivalent to…
The challenge with gc is that it itself isn’t a complete solution and actually creates new problems that are even harder to solve. Everyone worries about memory because it’s the most common resource but all resources have this problem (eg files, network handles, dB connections, audio handles, etc). So then you end right back to having GC languages add back scope-based ownership for that (python with, java closeable,…
> and get quite close to the performance of C/C++/Swift.
In some cases, identical performance. ;)
Re: Why Rust closures are somewhat hard
#85I 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 many ways, closures can be more difficult when there is not garbage collection because we precisely do not want to refer to values that have been freed or collected. For example, in C++, we can write code like: #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…
Re: Why Rust closures are somewhat hard
#86Earlier quoted context omitted.
In many ways, closures can be more difficult when there is not garbage collection because we precisely do not want to refer to values that have been freed or collected. For example, in C++, we can write code like: #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…
In your rust example could you give "a" a lifetime that is the same as the lifetime of the impl Fn that foo() returns? That way "a" lives as long as the impl Fn.
fn foo(a: &'a f64) -> impl Fn(f64) -> f64 + 'a { ....
but it doesn't work
Re: Why Rust closures are somewhat hard
#87Earlier quoted context omitted.
True, but you can do the same thing more efficiently with pointers. Suppose the parent maintains an array of children Node* Then each child has a Node** member which points to itself in that array. That's one dereference to get up to the parent context. In the Rust example, to do the same would take an extra few steps and probably miss the cache. I think speculative execution would favor the pointer method.
Do you have benchmarks? An array offset isn’t speculative and I suspect you’ll have a very hard time showing that vec[i] is slower than *ptr. One challenge is if vec[i] involves a bounds check which it might in native rust.
#include
#include
#include
static constexpr size_t MAX_NODES = 1 & descendant) {
if (n index_self = n;
descendant->ptr_self = &ptr_children[n];
n += 1;
}
}
};
int main(void) {
TopLevel top_level{};
for (int i = 0; i ();
top_level.add(node);
}
// access by pointer
{
auto t_0 = std::chrono::high_resolution_clock::now();
Node** it = &top_level.ptr_children[0];
for (int i = 0; i ptr_self;
it += 1;
}
auto t_1 = std::chrono::high_resolution_clock::now();
std::cout
(
t_1 - t_0)
.count()
index_self;
for (int i = 0; i (
t_1 - t_0)
.count()
Results: % clang++ -O3 bench.cc && ./a.out
Via pointer: 90 ns
Via index: 41 nsRe: Why Rust closures are somewhat hard
#88Earlier quoted context omitted.
> The idea that I can touch variables that have gone out of scope (and that have ostensibly been GC'd) I think this might be due to your mental model of GC not quite matching the way it's usually implemented (at least, in traditional GC, as opposed to reference counting); most GC gives no guarantees that memory will immediately be cleaned up once the last reference goes out of scope, just that it will happen some tim…
> most GC gives no guarantees that memory will immediately be cleaned up once the last reference goes out of scope But in this example the last reference hasn't gone out of scope, the last reference is in the closure. If the GC ignored closures and acted as if last reference had gone out of scope then there's a chance that that variable would be cleaned up before the closure gets called, which would make closures com…
Re: Why Rust closures are somewhat hard
#89Everything that's hard in Rust could be solved by GC. With Rust, programmers have to spend most of their mental energy worrying about management of memory, which has largely been automated already. I always go back to this quote from Andrei Alexandrescu (creator of D): A disharmonic personality. Reading any amount of Rust code evokes the joke "friends don't let friends skip leg day" and the comic imagery of men with…
The idea is to have a DSL inside Rust that essentially has haskell semantics. This allows you to write performance sensitive code in Rust and then marshall it into Gluon where you can do high level manipulations with type safety before you unmarshall into a performance sensitive section again.
To me the failure of Rust is more that it didn't go full-on-haskell syntax wise and (to a lesser extent) that there isn't a specification (or some standards) beyond the test suite.
Re: Why Rust closures are somewhat hard
#90The 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.
> The fact that you can even do stuff like this in Rust is amazing. Stuff like this? You mean closures? That's what you find amazing? What is considered ordinary in other programming languages is considered amazing in Rust. Amazing. > It’s simultaneously: relatively clean code, very efficient and type-safe. I like it. You like it? Do you work for mozilla? If there is a tech evangelist of the year award, I will vote f…
It's not that lambdas themselves are hard, it's that they made it work with all of the other constraints of the language.