4 ms·
On a similar note: some people seem to think immutable is slower than mutable. I have seen this many times, especially in my language of choice (scheme). A lot
by bjoli 7y ago
On a similar note: some people seem to think immutable is slower than mutable. I have seen this many times, especially in my language of choice (scheme). A lot of people coming from python or c write loops using (while ...) and mutation, which in most schemes (and in many dynamic languages) is a nice way to get boxing penalties. Instead, a recursive function is almost always preferred. I have since since long preferred recursion.
Which (not strictly relevant) leads me to my biggest gripe (which is pretty small) with rust. I like rust. I really do. But a couple of times a week I have to use loop when tail recursion would have been a lot clearer. In debug builds , rust disables sibling recursion (a less general form of tail recursion) which means I have to either use a trampoline macro (simple and slow) or some kind of syntax rewriting macros (complex and fast). Neither seem like a good idea.
Should I just accept the ugliness of loop+mutation or is there a nice alternative?
- jfkebwjsbx 7y agoRecursive functions should be avoided whenever reasonably possible, for several reasons (performance, safety, optimizer shenanigans...). Immutable may be slower or faster than mutable, depending on what one is writing and optimizer.
- bjoli 7y agoTail recursion is not slower. A properly tail recursive function can be transformed into a jump, and not mutating loop variables makes flow analysis less complex. Scheme implementations usually doesn't even bother doing type inference once you start using set!, and just implicitly boxes everything that you mutate. Of course you _can_ optimize it, but once you have a language with multi-shot continuations you are probably better off encouraging immutability.
- jfkebwjsbx 7y agoTail recursion is a trivial subset of recursion. If you are in that case, you are better off writing a loop and avoiding to rely on the optimizer. As for Scheme, that is a detail of Scheme, not general mutability vs. immutability.
- bjoli 7y agoWhich is what I said I'm doing. What I said is that I would to not have to do that. I just said I would love to be able to (explicitly) guarantee self- and/or sibling recursion, maybe using a compiler directive. I explicitly mentioned the tail in tail recursion in my post. General TCO would be nice as well, say when writing a state machine/recursive descent parser, but that is a hassle, especially if you want C interop. Since many of the rust devs are old ocamlers, I doubt I need to explain the virtues of (tail) recursion to them. Which is also why I never bothered to bring it up: they probably have very good reasons for not supporting it. The reserved "become" keyword is testament to them at least giving it some thought.
- wtetzner 7y agoMy understanding is that they couldn't find a way to get TCO to play nicely with destructors.
- gpm 7y agoTail call optimization often doesn't happen when you think it should (in Rust) - when it does work it is not slower, but you can't rely on it. In practice recursive functions, even tail recursive functions, are a performance footgun that will likely result in slower code. There was a proposal many years ago to add a keyword like "return" that guaranteed tail call optimization to rust. Unfortunately that RFC was never accepted and implemented.
- neutronicus 7y agoThe other way in which recursion is a BIG footgun is in reading Profiler output. In particular it is hard to determine whether the body of your function is slow or whether it is recursing too much.
- bjoli 7y agoHow is that different from loops?
- deleted 7y ago[deleted]
- bjoli 7y agoI know that it is almost impossible to even hope for in c++ due to finalizers and things like that. I don't know enough about the borrow checker to know if there are similar problems in rust. I have noticed that LLVM does sibling recursion (LLVM's name for self and like-typed function TCO) quite often for release builds by inspecting the assembly output. I have never actually had it not happen, which speaks for it being possible, at least for sibling recursion that does not pass into extern functions. "become" became reserved before rust 1.0. One could maybe just let it guarantee LLVM sibling recursion as a starting point and maybe extend it with proper TCO later.
- kzrdude 7y agoRust has controlled mutable state, which is sort of a best of both worlds (vs pervasively everything-is-mutable and functional approaches)
- bjoli 7y agoYeah! I have found that writing rust as I write scheme (with some limits, of course) usually means code runs fast without having to even think about the borrow checker. Sometimes iterators aren't enough though, and if I had reliable sibling recursion in debug mode, most of my use of loop would disappear.
- nicoburns 7y agoConsider contributing to the RFC process. This has been proposed a number of times, and I don't think anyone's against it. It's just not been considered priority. I would advise that TCE will probably be more acceptable to the Rust community with an explicit opt-in syntax. And that `become` is a reserved keyword in Rust that is semi-earmarked for this purpose.
- bjoli 7y agoI doubt I have the know-how it needs to navigate the issues raised in the previous discussions. I could maybe write a proposal where sibling recursion (i.e. self recursion and calls to equally-typed procedures) is guaranteed via the becomes keyword, but i dont really have the motivation to spend that much time discussing it or write the proposal. Such a proposal would in any way be "future proof" in the sense that general becomes-TCO could be added later in a compatible way.