C++, of course, has the same choice between virtual methods and templates, "interfaces", inline and external method implementation syntax, etc, although the separate vtables are a nice trick and traits look interesting. So I'm curious about one thing (which would be obvious if I'd used Rust, but isn't apparent on Google):
What's the compilation speed like?
C++'s tendency to stick template functions in header files is a curse on compilation speed compared to C. Rust's solution is apparently to compile all modules in a crate together, which at least avoids instantiating the same templates more than once when compiling from scratch, but sounds like it would make incremental compilation times even worse. I am interested in Rust, but slow compilation drives me crazy.
(And what are the rules for importing classes between crates - how do template functions work, are there any guarantees for changes to a class that won't prevent code that was compiled with an old version from working?)
Compilation speed is not as fast as we would like yet. A lot of that is simply due to generating too much code, which largely is an artifact of the way we implement unwinding. We're working on redoing that to improve code size and reduce compilation times. The monomorphization pass (which is the analogue to template expansion in C++) is pretty fast, because we operate on largely precompiled, serialized ASTs instead of reparsing and typechecking everything from scratch. (Also, we only bother to deserialize the functions you actually use.)
You can draw the boundaries between compilation units however you'd like. If you want incremental compilation, use many crates; if you want easier dependency management, use fewer crates. By default, crates are loaded dynamically at runtime, but you can statically link crates together and inline across crates for production.
The Rust compiler tracks breaking API changes and updates your crate UUID automatically. You can change private APIs and dynamically linked code as you'd like without causing dependent crates to be recompiled, but changing an inlined template function will cause your crate UUID to change, which triggers recompilation of dependent crates. The UUID is essentially just a large hash value derived from the signatures of all the public items in your crate.
At the moment, this is unimplemented, but it's intended that you be able to change the bodies of non-inlined generic functions without causing recompilation of your callers. This works because a non-inlined generic function can only be called with pointer types, which means we can generate one piece of code for the generic function and use that. Inlined generics can be called with any value and the code is duplicated for each value you called it with, so changing the body of one of those does trigger recompilation of dependent crates.
Looks interesting, but to be honest there is nothing new here. If you take a look at D, it also has both runtime and compile time polymorphism for a long time. The former is implemented with Java-style interfaces, while the latter is done through templates. Traits are also possible via template mixins: you define a template like so:
template ExtraStuff
{
int someData;
void someMethod() { someDate = 5; }
}
and then you mix it into the class in question:
class SomeClass
{
mixin ExtraStuff;
}
It also gives you a lot more flexibility: you can mix it not only in classes but in structs(in D those are different types), to the global scope and even inside the functions(in that case someData becomes a local variable and someMethod() an inner function).
The part that's new (as far as we know anyway) is the unification of typeclasses with OO (bounded polymorphism; e.g. fn foo<T:MyInterface>(x : T)). Mixins and traits are definitely not new.
I haven't read up a lot on Rust but I feel that it is similar to Go and perhaps Ceylon. Can someone explain why there is a sudden "renaissance" in such memory-managed systems programming languages?
Edit: Oh, and also the D programming language. There are quite a few of these!
Rust seems a different level of complicated to Go. Go seems quite minimalist. Rust seems more like it's trying to include the essentials of everything, but in as concise a fashion as possible.
That's a good way of putting it (although perhaps "everything" is too strong a word -- we don't want to build every paradigm imaginable into the language).
When in doubt regarding features, I tend to ask myself "would a next-generation browser engine need this language feature to be more secure/more maintainable/faster than current browsers?" (That's not to say that we're building the language only for a browser engine, but I think it's useful to have a target use case in mind when designing a language.)
Go was developed by guys who prefer C to C++, Rust was developed by guys who were fully immersed in a complex "modern C++" code base.. and it shows.
Go reminds me a lot of C as far as the look and feel is concerned. Go code is usually readable and straightforward, simply because Go (like C) deliberately limited the number of language constructs.
In contrast, Rust looks like a re-invention of modern C++.
I mean, C++ is much more popular than C for application development so that might not be a bad thing. However, I admit being in the C/Go camp of "build complex things out of a few simple building blocks" vs. the C++/Rust camp of "build complex things out of complex things".
As I've said earlier, we talk about how to minimize language complexity nearly every day. I would be thrilled if we could remove enough features to make Rust's spec as short as Go's.
That said, I think Rust is going to be somewhat more complex than Go because our needs are different (particularly around safety and memory management).
If you do see ways to simplify the language, we'd like to hear about them.
For me, the killer feature of Rust is the support for RAII and explicit, deterministic memory management (if you want it).
RAII and the ability to deterministically manage resources has always been C++'s killer feature IMHO.
There have been lots of improvements that could be made to C++ once you were willing to eliminate backwards compatibility - but all of the options up to now (e.g. D, Go), forced a GC on you, with no language support for explicit resource management.
I'm very pleased with direction "Rust" seems to be taking in this respect.
"but all of the options up to now (e.g. D, Go), forced a GC on you, with no language support for explicit resource management."
That's a lie. D supports both RAII(via scope stament which is a lot more flexible that C++ approach as well as using structs) and deterministic memory management.
"That's a lie." -> it's been a while since I looked at D so maybe it's changed since I last looked - but I remain unconvinced that the GC in D will deterministically call your destructors for you (which is my point) and runs in the same thread.
As I understand it, the Rust GC uses reference counting precisely for this reason. To be honest, I'd be happy even if it did not detect cyclic references as I never found these to be a problem when writing C++ code (could always re-factor to avoid them).
Maybe I should have said "deterministic destruction by the GC" instead of RAII.
Whatever, my intention was not to mislead. Please don't call me a liar just because you disagree with something I state with honest intentions.
How about RAII in Go in the form of a keyword that indicates an object can only be allocated on the stack? (Channels and goroutines would wreak havoc with that, unless these objects were passed by copying, or the compiler would just tell you that's forbidden)
I'm not sure of Go's support for RAII... the key feature is the "composability" of the RAII technique.
In C++, I can embedded a "socket" class as a member of some other object - on the stack or the heap - and it automatically and implicitly gets cleaned up correctly and deterministically, whether or not an exception is thrown, without me having to write any extra boilerplate.... so long as I follow the "rules" (and this is one of the places where C++ could be improved on).
"try/finally" or some sort of "stack-only" annotation doesn't really cut it for creating new resourceful objects out of existing ones.
For example, C# has the "using" syntax and also supports try/finally - these let you write code that works with anything that implements "IDisposable".
It handles the trivial cases - but as soon as you start wanting to compose together IDisposable instances you end up having to write lots of your own boilerplate to deal with propagating the "dispose" methods and ensuring that everything works correctly if an exception is thrown.
Of course, writing and testing all of the edge cases is very tedious and often difficult because you are dealing with resources like sockets and database connections after all - so even "mocking" them becomes hard.
Because its so difficult, most people just end up writing C# code that leaks resources (but not memory).
the key feature is the "composability" of the RAII technique.
I think it might be possible to have composability in RAII in Go if such objects weren't allowed to live anywhere but the stack. (Which would limit passing through channels.) Those objects would have a well defined creation and destruction time. While not completely universal, this might still be quite powerful and useful.
It would. (In fact, you might be able to do this today with runtime.SetFinalizer.)
You do have to be careful to avoid bringing back the objects that your finalizer references from the grave. If you do that in Go and create cycles, it leaks (which isn't unique to Go, many other languages do this). You need a sophisticated GC (like Java's) or a type system that's aware of the heap/stack distinction of objects (like Rust's) to mitigate that.
I suspect that the advent of light-weight virtualisation and sandboxing (LXC, seccomp, NaCl) allows language implementors to provide the interesting properties of Java — security and ease of deployment — without being barred from compilation to native code. That only applies to Rust and Go, Ceylon is a JVM language and D has been around since 2001; also Rust and Go are quite different languages, even in the memory department.
does anyone else have the feeling the idiosyncratic keywords in rust ("iface"? really?) are there to distract the casual reader from talking about the important stuff?
Decreasing typing (of keywords) is exactly the wrong way to go when designing a programming language. A program will be read far more times than it is written; the optimization should be towards reading and understanding rather than typing. How long it takes to type should be a distant consideration, if at all.
Often times, a language construct that can be defined in the fewest lines will be more readable. So the end result will generally be a more compact representation. But things like "iface" for interface strikes me as the result of designers who have their priorities mixed up. It's not even an abbreviation, it's just awkward (fn on the other hand has a long history of being an abbreviation for function).
I agree that there are legitimate concerns with some of Rust's keywords, such as the visual similarity between `let` and `ret` (which arguably could just be `return`).
> But things like "iface" for interface strikes me as the result of designers who have their priorities mixed up.
Disagree with you here. `iface` is no harder to visually parse as "interface" than `&&` is to visually parse as "logical and". You should try using the language and see if it's still a concern after ten minutes. If so, make a posting to the developer mailing list (https://mail.mozilla.org/listinfo/rust-dev) and let them know. The developers do care about pleasing syntax, but be warned that the language is still in such a larval state that they don't really spend much energy bikeshedding.
What's the compilation speed like?
C++'s tendency to stick template functions in header files is a curse on compilation speed compared to C. Rust's solution is apparently to compile all modules in a crate together, which at least avoids instantiating the same templates more than once when compiling from scratch, but sounds like it would make incremental compilation times even worse. I am interested in Rust, but slow compilation drives me crazy.
(And what are the rules for importing classes between crates - how do template functions work, are there any guarantees for changes to a class that won't prevent code that was compiled with an old version from working?)