We made Rust because you can't actually retrofit it's guarantees on C++ without breaking backwards compatibility. The CPP Core Guidelines are an example of this: Herb said that data race prevention is a non-goal, and in general, how it interacts with concurrency is not yet understood.
Don't get me wrong I respect Rust core team as theorists and think rust is an important research project.
But in C++ if you have a const member function F() then F() can only change member variables that use the "mutable" key word.
If you default to all methods are const you have much much safer C++. As safe as rust? not probably but combined with liftimes proposal and static analysis rules we have a language good engineers can sufficiently work with.
struct Foo
{
mutable auto a = 4;
auto increment() const -> void
{
this->++a;
}
};
If we train engineers to use const member functions, then the programmer is forced to think about state and mutation.
As C++98 PTSD begins to fade and C++17 becomes the cultural mental image of the language, the argument for switching to rust will become less and less compelling.
Eventually, the only "advantage" will be ML influenced syntax.
The reason is there are only three languages that can easily call into C++ code. Objective-C (swift by proxy), D, and of course C++.
Why rust didn't prioritize compatibility with C++ like C++ did with C boggles my mind. Why do you think C++ became so popular? Near perfect C compatibility (until recently) is literally the only reason. Instead of focusing on pragmatisim Rust focused on language purity.
> But in C++ if you have a const member function F() then F() can only change member variables that use the "mutable" key word.
This is not nearly enough. See the examples I gave in my other post.
Besides, all "const" means is "I won't mutate 'this'". It doesn't mean someone else who has a non-const reference to your object won't mutate your object. That makes it largely useless for reasoning at a local level.
> If we train engineers to use const member functions, then the programmer is forced to think about state and mutation.
The programmer may think about it, but the compiler doesn't enforce it at all.
> As C++98 PTSD begins to fade and C++17 becomes the cultural mental image of the language, the argument for switching to rust will become less and less compelling.
Only if you don't understand how Rust works.
> Why rust didn't prioritize compatibility with C++ like C++ did with C boggles my mind.
Because C++ compatibility is impossible without being a derivative of C++, and C++ is hopeless in the safety department. I believe it is impossible to make C++ memory safe without making it not C++ anymore.
> Instead of focusing on pragmatisim Rust focused on language purity.
> Because C++ compatibility is impossible without being a derivative of C++, and C++ is hopeless in the safety department. I believe it is impossible to make C++ memory safe without making it not C++ anymore.
Do you consider D to be a derivative of C++? I don't. They have a near perfect C++ interface. D can even catch C++ exceptions. It's pretty sweet. You can hear about the black magic Andrei used to get that to work here:
Side note: I'm actually thinking the Rust and C++ comparison misses the mark. First of all, C++ cannot be obsoleted by rust, it's economically impossible. Secondly, I don't even think they compete in the same domain space. Rust is actually lower level than C++, in that it does lower level stuff in a more natural way. It's C, that Rust makes obsolete; which is something C++ totally failed to do (because of it's RTTI, exceptions, and other runtime features -- all things you and the rest of the Rust team intelligently scrapped). After trying embedded Rust, I'm completely done using C (and the C parts of C++).
IMO the advantages C++ still has over Rust are:
- libraries, libraries, libraries
- C++ has so many great libraries
- *writing* C++ libraries with SFINAE sugar is the bees knees.
- much better support and tooling, but this is for sure a temporary advantage.
- template meta-programming feels like a different language, it's dynamic, functional, and the difference is refreshing. I like the mental switch you have to do to go from writing a C++ template to writing a C++ model.
- as much as header files suck, they let you skim an API much more easily than Rust or Swift.
- performant, memory safe data structures:
For example, it's trivial to write a low overhead pointer that can't dangle, throws an exception on nullptr deference, and that safely points to an object it doesn't own (even if that object is its owner). With this and a unique_ptr composed with similar safety mechanisms you now have the primitives needed to write any data structures or algorithm so that it is protected from undefined and unsafe behavior, yet remains performant. This solution has significantly less overhead than using reference counted pointers like shared_ptr derivatives or Rc. The performance tradeoff over using raw pointers is minuscule and totally dwarfed by the gain in safety guarantees. From what I've observed, you can't do this in Rust, if you try to write a doubly linked list you either have to use C-pointers that can dangle inside unsafe blocks, or eat the Rc overhead.
EDIT: How could I forget to mention the glory that is constexpr?
Advantages of Rust over C++ (again, IMO):
- the correct defaults (aka opposite of C's insanity)
- best C++11 features built in and defaulted (move semantics, unique_ptr is Box<T>, Rc is shared_ptr etc..)
- Bad ass embedded capabilities. You don't nearly feel as crippled like you do in C++ without the STL.
- the safety guarantees are real, and they're awesome
- move semantics into closures looks so goddamn elegant. I love it
- inline assembly doesn't feel like a hack
- modules and cargo rock
- secretly and surprisingly similar to ruby in a lot of places :)
- it wants you to think functionally, and unlike Swift, it means it. This is a good thing.
> Do you consider D to be a derivative of C++? I don't. They have a near perfect C++ interface. D can even catch C++ exceptions.
It's not a near-perfect C++ interface, because D can't instantiate C++ templates on its own.
> First of all, C++ cannot be obsoleted by rust, it's economically impossible.
Rust can make C++ obsolete in a technical sense. But I agree of course that C++ is immortal.
> For example, it's trivial to write a low overhead pointer that can't dangle, throws an exception on nullptr deference, and that safely points to an object it doesn't own (even if that object is its owner)
Wrong. You can't write a pointer that can't dangle in C++. Think iterator invalidation, "this" invalidation, etc. I've given innumerable code samples to prove this in the past.
It's hard to argue about this abstractly, so let's do this. If you give me the C++ code for a smart pointer that you claim is safe, I will construct you a code example showing use-after-free using that pointer.
> From what I've observed, you can't do this in Rust, if you try to write a doubly linked list you either have to use C-pointers that can dangle inside unsafe blocks, or eat the Rc overhead.
Yes, you can't do this in Rust. You also can't do it in C++.
C++ is good at making you think that you're using pointers that can't dangle, as evidenced by posts like yours. It's not good at actually preventing things like use-after-free.
> - template meta-programming feels like a different language, it's dynamic, functional, and the difference is refreshing. I like the mental switch you have to do to go from writing a C++ template to writing a C++ model.
I much prefer the static approach of Rust's generics to the untyped templates of C++--the fact that templates feel like a different language is a disadvantage in my view--but this is a huge tradeoff and it largely comes down to taste. This is unlike the memory safety guarantees of Rust pointers, which are not a matter of taste; safety is a formal property that C++ references simply do not satisfy and cannot satisfy no matter what.
I'm using a modified version in a personal library. It throws an exception on a nullptr dereference. I'm not sharing that version here because A. it's not ready, and B. You're definitely a better programmer than me and I don't feel like getting totally shit on today :)
I thought the whole point of the "world dumbest smart pointer" was that it's automatically nullptr'd for you so it can't dangle. I'm looking forward to hear why this isn't true. I've actually been brooding over this perceived advantage of C++ for a while now.
To use the header I linked it should be
`nonstd::observer_ptr<std::string> p(&strings[0]);`
You also need to dereference p to use the stream operator.
I'm disappointed you presented such a contrived example. You also deliberately avoid const-correctness. That's a little insidious. You need to const everything manually in C++, so as a good C++ programmer you should const everything as you write C++. Only carefully omit const when you need mutability.
We all know C++98 is dangerously unsafe. Let's write your example in C++11.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <utility>
#include "observer_ptr.h"
// kill the noise
using std::cout;
using std::vector;
using std::string;
using nonstd::observer_ptr;
int main() {
vector<string const> const strings { "Hello" };
observer_ptr<string const> const p(&strings[0]);
strings.clear(); // not gonna happen
cout << *p;
}
» clang++ -std=c++11 immutable.cpp
immutable.cpp:11:5: error: member function 'clear' not viable: 'this' argument has type 'const std::vector<const std::string>' (aka 'const
vector<const basic_string<char, char_traits<char>, allocator<char> > >'), but function is not marked const
strings.clear(); // not gonna happen
^~~~~~~
/usr/local/Cellar/llvm/HEAD/bin/../include/c++/v1/vector:735: 10: note: 'clear' declared here
void clear() _NOEXCEPT
^
1 error generated.
You changed the vector from a mutable one to an immutable one. That isn't the same code. What if you need a mutable vector?
Also, consider this:
std::observer_ptr<std::string const> const foo() {
std::vector<std::string const> const strings { "Hello" };
return std::observer_ptr(&strings[0]);
}
std::cout << foo(); // use after free, even with const!
This is how these discussions always go: someone presents an example of use-after-free in C++, someone else says "that's contrived"/"not real C++", and the discussion continues indefinitely. At some point we are going to just start debating "does anyone actually write use-after-frees in modern C++?", which is also a question we have empirical answers to by way of vulnerability databases and bug trackers, and the results don't look good for C++ there either.
If your contention is that observer_ptr is completely safe, then we could have easily added it to Rust. We didn't for a reason: it's unsafe in Rust and unsafe in C++.
Yes, if you make a dangling pointer factory or introduce global state then you can break things. Luckily you wouldn't use anything like that to write the safe and performant data structures I'm talking about.
They are protected from undefined behavior, performant, and are excellent middle ground between reference counting or raw pointers.
I like Rust I just don't understand how I'm wrong about this single little pro C++ has over it.
> Luckily you wouldn't use anything like that to write the safe and performant data structures I'm talking about.
Are you talking about how you can write data structure abstractions in C++ using unsafe feature (like std::observer_ptr) that are themselves safe to use? If so, that's exactly Rust's model! Rust's linked lists, for example, are written using unsafe code internally and present a safe interface.
> I like Rust I just don't understand how I'm wrong about this single little pro C++ has over it.
Because it's not a pro that you can write unsafe code in C++ without saying "unsafe"!
Highjack the term safety, use contrived examples, and slippery slope arguments all you want. You know exactly the advantage I'm talking about.
It's fine, I get you're very invested in this. I'm gonna go back to making stuff. I like the work you guys are doing and I don't want to give the impression I do not.
It's not "hijacking" the term "safety"--it's having a precise definition of it. It's not a slippery slope to show both examples of unsafety and to show that this lack of safety leads to security problems in the real world.
I understand I sound irritated here, but that's because we went through a lot of work to make Rust safe. It is irritating when people claim that languages that didn't even try to do any of that work somehow did it better than Rust did.
Rust is CLEARLY better at safety than C++. Stop seeking validation for that. Everyone who knows about Rust is convinced. I'm sorry it must be frustrating talking to someone emotionally disinterested about the whole thing. I've just been thinking about C++'s advantage in that particular instance from a software architecture point of view and you kind of failed to persuade me otherwise.
That being said, Rust is definitely a language I will continue to study. I have no doubt it's making me a better programmer.
EDIT: I find it funny that I'm always defending C++ here. C++ is like that kid at school who's not really your friend but you always get stuck doing project with. You end up spending a lot of time with him, so when the time comes that you hear someone talk bad about him you feel obligated to point out that he's a actually pretty alright dude.
It seems you're sort of missing a big point here. You demonstrate how, with sufficient vigilance, you can write safe C++. The goal of Rust is to move the vigilance into the language and make the unsafety opt-in rather than attempt-to-run-from.
So now using const is some kind of difficult task? Not much cognitive overhead there. I thought everyone was onboard with immutability these days.
My point is that safe low-overhead data structures with non-dangling, pointers protected from nullptr dereferencing ARE possible in C++ and not Rust. Yes, you have to put const after every type. Call that vigilance if you want. It's a really small price to pay IMO.
Edit: Down voted again. What's wrong with my point or code? I'm curious.
> So now using const is some kind of difficult task? Not much cognitive overhead there. I thought everyone was onboard with immutability these days.
The fact that if you make everything totally immutable you can write safe C++ is (a) not true; (b) even if it were true, a totally irrelevant point in practice since not everything can be immutable. "C++ is memory-safe if you don't mutate anything anywhere" is a completely uninteresting point to the real world (even if it were true).
> My point is that safe low-overhead data structures with non-dangling, pointers protected from nullptr dereferencing ARE possible in C++ and not Rust. Yes, you have to put const after every type. Call that vigilance if you want. It's a really small price to pay IMO.
No, it's completely wrong. You have no protection from dangling references in C++.
A "factory function" is just a function. Unless you have some way of statically distinguishing "factory functions" from functions, you are now arguing "you can write safe C++ if you make all your data immutable and you don't use functions". Besides the fact that this is not true (consider the destruction order for rvalues), how is this a statement that is possibly relevant to the real world?
> as much as header files suck, they let you skim an API much more easily than Rust or Swift
Just to reply to this individual point, rustdoc is fantastic. Personally I much prefer having an HTML-rendered "header file" for a crate's API. Even if they aren't hosted, it's trivial to generate one yourself using `cargo doc`. To be maximally useful, the author needs to have made doc comments, but at least there's a standard format for that, and you might not have anything like that in a header file anyways.
Super cool. I like everything they have going on with the triple slash comments too. Now all we need is an IDE that can get those docs up in a key stroke!
We made Rust because you can't actually retrofit it's guarantees on C++ without breaking backwards compatibility. The CPP Core Guidelines are an example of this: Herb said that data race prevention is a non-goal, and in general, how it interacts with concurrency is not yet understood.