Steam store rendered in Servo
twitter.com
twitter.com
Talking with other C++11/C++14 users here, the general feeling is that, yes, C++ is a good language because of the performance you can get out of it and some of the nice features that have been added, but the language carries around a lot of cruft that is unavoidable due to its age and maturity.
C++ has always been an expert's language. These days it's the expert's expert language++. There are no shortages of landmines and gotchas in the C++ landscape.
Modern C++ is a new language, and it really does help, but for the first time there's another language that can actually fill those same shoes.
A really big deal that isn't really addressed by golang is the fact that you can produce shared libraries with rust, and call them from existing C/C++ programs through a C interface. That's a really big deal in places with a lot of legacy code.
Another big deal (from the scientific computing POV) is the lack of garbage collection. Yes, GC is helpful, but replacing C++ means squeezing every last bit of performance out of your hardware. Rust does not sacrifice in this regard.
I really hope that Rust has a bright future. It's almost like "C++ done right", but I would stray away from such a label since someone might Linus you and say, "there's no way to do C++ right".
I also view golang as the successor to Python. It's faster, modern, addresses concurrency, and makes things easier for developers. It takes a strongly opinionated stance, and that's exactly the right choice for many problem domains.
Python, unfortunately, is dead to me. The GIL is a deal-breaker, and even though we have lots of Python code in production, I can definitely see a future where Python is phased out in favor of something like golang.
Basically, I'm bullish on Rust + Golang, neutral on C++, and slightly bearish on Python/Ruby-like languages that do not address the concurrency problem.
that's the point. if it's yelling at you you are probably doing something wrong.
> and a lot of patterns from C++ land don't really translate well into Rust code
that's the point, you can't really get rid of all the c++ warts without changing things
> if it's yelling at you you are probably doing something
> wrong.
That's not necessarily the case, any static type system must be inherently conservative and therefore must reject some otherwise valid programs. It also takes time to learn Rust's idioms for creating cyclic data structures, and surely we can still take steps to improve our documentation to that regard.As for cyclic data structures, one would often like to have a primitive which encapsulates a forward owning pointer and a backpointer tied to it, with the backpointer unable to outlive the forward pointer. That's enough to handle most tree-like structures, and doesn't require reference counts or reference-counted weak pointers. It's a compile-time concept checkable by the borrow checker. Something to think about for Rust 2.
Also, for Rust 2, I'd suggest exceptions. The "new error handling model"[1] is excessively verbose. Repackaging low-level errors for the next level up requires considerable coding. I understand the big problem with exceptions is what to do about unsafe code. I'd suggest "just say no". Unsafe blocks cannot raise exceptions. If an exception in safe code unwinds into an "unsafe" block, it becomes a panic. Unsafe code in Rust is usually to interface with non-Rust code. If you have unsafe sections in pure Rust code, you're probably doing it wrong.
The secret of exception sanity is a standard exception hierarchy, like Python's. Catching an exception class in Python also gets all its subclasses. "EnvironmentError" covers all I/O errors, network errors, and such, but doesn't cover internal program errors. New user-defined exceptions are added below the most appropriate part of the standard exception hierarchy. So Python usually doesn't have Java's exception hell. If exceptions go into Rust, do it that way. Require that all exception objects descend from some predefined exception object in the standard exception hierarchy.
An additional advantage of a standard exception hierarchy is that it leads to straightforward internationalization. If all the standard exceptions have translations, users get semi-reasonable error messages in their own language. If you have too many string-type error messages in a program, it's hard to handle multiple languages.
Go is starting to get exceptions through the back door. People are abusing the "panic"/"recover" mechanism in Go to get exceptions.[2] Don't go there.
[1] http://lucumr.pocoo.org/2014/11/6/error-handling-in-rust/ [2] https://code.google.com/p/go-wiki/wiki/PanicAndRecover#Usage...
One can already do this with `&T` (for the backpointer) and `Box<T>` or `&mut T` (for the forward pointer). The hard part is letting you safely mutate something once the back reference has been added, without any runtime checks and with arbitrary data in the pointer. I don't believe what you've described addresses that issue, but maybe I'm missing something.
The Rust ownership model can almost do this now. A move-type assignment to the forward pointer which causes a change in ownership would have to do a mutable borrow on the backpointer to get exclusive use, then change the backpointer to the new owner. That prevents a change to the backpointer while it's being used for something. The borrow checker would have to understand that those two pointers are tied together by an invariant. There would need to be syntax for back references, understood by initialization as well as moves.
It's such a common case that it's worth the special handling. It would let you do trees and doubly-linked lists without reference counts.
The Rust ownership model is very powerful. I was fooling around with something like this a decade ago as an improvement to C++, but all I expected to get out of it was the elimination of reference count updates when you passed an object to a function without giving the function permission to delete or keep the object. Rust pushes that model much harder, and it works. It can be pushed even further to make more common use cases compile-time checkable. This would cover more common idioms from C++.
Another possible extension is lifetimes on objects allocated with "new". If you need to add something to a tree, you want the new objects to have the same lifetime as the tree. If you could specify a lifetime in "new", resulting in an object being allocated in an outer scope, that would cover the idiom where you call some "add to data structure" function and it creates objects to be added to the data structure. Currently in Rust, you have to create new objects from within the scope that owns the data structure, then call some function to install them in the data structure.
The idea of creating an object in an outer scope seems strange, but when you create a closure, you're doing just that.
Can one? A &mut T and &T both pointing to the same memory and existing at the same time causes undefined behavior, and such a pairing could only be constructed via a compiler bug or (wrong) unsafe code.
You know, Java also has a standard exception hierarchy. However, whether in Java or Python, it usually makes sense for the exceptions of a library NOT to be part of the standard hierarchy. For instance, SQLAlchemy's exceptions all inherit from SQLAlchemyError. It's the same thing in Java.
A lot of the yelling done by the compiler comes from real-world experience building gigantic piles of code that need to be maintained for years and years.
And the bit about patterns that don't translate well, that's easily fixed with more experience coding in Rust. C++ programmers know C++ so well because they've been doing it for decades and the language forces you to be on the watch for all the gotchas.
That said, I still get minor jets of frustration when this works:
let y = a.b.c;
But this does not:
let x = a.b; let y = x.c;
And this works:
let x = foo.bar(); let y = foo.baz(x);
But this does not:
let y = foo.baz(foo.bar());
due to scoping and borrow checker rules. Papercuts, yes, but annoying nonetheless.
That said, Rust (effortlessly) caught what could have been a very dangerous bug for me last night (I'm using a Rust wrapper around Lua): when you call "tostring" in Lua it returns you a reference to a string on the Lua stack. If you then pop the Lua stack the reference becomes invalid. If you then use the reference to the string, well... (This is akin to iterator invalidation.)
Rust's borrow checker flagged the error and its error reporting made the problem obvious. This will make up for a very large number of papercuts.
But I have personally found it to be true that (as with any other language) you get a gut sense for the semantics of the language and stop fighting against them over time.
You want to enlarge the latter set, since that lessens programmer frustration and increases expressiveness, but in the context of a non-garbage-collected language that often requires adding complexity to the type and borrow checkers which may in turn end up introducing bugs.
A discussion of various run-ins with the borrow checker and some details of attempts to reduce the friction:
https://github.com/rust-lang/rust/issues/6393
'Both @pcwalton and @zwarich spent some time trying to actually implement this work (with a possible RFC coming hand-in-hand). They ran into some unexpected complexity that means it would take much more work than hoped. I think everyone agrees with you that these limitations are important and can impact the first impression of the language, but it's hard to balance that against backwards-incompatible changes that are already scheduled.'
[added on edit:]
In particular, here is a very nice statement of the issue(s):
https://github.com/rust-lang/rust/issues/6393#issuecomment-2...
nikomatsakis: 'I'd also like to have more progress on a soundness proof before we go about extending the system.'
And here again is the tension between complexity in the compiler and the coverage of the space of safe programs.
This is all about your frame of mind. When I started learning Haskell I felt the same way. Now I treat it differently. I ask the compiler questions and it gives me the answers. What is the type of this partially-applied function? Oh, that.
It helps to have an editor and workflow that supports this style of programming. Writing large amounts of code and submitting it in batch to the compiler is just going to give you a headache when you get dozens (or more) errors and warnings. With a tool like emacs flymake/flycheck, you can get continuous feedback and use hotkeys to jump to and quickly fix errors as they occur, rather than letting them pile up.
a lot of patterns from C++ land don't really translate well into Rust code
Well of course. Rust is a new programming language, not a rehash of C++. To take full advantage of Rust you need to learn its patterns and idioms.
not fair to blame the compiler for being good at its job
It's not. It's a very different language, but designed for the same use cases (mostly). There are many C++ patterns that become pointless, and new patterns you need.
Go has ... code generation that isn't even triggered by Go compiler.
Java is a complex, mess of a platform for a reason.
It is so embedded in core enterprise systems that even if the language was frozen in place it would still be used for the next decade or longer. Even more so given that Java/Scala is the lingua de franca of analytics/big data which is all the rage right now in your Fortune 500s courtesy of Hadoop.
A big part of it is also the huge amount of talent from countries like India and China who have been taught programming via Java 6/7.
1) The C++ Standards committee has been doing a very, very good job. Aside from the big one (compiler enforced memory safety), some of the best Rust features are on their way into C++. For example:
- Traits: C++ concepts are on their way - there is even a working implementation in a gcc branch.
- Iterators: See Eric Niebler's range proposal and accompanying range-v3 library.
- Monadic error types (including do-notation!) proposal: http://www.hyc.io/boost/expected-proposal.pdf
Other great stuff coming to C++ over the next few years:
- ASIO in the standard library - see prototype here: https://github.com/chriskohlhoff/asio
- Resumable functions
- Composable futures
- Parallel algorithms + concurrent containers
... much much more
2) Clang/Libc++ development is unbelievably rapid and it has greatly improved the tooling available to C++ developers.
Some highlights here:
- Clang-format and clang-tidy to help developers write cleaner code
- Sanitizers like AddressSanitizer, MemorySanitizer, ThreadSanitizer, UndefinedBehaviorSanitizer that instrument binaries to find bugs at runtime
- Much better error messages for template heavy code
- an API for easy development of custom tools that parse/consume C++ code
... much more
- Traits: C++ concepts are on their way - there
is even a working implementation in a gcc branch.
Rust traits and C++ concepts are not the same thing at all. C++ concepts are sort of like template parameters with better (not complete) type checking. Rust traits are closer to interface declarations. Though it's really hard to do a description of either justice in an HN comment.The rest of your bullet points are fair, if a bit optimistic. I don't see all the features you listed making it into C++ 2020. But I'm hopeful that many of them will be, especially sane libraries for extremely common problems like ASIO.
C++ supports both kinds of dispatch too, but the means to achieve it are not the same across all scenarios. If you want to go from one to the other, you'll probably have to rewrite your code or create wrapper classes in C++. In Rust, you accept a &trait instead of constraining a generic type.
C++ handles static dispatch through implicit template interfaces today, in the future by concept maps (but you can implement an ersatz version of it today, described in a blog post I wrote on this very topic[1]). Dynamic dispatch is always through your class's vtable.
[1] http://chrisbranch.co.uk/2015/02/make-your-c-interfaces-trai...
The borrow checker - the Rust type system is much more powerful in lots of ways, of which the ability to check and enforce ownership of objects is just one.
Macros - C++ pre-processor macros aren't in the same league as a proper syntax tree based macro system like Rust (and Lisps etc.).
Another thing that you've left off your list is modules, which has been proposed for C++. My suspicion is that the need to support the legacy is always going to make those features not quite as good as their equivalent in a language designed from fresh.
For example, if you move a uniq_ptr, it will become null, and using it will cause a segfault. In Rust, moving a Box<T> works just fine, because using it after the move is a compile-time error.
It's light years closer to C++.
I just can't imagine anyone writing larger, monolithic apps with it. Whilst people have been doing it with C++ for decades. Then again Go is in its infancy so will be interesting to see which direction it does take.
There is an ecosystem around C/C++ for supporting large codebases. There isn't anything like it (yet) for Go. And to be honest given that there is a gap in the "market" for a microservices language I can't imagine much being built in the future.
There is an ecosystem around C/C++ for supporting large codebases. There isn't anything like it (yet) for Go.
Sorry, does not compute. Go was specifically designed for large teams and big codebases.While I find the new features of modern C++ interesting, the (lack of) tooling is what keeps me away. I simply refuse to write Makefiles or deal with preprocessor nonsense in 2015. "go build" just works. "import github.com/foo/bar" just works. "go test", "go test -bench" just work. Etc.
Don't think of "successor" as a similar-but-better language (like I consider C# to be to Java) but in terms of where the community comes from in general. Rob Pike has said how he expected Go to attract C developers and was surprised how it attracted Python-ers in droves.
"I was asked a few weeks ago, "What was the biggest surprise you encountered rolling out Go?" I knew the answer instantly: Although we expected C++ programmers to see Go as an alternative, instead most Go programmers come from languages like Python and Ruby. Very few come from C++.
We—Ken, Robert and myself—were C++ programmers when we designed a new language to solve the problems that we thought needed to be solved for the kind of software we wrote. It seems almost paradoxical that other C++ programmers don't seem to care."
http://commandcenter.blogspot.it/2012/06/less-is-exponential...
I think they were surprised that C++ programmers didn't like Go because Pike and Thompson weren't actually C++ programmers. Griesemer however appears to know C++ well.
But from point of view of where it would be a good fit in practice, majority of large Python/Ruby projects, like those that are driving millions of websites right now, will greatly benefit from being rewritten in Go, without sacrificing anything important. That can not be said about C++ world — hardly anyone who chooses specifically C or C++ for a new project, over everything that is currently available, would consider Go an option.
I can understand this in the context of server side development. But Python is gaining ground in data analysis.
Servo's been looking pretty exciting for a while now. I believe they just passed a milestone where it now implements the CSS rules used by 50% of all webpages (according to a survey performed by Google), and its performance is still blowing other layout engines away if preliminary reports are to be believed (as ever, we'll see if that holds up as they implement more of the web). Definitely a project to watch.
In other neat news, just yesterday they managed to implement a rudimentary tabbed browser GUI using only web technologies: https://github.com/glennw/servo-shell . Ideally this would be a stepping stone to the more full-featured browser.html project: https://github.com/mozilla/browser.html
I'd really like to see Servo start pressuring the big guys and possibly start winning some of the embedding battles.
Project home: https://github.com/servo/servo
edit: planning on
http://blog.servo.org/2015/02/24/twis-25/
http://techcrunch.com/2013/04/03/mozilla-and-samsung-collabo...
Now they have to interface Servo to all the ugly stuff - the Javascript JVM, plug-ins, etc.
[1] http://www.sitetruth.com/fcgi/viewer.fcgi?url=http://store.s...
Last I heard, Servo does not plan to support plugins such as Flash. I'm not sure if the existence of Shumway changes this.
<html>
<script>
document.write("<h");
</script>1>
This is a h1 title
<script>
document.write("<!-");
</script>-
This is commented
-->
</html>
(From http://www.reddit.com/r/rust/comments/2zir9s/html5ever_proje... )I don't see people doing it in modern websites (doesn't mean there aren't), but we sort of have to support all of the Internet, so...
For this to work, scripts have to appear to block the parser. However, it's desirable to start fetching external resources (images, scripts, etc.) that occur after the script that's blocking the parser. In Firefox, the scripts see the state of the world as if the parser was blocked, but in reality the parser continues in the background and keeps starting fetches for the external resources it finds and keeps building a queue of operations that need to be performed in order to build the DOM according to what was parsed. If the script doesn't call document.write or calls it in a way that closes all the elements that it opens, the operation queue that got built in the background is used. If the document.write is of the bad kind, the work that was done in the background is thrown away and the input stream is rewound. See https://developer.mozilla.org/en-US/docs/Mozilla/Gecko/HTML_... for the details.
For added fun, document.write can write a script that calls document.write.
i86 CPUs have to do something like this if you store into code just ahead of execution. That was an optimization technique marginally useful in the 1980s. Today it's a performance hit. The CPU is happily looking ahead and decoding instructions, when one of the superscalar pipelines has a store into the instruction stream. The retirement unit catches the conflict between an instruction fetch on one stream and a store into the same location in another. The CPU stalls as instructions up to the changed instructions are committed. Then the CPU is flushed and cleared as for a page fault or context switch, and starts from the newly stored instruction.
Only x86 machines do this, for backwards compatibility with the DOS era. The same thing seems to have happened in the browser area.
(Prospective fetching should be disabled on devices where you pay for data traffic. Is it?)
https://www.youtube.com/watch?v=TH9VCN6UkyQ
I don't care about the specifics of his language but I think he brings up a very valid point which my summary would be, we should design a language that is both performant AND fun to program. Fun = type less boilerplate, make refactoring easier.
C++ is not that language and from what I've read Rust is not that language either. C++ is stuck with backward compatibility so it can arguably never achieve those goals. Rust didn't seem to have those goals it mind, it had other goals so it's not really a fun language either. Too verbose or so I've read.
Jonathan Blow's language is not memory safe, and is not trying to be. That may well be fine for his domain, but not for ours.