| > Most C++ programmers do not fear using references or pointers because of invalidation. Compared to Rust? I doubt that. Like I said, Rust lets you dance near the line. This is from experience in various large C++ codebases. I'm not saying people use refcounting a lot, I'm saying it gets used more than Rust. YMMV though, so it does boil down to a matter of different experiences here. We'll probably have to agree to disagree. > In fact it often has false positives requiring borrow gymnastics because it is too primitive. Not really. Aside from non-lexical borrows and a couple other nice-to-have things (but not necessary), the borrow checker is pretty precise for what it tries to prove. One might argue that the guarantees Rust tries to maintain (one writer or multiple readers for a piece of data) are too primitive. I don't think that's true. Doing a context-sensitive/flow-sensitive analysis might lead to more patterns being allowed but it's hard to scope guarantees when the analysis is interprocedural -- stopping at function boundaries makes sense to me. > The Rust compiler does not tell you when it's impossible to satisfy your problem using borrows. It sort of does. If you try to introduce borrows and listen to the suggestions the compiler gives you, and eventually end up nowhere, it's probably not possible to do it that way. It's not perfect, but it's good enough. And it's immune to further changes -- you don't need to design your pointer usage so that it's future-proof; design it however you want, and if a future refactoring introduces a possible use-after-free, fix the compile error. > And your last line is a complete reversal of your previous attitude: Suddenly you are focusing on theory while ignoring that in practice you can not usually break out `unsafe` to solve your borrow problems. No, it's not, I'm just pointing out the equivalence. My point was that "The things you can prove statically with the borrow checker are a subset of the things that wont trigger UB in C++." is irrelevant for two reasons -- (a) in practice IMO the things Rust makes you feel safe to do is a superset of what C++ lets you feel safe to do, and (b) if we're going to talk about "all possible things C++ theoretically allows you to do", then you should include unsafe -- you were comparing "safe Rust" with "all C++", which is unfair here, since the entities to be compared for what you're theoretically allowed to do are "all Rust" and "all C++". I focused on a different flaw in your argument, so of course the focus changed. I'm not saying you should use unsafe a lot, I'm saying "what C++ lets you do is equivalent to using unsafe a bit more often in Rust". I don't endorse it, but if you consider "C++ lets you do so many things Rust doesn't" to be a plus point of C++ (I don't), then you should at least be comparing the right things and allowing yourself to use unsafe in Rust too. > more heavyweight abstractions than necessary Examples? |
Your problem is that you are trying to imagine what happens in the real world rather than just looking at it.
> This is from experience in various large C++ codebases. I'm not saying people use refcounting a lot, I'm saying it gets used more than Rust.
I count 56 imports of Arc and 37 of Rc in servo, would you wager the equivalent parts of chromium or firefox use more ref-counting?
> Not really. Aside from non-lexical borrows and a couple other nice-to-have things (but not necessary), the borrow checker is pretty precise for what it tries to prove.
The problem is that the borrow checker does not look at the function as a whole. You are a slave to the borrow checker, you can only consider other things when it has been satisfied, even if its trivially safe from a whole-function view. Some simple things aren't even expressible at all without non-lexical borrows, no matter what gymnastics you try.
> It sort of does. If you try to introduce borrows and listen to the suggestions the compiler gives you, and eventually end up nowhere, it's probably not possible to do it that way.
That sounds like a terrible workflow that I don't think Rust programmers use in the real world. I think they usually use shared ownership because they know they have shared ownership, not because they gave up on the borrow checker. Any instance of actually resorting to reference counting because of the inability to make the borrow checker happy actually counts against Rust, not for it.
> in practice IMO the things Rust makes you feel safe to do is a superset of what C++ lets you feel safe to do
People routinely do safe things in C++ that would be impossible to express in safe Rust code. You need to let go if this untruth.
> if we're going to talk about "all possible things C++ theoretically allows you to do", then you should include unsafe -- you were comparing "safe Rust" with "all C++", which is unfair here, since the entities to be compared for what you're theoretically allowed to do are "all Rust" and "all C++".
I focused on safe Rust because your argument was that safe Rust is just as powerful as C++, just proved safe statically. This is far from the truth and you refuse to accept the correction. Most people in the Rust community would probably say they accept the loss in power because they prefer absolute safety. This is a very defensive position. It's impressive just how little power Rust gives up in comparison to languages like C#/Java. But to pretend that you actually have more power in Rust due to psychological effects is just zealotry and doesn't reflect how people use C++ in the real world.
> Examples?
Any abstraction that uses dynamic checking that would be considered a bug to actually trigger. Some examples include borrowing a RefCell, random access of iterators, sequential access of certain kinds of iterator adaptors, etc.