Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Posted Aug 19, 2024 20:41 UTC (Mon) by NYKevin (subscriber, #129325)Parent article: FreeBSD considers Rust in the base system
I must admit that I'm shocked people are still talking about Rust in this way in 2024. Memory safety is not OOP, or any of the other fads that have pervaded the software engineering landscape from time to time. Memory safety is mandatory. You have to care about it, or your language has to care about it for you. There is no other way. Rust provides a unique zero-cost abstraction which enables the vast majority of a program to run at C++-like speeds without needing to care about memory safety at all. That is the argument for using it: It provides a specific, tangible benefit that is not available in most (if not all) other programming languages, and which is useful to every nontrivial program that allocates memory. Rust has a number of other, less interesting properties that also make it a good language, but borrow checking is the reason that Rust is special, and not just another "C but without all the warts" language.
There is certainly room to argue over exactly how *strong* of an argument in favor of Rust that is, and how it applies to FreeBSD's circumstances in particular. But reading the whole of Kemp's email, it does not sound as if he is seriously engaging with that argument in the first place.
As for the suggestion to ask people to reimplement some large piece of software, or else we won't take you seriously: I really want to believe that everyone in these discussions is arguing in good faith. I do not want to believe that Kemp is seriously advocating for an institutionalized hazing ritual before people may be taken seriously in this space. But I really struggle to see what kind of useful data he would expect to get out of such an effort, or in what other way it would be useful to require people to do such a significant amount of work before writing a proposal.
It makes some sense to ask to see an implementation of something useful that is not the language's own stdlib or compiler, in order to better understand how the language looks in practice (and how much it depends on external libraries, an entirely valid point that Kemp also brings up). But you don't then need to demand that the person making the proposal is also the person who authored said implementation - the benefit of that is really marginal at best, and it's severely limiting. So I'm really at a loss for what Kemp is getting at or why he thinks this is a good idea.
Posted Aug 19, 2024 23:15 UTC (Mon)
by jlarocco (subscriber, #168049)
[Link] (17 responses)
I agree, but you seem to have missed the part where the dev in the article decided not to use C++ smart pointers "" because I'm not real great with C++"".
It's absurd to advocate for Rust, while at the same time actively avoiding memory safety features in C++ to support the use of Rust. There seems to be a double standard.
Posted Aug 20, 2024 0:38 UTC (Tue)
by khim (subscriber, #9252)
[Link] (11 responses)
I don't see how. What do you call “memory safety features in C++”? ASAN or MSAN? I hope the Somers used them. But smart pointers? They are not memory safety features! They are convenience features! You can have both dangling pointers and double free even with C++ doesn't, really, have any safety features, only convenience features with “you are holding it wrong” refrain which require extensive knowledge about how temporary variables work work, how things like mandatory RVO works and so on to be able to use them without causing complete meltdown of you program. And I'm speaking as someone who used C++ for more than 20 years and is still actively using it. For someone who stopped actively following it years ago the “let me stick to something I know 100%” approach is not a bad choice.
Posted Aug 20, 2024 4:26 UTC (Tue)
by jlarocco (subscriber, #168049)
[Link] (10 responses)
> I don't see how.
I'm assuming Rust code that went out of the way to use "unsafe" would be called out in review and not allowed. So why allow C++ code using "raw" pointers?
> But smart pointers? They are not memory safety features! They are convenience features! You can have both dangling pointers and double free even with unique_ptr (the closest thing C++ have to “safety feature”) and string_view or span make these easier than raw C-style pointers! And don't even start on optional and visit!
I took "smart pointer" to mean std::shared_ptr, which is nothing like unique_ptr, and much more like Rust's borrow checker.
> For someone who stopped actively following it years ago the “let me stick to something I know 100%” approach is not a bad choice.
If that's what they want to do good for them, but that doesn't mean they get to commit crap code.
Posted Aug 20, 2024 6:11 UTC (Tue)
by roc (subscriber, #30627)
[Link]
Because "this" is a raw pointer in C++. So are all C++ references, effectively --- there is nothing to stop the referenced object going away, leaving behind a dangling reference.
Another big use of raw pointers or references is out-parameters to functions. No-one's going to use smart pointers there.
The dialect of C++ that doesn't use "this" or references anywhere would be a very strange one indeed. No-one does that.
Posted Aug 20, 2024 8:58 UTC (Tue)
by excors (subscriber, #95769)
[Link] (2 responses)
I think it's the other way round. std::shared_ptr<T> is Rust's Arc<T>: thread-safe reference counting, with significant run-time overhead. std::unique_ptr<T> is a crude version of Rust's T: single ownership with move semantics, almost zero run-time overhead, and you can borrow a non-owning reference to the owned object (T& or const T& in C++, &mut T or &T in Rust). (But std::unique_ptr is worse because it only supports heap-allocated objects, not stack-allocated; and it has a little more run-time overhead, checking for nullptr to see if it still owns the object, whereas Rust tracks ownership at compile-time; and unique_ptr gives lots of opportunities to violate memory safety, though it's still much safer than raw pointers.)
std::shared_ptr and std::unique_ptr are both typically called smart pointers, including by the C++ specification itself (section 20.3). And the context of the FreeBSD post is code where objects are allocated and freed within a single function, so std::unique_ptr would be much more appropriate (or maybe std::vector since they're mostly arrays).
Posted Aug 20, 2024 18:09 UTC (Tue)
by NYKevin (subscriber, #129325)
[Link] (1 responses)
Posted Aug 21, 2024 11:13 UTC (Wed)
by excors (subscriber, #95769)
[Link]
E.g. in C++ you may avoid using a raw T because it has an expensive copy constructor and no move constructor, or because you suspect it might and its documentation doesn't provide a clear answer, or you're writing generic code so you can't know in advance. It's safer to wrap everything in pointers to avoid surprising performance issues, whereas in Rust you know it's always possible and cheap to move any T. In particular I think it's fairly common to use std::vector<std::unique_ptr<T>> to ensure there's no unwanted copying when the vector grows, whereas in Rust you'd just use Vec<T>.
std::unique_ptr<T> can be used in things like the pimpl idiom, i.e. a public class's member variables where the definition of T is private and should not be accessible to users of the class. You don't need to do that in Rust, since a public struct can have fields with private types and the visibility rules keep the implementation hidden. (That does mean the private type becomes part of the public ABI, but Rust doesn't provide ABI stability anyway.)
std::unique_ptr<T> can be used for members with delayed initialisation, where you don't want to (or can't) call the default constructor of T, so you leave the unique_ptr empty until it's initialised later. In Rust you can use Option<T> instead, which admittedly isn't T but it's much closer to T than to Box<T>, since there's no heap allocation.
std::unique_ptr<T> can be used for local variables when T is too large to comfortably go on the stack. In Rust, tough luck - when you create a new Box<T>, the compiler's probably going to construct a temporary T on the stack before memcpying it into the heap. You'll need to make sure your environment has large stacks, and then you might as well just keep T on the stack and skip the boxing. (And avoid defining large structs; e.g. never use large statically-sized arrays, use Vec instead.)
There are cases where you'd still want a singly-owned Box<T>, like polymorphic types (Box<dyn Trait>) or recursive types (binary trees etc), but I suspect that's rare compared to all the other uses for std::unique_ptr that shouldn't be translated into a Box.
Posted Aug 20, 2024 9:47 UTC (Tue)
by khim (subscriber, #9252)
[Link]
Nope. You either have no idea how Rust works or are mixing Rust and Swift. Swift does automatic reference counting, Rust doesn't. Rust's references are much closer to Sure, but chances are high that if someone uses things they know and understand the end result would be better than if they would use something they don't, really, understand. Situation with Rust is radically different: attempt to abuse something leads to compile time errors (there are certain soundness error where you can actually write code that compiler accepts and that is not correct, but it's very hard to hit these by accident… that's why Rust features are explicitly a safety features, but very explicitly not a security features, that's something else, again).
Posted Aug 20, 2024 12:37 UTC (Tue)
by tialaramex (subscriber, #21167)
[Link] (4 responses)
std::unique_ptr<Goose> is Box<Goose>. This Goose lives on the heap somewhere, when we're done with it we need to destroy that heap allocation as well as whatever (if anything) should happen to the Goose. Both these types provide mechanisms to get the actual raw pointer out, and to convert a raw pointer back into the type, they're very parallel. It's easier to see what's going on when you Box::leak(a_goose) but the same API exists for std::unique_ptr just with a less explanatory name.
std::shared_ptr<Goose> is Arc<Goose> or in a few cases on some toolchains, magically Rc<Goose> instead -- hope the tooling was correct if that happened. There's a Goose, on the heap, and when we "duplicate" it we just get the same Goose again but we're counting how many distinct references to it exist, and once there aren't any we can get rid of the Goose. Again the APIs are parallel, std::weak_ptr is Weak<T> the most substantial difference is that C++ provides a mechanism to decide explicitly whether the control block is part of the same memory allocation. If it is, even though the std::shared_ptr<Goose> is "gone" we can't free the memory because our control block is in there, and that's needed for any remaining std::weak_ptr, but on the other hand if it isn't then we're doing a separate allocation for an item and its associated control block.
It is a problem that C++ programmers see "more modern" as safer, when it maybe isn't. For example Arc<Goose> doesn't allow multiple mutable references, but std::shared_ptr<Goose> is fine with that, and of course although programmers in C++ know they're supposed to never make mistakes they're only human, two references to the same Goose might get modified without a happens-before and now we've got a data race, it's Undefined Behaviour.
Posted Aug 20, 2024 12:57 UTC (Tue)
by khim (subscriber, #9252)
[Link] (2 responses)
Modern C++ absolutely is safer, but the big problem is that modern C++ is not a collection of modern features. Instead of being something in the language it's more of something outside of the language. Core guidelines of collection of Abseil tips or things like that. With some support from the language, sure, but mostly documents for humans. And they are pretty big. Learning them may improve robustness of your code but blind attempt of using tools designed to support these guides may easily lead to the opposite. That's the issue: unlike Rust tools that are designed to work with ignorant users C++ tools are very much not designed for that. Rust tools wouldn't withstand in the face of malicious intent (that's why they are not security tools, but safety tools) but they handle ignorance just fine. C++ doesn't handle it well.
Posted Aug 20, 2024 15:59 UTC (Tue)
by tialaramex (subscriber, #21167)
[Link] (1 responses)
You could argue they aren't _less_ safe, but even where that's true, they aren't _more_ safe, they're just more modern
For string_view in particular we know people took code that was wasteful but correct (copying strings needlessly) and converted it to code that's fragile or outright wrong through use of string_views whose underlying string might vanish while they're in use.
Posted Aug 20, 2024 16:22 UTC (Tue)
by khim (subscriber, #9252)
[Link]
No. They are safer and faster. They are combining two unrelated parts and they don't rely on zero-termination. These things already give you more safety then C counterpart.
They are not memory safe, true, but they are safer. The danger is, of course, in thinking: oh, these are modern facilities, surely they should make everything memory-safe if not abused? And no, they don't give you that kind of safety. Yes. But if you would try to convert that code into pile of raw pointers chances are that you would screw up everything even more badly. I would say that problem is not that “Modern C++” is not safer than “old C++” or “C” but that it's sold as if it's improvements in safety are comparable to what Rust offers, where in reality they are marginal at best.
Posted Aug 21, 2024 3:12 UTC (Wed)
by NYKevin (subscriber, #129325)
[Link]
C++ smart pointers are not directly analogous to Rust smart pointers, because C++ smart pointers have an empty state, and Rust smart pointers don't. This is required for C++ move semantics* to work. So all C++ smart pointers are de facto equivalent to Rust Ptr<Option<T>> (or perhaps Option<Ptr<T>>) rather than Ptr<T> (for the appropriate value of Ptr), but because C++ is unsafe, they all implicitly call unwrap_unchecked() (or as_ref().unwrap_unchecked(), etc.) every time you dereference them.
* In C++, moving an object has no effect on the original object's lifetime. A move constructor is like a copy constructor, except that it is allowed/expected to take ownership of the original object's resources. The original continues to exist until whenever it would otherwise be destroyed, and then its destructor is run if it has one. Because we don't want unique_ptr to double-free every time it is moved, we have to put it in an empty state where it won't free anything. In Rust, moving an object causes the original to magically vanish at the moment the move completes, without running the destructor at all. Well, actually there's no magic, because Rust is a systems language, so this is all well-specified and you can do it by hand in unsafe Rust. Moving an object is implemented as if by memcpy, and once the byte copy completes, the original object is "garbage" (i.e. you must not access it or otherwise interact with it, not even to run its destructor), and you may then deallocate or reuse its memory if you so desire. You can see a worked example of this in the section of the Rustonomicon where they implement Vec. In safe Rust, the compiler takes care of these details automatically, and causes it to look like the object has "magically vanished" (by the devilishly clever method of throwing a compile error if you try to interact with it after moving from it).
Posted Aug 20, 2024 0:42 UTC (Tue)
by NYKevin (subscriber, #129325)
[Link] (4 responses)
That is not what Somers said. He said he avoided some C++ features because he was unfamiliar with them, not because he wanted to use Rust instead. He did also say that he would have used Rust instead if possible, but that's an entirely different thing to intentionally sabotaging FreeBSD's C++ in order to make it compare unfavorably to Rust (which is what you appear to be describing). I don't think anyone has credibly accused him of doing that.
One could, I suppose, contend that a volunteer is wrong to choose to spend his time learning Rust instead of C++, or that it is somehow "unfair" for someone to prefer learning one over the other. Personally, I am not willing to make arguments of that form, because it strikes me as presumptuous. Volunteers may decide how to spend their own time.
I would like to interpret your argument as something more sensible, but I'm struggling to think of anything else it could mean.
Posted Aug 20, 2024 4:34 UTC (Tue)
by jlarocco (subscriber, #168049)
[Link] (3 responses)
There are smart pointers (std::shared_ptr, std::unique_ptr, etc.) ASAN, valgrind, coverity and linters, etc. that help make C++ safer but don't require an entirely new language and compiler infrastructure.
Posted Aug 20, 2024 8:07 UTC (Tue)
by taladar (subscriber, #68407)
[Link]
Posted Aug 20, 2024 8:34 UTC (Tue)
by farnz (subscriber, #17727)
[Link]
Note that he didn't write C++ specifically; he used a few C++ features in his C code, but the languages he knows well enough to be confident with are C and Rust.
This means that for him to learn to write memory-safe C++ would involve him learning C++; what's the incentive to learn and get good at C++ if you're happy with Rust, competent in C, and only use C++ in areas where C's a bit lacking but Rust is not available?
Posted Aug 20, 2024 10:05 UTC (Tue)
by moltonel (subscriber, #45207)
[Link]
From the PoV of an individual developer, getting memory safety in C++ is a more extreme endeavor than getting it in Rust. C++ requires a lot more knowledge, tooling, and diligence to get to the same safety level as Rust's baseline. You can't blame Somers for investing more skills in a language that gets more things done.
Posted Aug 21, 2024 16:31 UTC (Wed)
by mrugiero (guest, #153040)
[Link] (1 responses)
Yeah, this part angered me a bit. This was just condescending elitism with no argument behind it. There were some quite reasonable arguments against it later on, but this wasn't one.
Posted Aug 25, 2024 16:51 UTC (Sun)
by marcH (subscriber, #57642)
[Link]
The most "angered" person should be PHK himself for sabotaging all his reasonable arguments with such an introduction.
Memory safety is still considered nice-to-have?
> There seems to be a double standard.
Memory safety is still considered nice-to-have?
unique_ptr (the closest thing C++ have to “safety feature”) and string_view or span make these easier than raw C-style pointers!
And don't even start on optional and visit!Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
> I took "smart pointer" to mean std::shared_ptr, which is nothing like unique_ptr, and much more like Rust's borrow checker.
Memory safety is still considered nice-to-have?
std::unqiue_ptr than to anything else in a sense that Rust guarantees that when you are modifying something it's safe to do that: all other observers than may be affected by your changes are quiescent.std::unqiue_ptr does more-or-less the same thing, but with a bad twist: if you are doing something wrong you are not getting a compile-time error, but runtime error — but only if you are lucky. That's why it's convenience feature and not a memory safety feature!Memory safety is still considered nice-to-have?
> It is a problem that C++ programmers see "more modern" as safer, when it maybe isn't.
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
> You could argue they aren't _less_ safe, but even where that's true, they aren't _more_ safe, they're just more modern
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
Memory safety is still considered nice-to-have?
