Seriously though, why not just use a rec keyword and then disallow any of the things that don't play nicely when you're in the recursive function? If you really wanted to be cool you could put those things right into the type system.
Seriously though, why not just use a rec keyword and then disallow any of the things that don't play nicely when you're in the recursive function? If you really wanted to be cool you could put those things right into the type system.
1. deterministic destructors that run at the end of functions (therefore making things that look like tail calls not actually tail calls) and
2. binary compatibility with C and C++ libraries and tools (they say that tail recursion doesn't let you usethe C calling conventions that these tools and libraries expect you to use)
There is no point in allowing tail recursion in restricted contexts if you can't use these restricted functions to do the sort of stuff Rust was actually made to do
Couldn't owned boxes (~) passed through the jump transfer their ownership to the next frame (so they'll be destructed by whoever finally does return), and everything else just get destructed before the jump?
* It's surprising behavior for destructors, which can have side effects. C++/D RAII and try/finally in other languages never behave like this.
* You can do it yourself, by moving the values-to-be-destructed into the bit bucket with "let _ = ...";
* You still have the ABI issues in that you can't use cdecl and have to use Pascal/stdcall to get TCO.
I've read variations of this comment about Rust C++ compatibility a few times, but haven't managed to find a source. Any references you could point me to?
http://www.reddit.com/r/rust/comments/1c3clf/c_ffi/c9codm1
In any case, I'm not sure that any official Rust source has ever tried to claim "C++ binary compatibility". Perhaps this language is a result of people misinterpreting the fact that Rust lays out structs in memory in the same fashion as C and C++.
I don't think people are doing that. It's just that being pragmatic also makes you opinionated by necessity.
Actually that would a very good definition: being pragmatic means you are "opinionated by necessity" (as opposed to opinionated by other concerns, e.g pureness, advancing the state of the art, etc). So being pragmatic is a subset of being opinionated, not a synonym.
>when the implementer of a language or technology makes a choice that restricts how its users can do some things, it makes an opinionated decision, that just happens to be pragmatic in the use-context he has thought of, but may not be pragmatic at all for the use-context that some technology user imagines
A language has thousands (millions) of users and every one can imagine whatever use-context. Not catering to all of them doesn't make you opinionated: merely pragmatic. Nobody has the time and the means to cater to all possible use-contexts. And we all know that adding too many things can ruin a language, over-complicate it and such. So that's another pragmatic concern.
>like a language feature that may be hard-to-impossible to implement but that when done properly (and it only has to be done once) would simplify the lives of a large group of programmers that just want to do things in a certain way.
Sounds like a very horrible feature to build into a language. Especially when you are starting out. If it's "hard-to-impossible to implement" then you are wasting tons of resources to something that might very well not pan out in the end.
To reject this is not opinionated in the bad way. It's merely being pragmatic again, ie understanding that you have to prioritize things, and not go on a wild goose chase.
All words are empty until we put meaning on them. Go and read what they are trying to achieve and how, and then they won't be empty anymore.
>Usable and pragmatic are purely contextual. Is rust faster than Haskell?
It's not highly optimized yet, as it's in pre-alpha stage. But it's goal is to be faster than Haskell, and close to C/C++/ADA speed.
Usable and pragmatic means that it should work for their goals, which are very real and tangible themselves: to use it as a compiled language to create a fast, parallel and secure web browser engine.
They don't want to pile on academic concept and programming features or compiler tricks just to be "cool", "interesting", or "cutting edge". They don't even care if they would be "nice to have". They care about: what helps their goals, and what can be implemented without overcomplicating things.
If Rust cannot become a language in which it's able to write a fast (faster than the currently available), parallel (more parallel than the currently available) and safe (safer than the currently available) browser engine, then it would have failed on its targets.
- deterministic memory management
- actual generics. :P
- an advanced type system (vs. java/c++)
- design choices taken towards efficiency
- default-immutable memory
The elevator pitch is "Speed of C++, safety of ML, concurrency of Erlang."
(Is concision really what you lose when you need explicit looping constructs? Maybe "abstraction capability"?)
So support for tail calls is not about safety but expressiveness, and Rust chooses to express iterative algorithms through other means, such as the iteration protocol that's built on top of higher-order functions.
EDIT: Maybe I'm misreading you and you only mean the imperative nature of loops forces you to use mutation. Rust does not eschew mutation altogether. Even as a functional programmer myself, I'd argue pretty emphatically that mutation, particularly of local variables, is not a grave safety concern.
Did you mean something other than combinators?
For example, above, you mention how TCO lets you use first-class functions to help you eliminate mutability. It's the elimination of mutability that's the safety here. Rust allows you to eliminate mutability through other means.
For some use cases (games, web browsers, etc) the first goal is much more important than the second.
Is that actually a real problem? I never hear C/C++/Java/Python/Ruby/Javascript/D/Go/Clojure programmers complain about such bugs. On a Linux/Windows/OS X system the stacks are big enough to practically never hit such bugs. Whenever I get a stack overflow, I coded an infinite loop, which is actually easier to find without TCO, because the program is terminated.
TCO is a way to turn a recursion into a loop. If for every situation where you would elegantly use recursion you'd end up with real recursion and all its overhead you'd indeed end up overflowing the stack. For a C/C++ etc program it would not matter whether your loop iterator would be 10 or 10,000,000,000, the program would just run a little slower. The rust/clojure program (coded up without knowledge of the underlying mechanisms, so 'naively') would likely run out of memory. With TCO such a naive implementation would run just fine. 10,000,000,000 is merely large, not infinite and should all things otherwise being equal not come with a huge memory penalty if all you're doing is writing elegant code in the most applicable idiom for a certain language.
Though perhaps you'll be happy to hear that Rust stacks are growable (Go-style) rather than fixed, so stacks are typically small and running out of stack is (afaict) theoretically less of a concern than in C/C++.
Interesting, isn't there a possible transformation where you would be able to move the constructor/destructor pairs out of the generated loop construct without breaking the function?
After all a tail call is just a fancy go-to under the hood.