Live data from Hacker News

Making C++ safe without borrow checking, reference counting, or tracing GC

verdagon.dev

111–120 of 226 posts

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#111
post #91

Earlier quoted context omitted.

> Slotmap uses unsafe everywhere, it's a memory usage pattern not supported by the borrow checker. Is disabling the borrow checker really a common pattern in Rust? Wrapping "unsafe" code in a safe interface is a common pattern in Rust, yes. There is absolutely nothing wrong with using "unsafe" so long as you are diligent about checking invariants, and keep it contained as much as possible. Obviously the standard libr…

If unsafe means “safe but the compiler cannot verify” then I guess just consider .cpp to mean “safe but the compiler cannot verify” and we have suddenly made C++ memory safe

Sure but you're missing the

> so long as you are diligent about checking invariants

part. Could you go through and check all the parts of a huge C++ codebase to make sure invariants are held as opposed to a few hundred lines of unsafe Rust code?

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#112

Earlier quoted context omitted.

How do arenas prevent out-of-bound access, double free or stale pointers?

Out of bound access is avoided because you ise handles that the arena has given you, creating an invalid handle is restricted. You avoid double free because of Rust's owbership semantics that make the arena itself reaponsible for "deallocation" (which is just blanking the value and letting Drop do its thing). You avoid stale pointers because every access is checked at runtime if you're using a generational arena.

We are talking about C++ ;-)

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#113
post #111

Earlier quoted context omitted.

If unsafe means “safe but the compiler cannot verify” then I guess just consider .cpp to mean “safe but the compiler cannot verify” and we have suddenly made C++ memory safe

Sure but you're missing the > so long as you are diligent about checking invariants part. Could you go through and check all the parts of a huge C++ codebase to make sure invariants are held as opposed to a few hundred lines of unsafe Rust code?

Sure, but I think the point here is the degree.

Presumably if it takes a lot of unsafe rust lines to build something, it won’t matter if it’s 30% safe or whatever.

I just see the point of “unsafe is fine” a lot when the whole point of rust is that memory safety issues are never worth the cost.

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#114
post #111

Earlier quoted context omitted.

Sure but you're missing the > so long as you are diligent about checking invariants part. Could you go through and check all the parts of a huge C++ codebase to make sure invariants are held as opposed to a few hundred lines of unsafe Rust code?

Sure, but I think the point here is the degree. Presumably if it takes a lot of unsafe rust lines to build something, it won’t matter if it’s 30% safe or whatever. I just see the point of “unsafe is fine” a lot when the whole point of rust is that memory safety issues are never worth the cost.

Right, I guess the question is what will that proportion be when Rust is used for things like operating systems and web browsers. 30% would be untenable but a few hundred/thousand lines of unsafe code is fairly easy to put under a microscope.

For some current day research into this, there is the paper "How Do Programmers Use Unsafe Rust?"[1] which I'll drop a quote from here:

> The majority of crates (76.4%) contain no unsafe features at all. Even in most crates that do contain unsafe blocks or functions, only a small fraction of the code is unsafe: for 92.3% of all crates, the unsafe statement ratio is at most 10%, i.e., up to 10% of the codebase consists of unsafe blocks and unsafe functions

That paper is definitely worth reading and goes into why programmers use unsafe. e.g 5% of the crates at that time were using it to perform FFI.

In writing "RUDRA: Finding Memory Safety Bugs in Rust at the Ecosystem Scale" [2], I recreated this data and year-by-year the % of crates using unsafe is going down. And for what it's worth, crates are probably a bad data-set for this. crates tend to be libraries which are exactly where we would expect to find unsafe code encapsulated to be used safely. There's also plenty of experimental and hobby crates. A large dataset of actual binaries would be way more interesting to look at.

[1] https://dl.acm.org/doi/10.1145/3428204

[2] https://taesoo.kim/pubs/2021/bae:rudra.pdf

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#115
post #40

> "We'll instead take and return the vector directly" Won't this clone it?

Not necessarily, although it's a bit complicated to understand in C++. Starting with C++17, there is a feature called guaranteed copy elision that works for many/most scenarios that you would want. You need to read through the following resources to understand it fully: https://en.cppreference.com/w/cpp/language/copy_elision https://en.cppreference.com/w/cpp/language/value_category

> Not necessarily, although it's a bit complicated to understand in C++.

One could say this statement applies to most lines of C++ code. Lol

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#116

Earlier quoted context omitted.

I really love parts of rust and kinda hate other parts. but this is what really ruins it for me. I want to play. I want to knock something together and work with it and see what kind of shape it is. rust demands that I cross every last t before I can run it at all. which is great if you already have a crystal notion of what you are building

> rust demands that I cross every last t before I can run it at all. It's worse than that IMO. Rust makes it very awkward/impractical to have cyclic data structures, which are necessary to write a lot of useful programs. The Rust fans will quickly jump in and tell you that if you need cycles, your program is wrong and you're just not a good enough programmer, but Maybe it's just that the Rust borrow checker is too li…

Cyclic data structures are implemented easily with unsafe. Like non-cyclical ones (Vec for example). The difficult part is to make a safe API to that. This difficulties are not of syntactic nature but design difficulties. You need to think through your use cases for such a struct and to devise an API that supports them.

This is more difficult than C++ way "just do it". With C++ you will solve the same problems but on a case by case basis as they come into view. With Rust you need to solve these problems upfront or do a lot of refactoring later. There are upsides and downsides in both approaches, but it is clear that Rust is not good to sketch some code quickly to see how it will do.

It is still possible to do it quickly with Rust in a C++ way by leaking usafety everywhere and passing raw pointers, but I think it is still easier to do it with C++ which was designed for this style of coding.

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#117

Earlier quoted context omitted.

The basic model of Rust is to move use-after-free from a dynamic, runtime check to a static, compile-time check. But to keep the static checks from being Turing-complete, you need to prohibit arbitrary cycles while something like a tree (or other boundable recursion) is doable. So Rust not being able to check cyclic data structures isn't a "Rust currently can't do better" situation, it's a "Rust just can't do better"…

missing data structure helper -- didn't you already just name-check that though, since that's basically RefCell .. or if you're willing to roll the dice... UnsafeCell (aka "trust me I know what I'm doing")?

What you essentially want for the user to not write any unsafe code is this kind of interface:

   trait Allocator {
     fn allocate(&'a self, init: T) -> Handle;
     fn deallocate(&'a self, handle: Handle);
     fn read(&self, handle: Handle) -> impl Deref;
     fn write(&self, handle: Handle) -> impl DerefMut;
   }
&'a RefCell is pretty close to a definition of Handle, except that Rust provides no implementations of allocate and deallocate that take a const instead of a mut reference for self. Trying to make an allocator that lets you safely deallocate something requires a completely different implementation of Handle than what RefCell can provide, and even if you're fine without deallocation, allocation with a const ref still requires unsafe to get the lifetime parameter right.

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#118
post #13

Earlier quoted context omitted.

> It will never be perfect, but every little improvement helps. Or it might convince people to stay longer on a plane with a provably [0] terrible safety record. [0] https://alexgaynor.net/2020/may/27/science-on-memory-unsafet...

To put matters into perspective, Rust reference implementations depend on C++ toolchains. Same applies to all major Ada, Java, .NET, Swift, Ocaml and Haskell implementations. And any GPGPU toolchain. Which kind of shows it isn't going anywhere and those planes have to be improved no matter what.

As an addendum, the same goes for many C toolchains. Anything requiring GCC 4.8 or later is depending on a C++ compiler. And projects like LLVM’s libc, Fuchsia’s Zircon kernel, the bareflank hypervisor, etc, demonstrate that C++ really can be used anywhere C is used.

C++ is the new C in the sense that it’s the language everything else is built on and I expect it will be even more difficult to displace than C. For instance, the complexity of C++ makes it next to impossible to incrementally rewrite in another language, simply writing a production quality C++ implementation is a gargantuan investment so a superset language is questionable, and the C++ community is committed to evolving and improving their language whereas C has largely ossified. Perhaps C will outlive everyone reading this thread, but C++ will outlive C.

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#119
post #91

Earlier quoted context omitted.

> Slotmap uses unsafe everywhere, it's a memory usage pattern not supported by the borrow checker. Is disabling the borrow checker really a common pattern in Rust? Wrapping "unsafe" code in a safe interface is a common pattern in Rust, yes. There is absolutely nothing wrong with using "unsafe" so long as you are diligent about checking invariants, and keep it contained as much as possible. Obviously the standard libr…

If unsafe means “safe but the compiler cannot verify” then I guess just consider .cpp to mean “safe but the compiler cannot verify” and we have suddenly made C++ memory safe

Sure, and if a typical Rust program that I write has no unsafe in it directly, and 5% of its dependencies' code have unsafe in them, that's also the same as writing a program in the "not c++" language directly, and using "not c++" dependencies for all but 5% of the dependency code.

Seems like a silly analogy to me, though.

Re: Making C++ safe without borrow checking, reference counting, or tracing GC

#120
post #94

Earlier quoted context omitted.

> rust demands that I cross every last t before I can run it at all. It's worse than that IMO. Rust makes it very awkward/impractical to have cyclic data structures, which are necessary to write a lot of useful programs. The Rust fans will quickly jump in and tell you that if you need cycles, your program is wrong and you're just not a good enough programmer, but Maybe it's just that the Rust borrow checker is too li…

> currently can't do better The limitations are an inherent consequence of basic tenets of Rust's design. Rust wouldn't be Rust anymore if you fixed them. > Some of the restrictions of the Rust borrow checker and type system are arbitrary. They're there because Rust currently can't do better. They're not the gospel, they aren't necessarily inherent property that must always be satisfied for a program to be bug free.…

> It's not meant to foster creativity, it's meant to be safe for big business and novice employees.

Interestingly, my experience is the opposite.

I find that the "straightjacket" is extremely precious during refactorings – in particular, the type of refactorings that I perform constantly when I'm prototyping.

Compared to this, I'm currently writing Python code, and every time I attempt a refactoring, I waste considerable amounts of time before I can test the interesting new codepath, because I end up breaking hundreds of other codepaths that get in the way and I need to go through the testsuite (and pray that it contains a sufficient number of tests) hundreds of time until the code is kinda stable.

Which is not to say that Rust matches every scenario. We agree that it doesn't, by design. But I don't think that the scenarios you sketch out are the best representation of what Rust can/should be used for and can't/shouldn't be used for.

Post reply on HN