The Rust Programming Language
dl.dropbox.com
dl.dropbox.com
It's very important to language designers at this time to avoid stop-the-world GC approach in Rust at any cost.
Google Go is beautiful programming language, but its key limitation is stop-the-world approach to GC which makes it very difficult to implement highload web servers, DB engines and other software with strict max-time-to-response requirements. Recent research [1][2] suggests that "normal" Go program spends more time collecting garbage than performing actual calculations.
[1] https://days2011.scala-lang.org/sites/days2011/files/ws3-1-H... [2] http://www.theregister.co.uk/2011/07/01/go_v_cpluplus_redux/
Go programming language has gone too far to separate owning and non-owning pointers at syntactic level, to disallow changeable global variables, to enforce rules like "each mutable object is accessible from only one thread", and introduce other means to avoid GC.
But Rust is new and young, already has 2 types of pointers, and it could avoid GC by RAII and reference counting. Objects linked in cycles with owning pointers would cause memory leaks, and I think this behaviour may be left as is, kinda "it's not a bug, it's a feature". Weak pointers could point to intermediary descriptor which holds owning-refcount and nonowning-refcount. Weak ptr behaves like a NULL if owning-refcount is zero and target object is already destroyed. This is to guarantee safe memory model.
In short please take all means to avoid main Go defect "Garbage collection takes more CPU time than actual computations". Please avoid stop-the-world GC approach which makes the language almost useless for real applications.
Other language and GC implementations with a large shared heap can work fine. See, for example, Azul systems' JVM.
This however is not as easy to use as typing "ruby dostuff.rb" or something. You need to run a full virtuall OS.
I do really really hope that Intel does these Hardware changes it would help ever language with GC. (The OS would need to support that too but thats probebly a easyer then getting intel to add the feature)
If reference counter is thread-local (i.e. its increment/decrement is not done with InterlockedIncrement/Decrement = LOCK XADD/XSUB), then it won't cause a lot of MESI traffic and related memory I/O interlocks.
And, as far as I can see, refcounting in Rust can be thread-local.
Russ Cox says in [2] (see link in my first comment above): We used gopprof to study an inefficient Go program and then to improve its performance by an order of magnitude and to reduce its memory usage by a factor of six. A subsequent comparison with an equivalently optimized C++ program shows that Go can be competitive with C++ when programmers are careful about how much garbage is generated by inner loops.
They got a speedup about order of magnitude by improving memory management in the inner loops. So 10% of time for refcounting isn't that much. 90% of time for GC is much worse.
Especially if it is a stop-the-world GC on 12-core server system. One core collects garbage, 11 cores just cool off.
Also, the Go GC is a parallel GC which will use all cores during collection.
Leaking cycles is not really an option, sorry. At the very least we're going to have a cycle collector. It's too easy to make cycles, especially when you have closures (all you need is to close over an object that contains a pointer to the closure itself; this is very common for e.g. event handlers).
Besides, a lot of the problems you state can be addressed with generational or incremental garbage collection.
But if there's a difference between owning and non-owning pointer, then backlink to parent object in child object could be non-owning and other issues could handled in similar manner.
Cycle detector which stops the program with references to the source code where the cycle was created - is a good idea, because in server systems under heavy load it just could be disabled. Basically it's a debugging tool, once it was proven by the testing that there are no bugs, these checks can be turned off in production. Like a C assertion checks.
In Google Go garbage collector stops all heavyweight OS threads [See go/src/pkg/runtime/proc.c:runtimeВ·stoptheworld() in the source tree.]
So, the fact that you're saying "Don't do what Go does, it makes Go almost useless for real world applications" seems odd to be because:
1. Not all applications care deeply about stop-the-world gc pauses.
2. Not all of those that do will have a problem with them, as GC can be avoided by value semantics and escape analysis.
3. Go will not necessarily always have a Stop-The-World GC, though it does now. I don't think it's required by the spec.
4. People are using Go in real world applications, implying that it isn't almost useless. Even servers.
5. The Rust folks clearly have thought hard about efficient and scalable memory management, and the slideshow indicates that, so I'm not sure why you're worried for them.
From the third slide: we can see two different function definition syntaxes, a "normal" style and a "ruby" style. I really like the way of JavaScript, CoffeeScript and OCaml that have a single unified function syntax.
Also on this slide: why have a special syntax for importing submodules from modules/crates? Again, CoffeeScript excels here, an import is simply a variable declaration:
{int, vec} = require('std')
Furthermore, it has always bothered me why generic types use <> brackets. I can understand it in Java and C# (where types are declared before values/functions), but in Rust, it's really unnecessary to introduce the 4th type of brackets into the language (apparently, they used [], but latter changed it to <>... I would really like to know why.)In OCaml you can say let rec func = ... , let func = ..., let func = function |... | _->... and let func = fun ... -> ... Now I find that each of these variations make good sense and would call them unified facets but they tripped me up many years ago when I was first learning the language.
let f = function 0 -> false
| 1 -> true
use (f)
and then refactor the code into use (function 0 -> false | 1 -> true)
That's the consistency I'm talking about...I don't know coffeescript, but this looks to me like "std exposes two things, and I am bringing them into this namespace as int and vec". Whereas the rust code looks like "std exposes int and vec, which I am bringing into this namespace, and possibly other things which I am not".
std = require('std')
and access std.int, std.vec and so on.I think I like rust version better: when I see an '=' sign, I assume that the RHS returns something which is assigned to the LHS, and something doesn't depend on the LHS. Coffeescript violates that, and it feels much more like 'special syntax'. But without knowing either language, I won't go further than "this seems weird".
{ x, y } = { x : 3, y : 5, z : 3 }
is perfectly valid as well. Once you get the hang of coffeescript's destructuring, it's insanely powerful. [x, y] = [1, 2]
With splats: [first, second, rest...] = contendersIn both C++ and C# it leads to ambiguous constructs and parsers beyond the realm of yacc & friends.
The Java syntax was carefully designed to avoid those ambiguities, but still...
Simply using [] or {} for template arguments would have been way, way better for everyone.
Functions declared at top-level with the |fn| keyword can be mutually recursive and don't close over anything by default. Blocks, however, are unique in that they can close over stack variables (we statically ensure that they can't escape) and can't be mutually recursive. At the moment, blocks can only appear in function argument position. So, although both |fn| functions and blocks are functions in some sense, they have different enough semantics that we felt that different syntax helped to convey the distinction.
"Also on this slide: why have a special syntax for importing submodules from modules/crates? Again, CoffeeScript excels here, an import is simply a variable declaration:"
Well, in CoffeeScript, imports are actually statements that are evaluated at runtime. In Rust, they're compile-time constructs. Furthermore, using a separate syntax gives us more power — you can say |import *|, you can say things like |import std::{hashmap::map, vec}|, etc.
"Furthermore, it has always bothered me why generic types use <> brackets. I can understand it in Java and C# (where types are declared before values/functions), but in Rust, it's really unnecessary to introduce the 4th type of brackets into the language (apparently, they used [], but latter changed it to <>... I would really like to know why.)"
Because you can use generic type instantiation in value position as well (e.g. none::<int>), and [] was preventing us from using [] for array indexing (previously, you wrote v.(0) for v[0]). I also thought it'd be more familiar for C++, C#, and Java programmers.
I don't actually program C, I just have projects in mind where it seems like that's the best language, where performance and compatibility with other languages is important. But C seems like such a pain to program in that I really wish there was a better option.
That said, writing kernels is explicitly not a goal of Rust. It may well work great with a custom bare-metal runtime, but we aren't letting the needs of OS kernels constrain our design space at this time.
It's ambitious, definitely, but the world needs more ambitious things that will be amazing if they succeed.
Interesting parallel to how Go has interfaces without classes.
Each time when I have to define callback inside callback inside callback using anonymous functions in javascript, I wish they chosen something shorthe than "function".
Small reserved words means I don't have another failed compilation because I typed "fucntion", or the small but significant mental pain of backspacing.
Python's built in function "len()" is a lifesaver in this regard.
And if we're going to have something so devoid of obvious meaning as "ret" why not go the whole way down to a single character?
Smalltalk which I generally consider to be a verbose language manages quite nicely using "^" to indicate an explicit return-
^answerThe concurrency feature that stands out: ... no need for ... concurrent garbage collection. (slide 17)
The upside should be amazing performance compared to simple GC.
The cost is developer effort for memory management hints (slide 22). We'll have to see if it's worth it.
As a C++ developer I find my efforts on "memory management hints" to be totally worth it, due to the fact that you actually have to think about object ownership. Which in my mind makes for better designs.
The only thing it's missing that I'd like to see is support for CPS conversion like streamlinejs does. Even if it supports growable stacks that start at 1KB, with thousands of threads CPS conversion is still more efficient. Having said that, seeing as they are writing browsers, not servers, I can see why it's not a priority to do this.
If you're allocating the stack frame on the heap, you can just replace the prologue (e.g. x86 intel syntax, cdecl calling convention)
sub esp, LOCAL_SPACE_NEEDED
with push LOCAL_SPACE_NEEDED
push ARG_SIZE_TO_COPY
call allocate_frame
(where allocate_frame allocates enough space for storing link to previous stack frame, all args and all locals; then, it copies all arguments, return address and stack pointer, switches stacks to the new stacks and returns from the call)You can modify the return to call "deallocate_frame" (which would switch stacks back before returning), or you can just make sure the return address on the new stack points to "deallocate_frame_and_return", and use a regular return in place.
[Actually, come to think of it, it's an excellent idea for a stack protection system that can be turned off for speed - the call to "allocate_frame" could be configured at runtime to patch its caller to just do "sub esp, LOCAL_SPACE_NEEDED" for maximum speed. Or it could just check stack overflow; or it could allocate frames on heap for better memory use; and it could mprotect/virtualprotect the edges of this frame for protection]
So the stack frames aren't allocated on the heap.
> > So where exactly are the closures allocated ?
> They would be allocated on the heap
You're playing a weird game of semantics here. closures and stack frames are the same thing in this case. The fact that the "call stack" is implemented via a register called "the stack register" and uses a LIFO move-the-mark allocation scheme is an irrelevant implementation detail.
Your assertion that CPS conversion is somehow more efficient than growable stacks is not supported by any of the arguments you presented.
Frankly, your bits of syntax are no worse than `Std.stuff` and `'a stuff` (or `Stuff a`).
As for std::stuff, in earlier versions we wrote std.stuff, but it actually hurt readability (not to mention complicated typechecking), since '.' is also used for record field projection and object method invocation. In the MLs it's not so bad because module names have to begin with a capital letter, but we tried to avoid making case significant.
I'd expect the arguments of the map-function in the example on page 3 to be the other way around. The 'grades' argument gets pushed down several lines in the current form.
Works ok in FF on Xubuntu 11.10 but sloooooowwwwwwww
I've seen a pair of slide decks on it lately and it's been a much, much better experience than consuming through Slideshare: it's faster, more reliable, it looks better and it's not flash.
So i fullscreened and then clicked on the link, but then i couldn't get out of full screen or close the tab because they intercept keystrokes.