How does Rust compare with C++11 as a language? C++11 seems to (in some ways) have caught up with what Rust has to offer (compared to older C++ versions) e.g. smart pointers, concurrency and regexes part of the standard library
How does Rust compare with C++11 as a language? C++11 seems to (in some ways) have caught up with what Rust has to offer (compared to older C++ versions) e.g. smart pointers, concurrency and regexes part of the standard library
Rust is inherently memory safe - however in practical terms this isn't important for most applications. If you are writing security critical applications Rust will provide you with some very important guarantees (ie. there are certain mistakes which are inherently not possible in the language). C++ doesn't really guarantee anything and if you're an idiot you can shoot yourself in the face. However in practical terms memory management in C++11 is very straightforwards and C++11 compliant code (ie. using the STL and not writing it like C) is very safe and clean. You're not mucking with raw pointers anymore
The main issue I see is that Rust is still in early development. It may or may not get "big" in the coming years. And library support is ... lacking
In contrast C++ has the STL and boost and every library under the sun. I haven't working with a lot of other languages extensively, but I've never seen anything as clean, robust and thorough as the STL and boost. C++ will remain relevant for a long long time. If Rust takes off in a big way, you'll be well positioned to jump ship.
However, I cannot agree with you that Rust's safety guarantees are not useful for most C++ programs, or that you have to be an "idiot" to do memory-unsafe things in C++11. Someone at Yandex recently did a presentation about Rust [1] in which they pointed to a bit of (completely idiomatic!) C++11 code that caused undefined behavior. The audience, full of seasoned C++ and Java developers, was asked to identify the problem. Not one of them could point to what was causing it (the compiler certainly didn't). The next slide demonstrated how Rust's compiler statically prevented this issue. The issue could have taken weeks to surface and days to track down, and the C++ compiler simply didn't have enough information to determine that it was a problem. This is something that happens over and over to anyone using C++, in any application, not just security-critical ones.
I'm not saying C++11 doesn't improve the situation, because it does--it would be disingenuous to say otherwise. But it's equally disingenuous to imply that C++11 makes memory management straightforward or safe. It does not.
[1] http://habrahabr.ru/company/yandex/blog/235789/ (note: the presentation and site are in Russian).
It would be great to have at least that segment of the talk translated. Sounds like a good example.
std::string get_url() {
return "http://yandex.ru";
}
string_view get_scheme_from_url(string_view url) {
unsigned colon = url.find(':');
return url.substr(0, colon);
}
int main() {
auto scheme = get_scheme_from_url(get_url());
std::cout << scheme << "n";
return 0;
}If I have a string like "foo bar baz" and I want the second word, should I copy out that data into a whole new string? That seems rather inefficient.
(How is a compiler going to optimise that away?)
Keep in mind that on a 64-bit architecture a view is at least 16 bytes large and that small strings can be copied to the stack resulting in better locality and reduced memory usage.
Last but not least, with copy elision, your temporaries might not even exist in the first place.
Example:
std::string data;
// ...
auto str = data.substr(2, 3);
// pretty sure str will be optimized away
if (str[0] == 'a') std::string str = data.substr(2, 3);
return str;
to only allocate one new string (for the return value of substr), instead of two. There's no way the compiler can get out of constructing at least one std::string for the return value, especially if there's any form of dynamic substr'ing (e.g. parsing a CSV file with columns that aren't all the same width).Sharing is only multithreading unfriendly if there's modification happening, and modification of textual (i.e. Unicode) data is bad practice and hard to get right, since all Unicode encodings are variable width (yes, even UTF-32, it is a variable width encoding of visible characters).
Furthermore, a string_view is strictly better than a string for many applications, since a string_view can always be copied into a string by the caller if necessary (i.e. each function can choose to return the most sensible/most performant thing, which is a string_view if it's just a substring of one of the arguments).
The only sensible argument against string_view in C++ I know is: it's easy to get dangling references. Which is correct, but that's a general problem with C++ itself, not with the idea of string views (Rust has a perfectly safe version in the form of &str, which cannot become dangling like in C++).
> Keep in mind that on a 64-bit architecture a view is at least 16 bytes large and that small strings can be copied to the stack resulting in better locality and reduced memory usage.
No, a string_view points into memory that already exists, there's no increased memory usage; a small string copied on to the stack will be part of the string struct, which is at least 3 * 8 = 24 bytes: a pointer, the length and the capacity. Also, a memcpy out of the original string is always going to be more expensive than just getting the pointer/length (or pair of pointers) for a string_view, since the memcpy has to do this anyway.
[1]: http://en.wikipedia.org/wiki/Copy_elision
[2]: http://definedbehavior.blogspot.com/2011/08/value-semantics-...
Sharing is only multithreading unfriendly if there's modification happening, modification of textual (i.e. Unicode) data is bad practice and hard to get right
Read-only access to data indeed scales "infinitely" on modern architectures.
No, a string_view points into memory that already exists,
Yes. Right. How do you store that? You need at least one pointer and and an int or two pointers. That 16 bytes. Memcpy for a couple of bytes is very quick when it's stack to stack thanks to page locality.
Also, if you are using pointers you will have aliasing issues which will have an impact on performance. If you work by values you allow the compiler to optimize things better.
For small strings string view are just dumb and "most of the time" strings are very small.
To give a better example of why working a string view is both a bad idea and dangerous, it's as if you said "I don't want to copy this vector, therefore I will work on iterators". That's obviously a bad idea.
Not just sucked; it was entirely wrong. Copy elision is not related to std::string vs. string_view. Even with copy elision turned up to 11, returning a std::string will be more expensive than a string_view.
> How do you store that? You need at least one pointer and and an int or two pointers. That 16 bytes. Memcpy for a couple of bytes is very quick when it's stack to stack thanks to page locality.
I was very careful to cover exactly this in my comment.
Computing the memcpy is strictly more work than creating a string_view, since you need the information that is stored in a string view (i.e. pointer and length) to call memcpy.
Furthermore, the 'stack string' is actually stored contained inside a std::string value, which is larger than 16 bytes. There is no way that returning a string_view causes higher memory use at the call site than returning a std::string. (If you're complaining that it forces old strings to be kept around, well, you can always copy a string_view to a new std::string if you need to, i.e. a string_view can do the expensive 'upgrade' option on demand.)
Here's the quote from my comment above:
> a small string copied on to the stack will be part of the string struct, which is at least 3 * 8 = 24 bytes: a pointer, the length and the capacity. Also, a memcpy out of the original string is always going to be more expensive than just getting the pointer/length (or pair of pointers) for a string_view, since the memcpy has to do this anyway.
> Also, if you are using pointers you will have aliasing issues which will have an impact on performance. If you work by values you allow the compiler to optimize things better.
You do realise that a std::string contains pointers and so on inside it? Furthermore, the small string optimisation (copying to the stack) means every data access to a std::string includes an extra branch.
> For small strings string view are just dumb and "most of the time" strings are very small.
So instead of just having a cheap reference into a string you're happy with the overhead of a function call (memcpy) and a pile of dynamic branches? I wouldn't be surprised if the branches are the major performance burden for std::string-based code that is processing a pile of substrings of some parent string. In this case, the data from the string_views will normally be in cache anyway (i.e. it will've been recently read by the function that decides who to slice into the string_view).
> To give a better example of why working a string view is both a bad idea and dangerous, it's as if you said "I don't want to copy this vector, therefore I will work on iterators". That's obviously a bad idea.
It's not obviously bad to me. In fact, it seems very reasonable to work with iterators rather than copying vectors (isn't that exactly what the algorithm header does?).
If your problem is that it is unsafe and hard to avoid dangling pointers etc, that's just a fundamental problem of C++ and is unavoidable in that language. One fix would be to use Rust; it handles iterators and string_views safely.
const regex r(R"(meow(\d+)\.txt)");
smatch m;
if (regex_match(dir_iter->path().filename().string(), m, r)) {
DoSomethingWith(m[1]);
}
- What's wrong with this code? - Haqrsvarq orunivbe va P++11
- Pbzcvyre reebe va P++14
- .fgevat() ergheaf n grzcbenel fgq::fgevat
- z[1] pbagnvaf vgrengbef gb n qrfgeblrq grzcbenel
(http://rot13.com/ 'd if you want to guess.) - iterator invalidation
- dangling references
- buffer overruns
- use after move (and somewhat, use after free)
- general undefined behaviour (e.g. overlong shifts, signed integer overflow)
And there's more to memory safety than security critical applications. Rust means you spend a little more time fighting the compiler, but a lot less time fighting the debugger and a lot less time trying to reproduce heisenbugs caused by data races/undefined behaviour.Of course, the library/tool support is indisputably in C++'s favour.
> if you're an idiot you can shoot yourself in the face
If you're a human you will shoot yourself in the face. It just takes far too much brain power to write correct C++ always (a single mistake leads to brokenness), especially in a team where there can be implicit knowledge about how an API works/should work that may not be correctly transferred between people.
On the other hand, it's somewhat ironic that you point to overlong shifts as a C++ problem when Rust has the exact same behavior. What does this function return?
pub fn f(x: uint) -> uint { x >> 32 }
Honestly, I loved the idea of Rust. I was sold a memory-safe C++, and that sounded awesome. But what I got instead was an ML with better low-level support; it felt like an enormous bait-and-switch, as nobody is interested in yet-another-functional-language.> use-after-move [...] ---references outlasting their owner---
Not really, e.g.
std::unique_ptr<int> x(1);
foo(std::move(x));
std::cout << *x; // undefined behaviour
Unless you mean something other than `&` references.> Honestly, I loved the idea of Rust. I was sold a memory-safe C++, and that sounded awesome. But what I got instead was an ML with some low-level extensions; it felt like an enormous bait-and-switch, as nobody is interested in yet-another-functional-language.
Something in this sentence has to be wrong, since people are clearly interested in Rust: either people are interested in YAFL or Rust isn't what you seem to think it is.
Anyway, that just sounds like a 'problem' with your background/expectations and/or whoever sold it to you. Rust is a C++ competitor (i.e. targets the similar low-level space) but it is not definitely trying to just be a C++ rewrite fixing the holes. I don't think there's any official marketing implying the latter.
In your unique_ptr example, you're right: the reference doesn't outlast its owner, but it becomes a dangerous zombie after getting its guts removed. It is worth mentioning that the behavior may or may not be UB depending on how `foo` takes its parameters: std::move is really just a cast.
Maybe interest is the wrong word to use; many functional languages have generated a lot of interest, but this interest has historically not translated into actual mass usage. Instead, popular languages have adopted certain functional features over time (lambdas, comprehensions, type classes, etc), but have remained fundamentally Algolian for the most part. Rust seems to go in the opposite direction: start with ML (or something ML-like, anyway), and strip it down until it fits into the C++ space.
I am definitely interested in C++ replacements, to be clear. I have explored things that stray from it much more than Rust, such as Haskell and ATS, but I went into those fully expecting to see something different. But look at documents such as [1], and tell me that it doesn't create the expectation that Rust is trying to fit C++'s shoes a little too tightly. Additionally, trawling through mailing list discussions, familiarity with C-like languages seems to have been a design principle since the start (see for example the <> vs [] for generics debate).
Finally, I wasn't (and am not) passing judgment on Rust for being what it is. I was conveying my experience from being excited about it, to being less excited about it after actually learning it. I don't expect a productive discussion to come out of it; I've also seen how defensive the Rust community can be [2].
[1] https://github.com/rust-lang/rust/wiki/Rust-for-CXX-programm...
[2] https://pay.reddit.com/r/rust/comments/2bbeqe/it_started_out...
On that note, would you interpret [a] as meaning Rust is trying to be a functional language? The reality is more plagiaristic: functional language have nice features and so Rust borrows some of them. (In my mind the correct interpretation of both documents would be: Rust is a mesh of various languages with enough similarity to many for translation guides to be helpful.)
There has been syntactic decisions tilting towards C++/Java/C# programmers (like the <> for generics), but as far as I can remember those sort of decisions are all minor in terms of semantics. For the most part the actual semantic behaviours are considered in terms of "does Rust need this" rather than "will this move us more towards C++" (even if the feature was inspired by C++).
[a]: http://science.raphael.poss.name/rust-for-functional-program...
I must thank you for pointing that link out to me, though: it said what I was trying to say much better than I could in its prologue. Namely, how hard it is to sell a functional language to old-school C people, and how Rust may have a hard time with that (even if it's not a pure functional language).
Rust is not functional. It may draw heavy inspiration from statically typed FP, and closures, ADTs, pattern matching, and and expression-heavy programming style might give that impression, but it is at its heart a procedural systems language. As stated in the blog post, most of Rust's core features map directly to underlying machine instructions, and there is always the opportunity to cede control from the type system if you absolutely have to. Indeed, core library types like `Box<T>` and `Vec<T>` are at their fundamentally built on `unsafe` code.
If anything, it's the functional parts that feel bolted on: closures are crippled (though getting better soonish), the types that get spit out of iterator chains are hideous, no currying, no HKTs, functional data structures are much harder to write w/o a gc, etc.
Leaving aside whether I think that's a fair description of Rust, I think plenty of people are interested in a functional programming language without the overhead of GC that is suitable for use as a low-level systems language.
You probably shouldn't write "Nobody is interested..." when what you really mean is just "I am not interested..."
The Rust compiler is vastly smarter and gets you type checking you have to pay out the nose for in C & C++. I'm a fanboy of Rust, but I would suggest looking hard at Rust for any C or C++ production code going forward. (My default going forward for this space will be Rust unless overriding considerations say otherwise).
I think (coming from a dynamic language world) the memory safeness is what pulls me towards Rust. But from what you say and what I've read elsewhere, that was old-style C++ and not C++1[17].
Thanks!
My advice is, if you're learning it for work, then go with C++. Even if it succeeds, it will take some years for Rust to be mainstream and as pointed out the library support is great.
If you're learning it for fun or for the sake of learning something new. Then Rust is a very nice and promising language bringing things from functional languages that C++ lacks and offering very interesting tooling around it.
Whatever you choose, after you feel confident with one go and learn the other as it will probably give a better perspective in the strengths and/or weaknesses of both.
I haven't touched Rust since 0.7 (where I abandoned it after filing two bugs on the project before I even got argument parsing working), but it's the most promising of the new batch of languages in my mind, and I have some side projects in mind I'd like to try when 1.0 comes out.
Steve Klabnik has been doing a ton of work on the docs, and that has brought a lot of "coming from an HLL" perspective to them.
Also, I'm coming mainly from a C background, not higher-level language.