Live data from Hacker News

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

verdagon.dev

101–110 of 226 posts

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

#101

> Tracing GC is the simplest model for the user, and helps with time management and development velocity, two very important aspects of software engineering. > Borrow checking is very fast, and helps avoid data races. One thing many people seem to assume is that not having to care about memory means you can program faster and get to your goal faster. As the author here seems to do. However as it turns out, if your pr…

Two things, full time Rust dev here: a) Rust's borrow checker is good and its type system good, but IMHO it's not really doing what you say it is as well as you're implying: "explaining in an explicit way who owns what" ; While ownership is explicit and static (apart from RefCell and friends), description of that ownership is scattered all over, program state flows are not modelled in the type system at all, and on t…

The limitations of the borrow checker when it comes to borrowing self are annoying. I've had cases where I just said "screw it" and copied the body of a function inline in the 1 or 2 places it was being called just to make the borrow checker happy.

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

#102
post #17

Use memory arenas and never think about any of this again.

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.

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

#103
post #98

Earlier quoted context omitted.

You can also say that the borrow checker works like a helpful editor, double checking your work, so that you can focus on the important details of performance issues, safety issues, and such, without needing to waste brain power on the low-level details.

This would be true if code using the borrow checker was easier to read than to write.

The point is that the compiler helps you “read” it. This takes mental effort off of you.

I agree that not everyone thinks this is true, but this is my experience. I do not relate to the compiler as a straight jacket. I relate to it as a helpful assistant.

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

#104
post #64
post #46

Earlier quoted context omitted.

Yea, different languages for different purposes. Rust is for finished products, not so much for experimentation. When you want to play or experiment you should use Lisp.

That makes it expensive to move from experimentation to "fairly usable", though.

Your Lisp program will be entirely usable once you have experimented and found the right way to do it. Lisp compilers are really good, and they support gradual typing: you can write your program with no explicit type information, and then speed it up by adding type information in the hot spots. You can deploy that to production and it will serve you well.

At some point your Lisp program will be mature, you will have implemented most of the features you know you will need, and you will know that any new features you add in the future will not alter the architecture. Once you understand the problem and have established the best architecture for the program, you can consider rewriting it in Rust. Lisp’s GC does have a run–time cost, and you can measure it to figure out how much money you will save by eliminating it. If you will save more money than the cost of the rewrite, then go for it. Otherwise you can go on to work on something more cost–effective.

Note that you might not need to rewrite the whole program; it might be more effective to rewrite the most performance–critical portion in Rust, and then call it from your existing Lisp program. This can give you the best of both worlds.

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

#105
post #62

Earlier quoted context omitted.

I really feel like it's a hell of a definitions dodge to say "This is what the model is" when no compiler implements constraints to require the user to treat the model like that (i.e. I can always just increment the pointer, or typecast it to numeric type, do math on it, and typecast back to a pointer, without having to pull any big red levers like using "unsafe" methods). If it's undefined but it compiles to somethi…

Yes it’s really undefined. There is a distinction from “implementation defined behavior” which you seem to be confusing it with. You are practically wrong in your assumptions. Since undefined behavior is undefined the compiler is free to do anything with compilation, it may compile to something but you have no guarantee what that something is. And in real life this often actually bites you when the optimizer comes in…

No; this is a common misconception I see from people who swallowed the "it's allowed to format your hard drive and blow up your monitor" dodge vs. the electrical engineers who know where terminology like 'undefined behavior' originated in engineering. In practice, it tends to do something subtle and usually right but probably wrong for the simple, practical reason that if it did anything as obviously wrong as "format your hard drive and blow up your monitor," someone would have tripped over it testing the compiler and changed the compiler.

This is why I actually hate using this programming language, because when you hit undefined behavior (which the language makes trivial to do; incrementing a pointer past the allocated memory is a one-line operation that throws no errors) the end-result is usually subtle, wrong, and hard to find later if it isn't actually "close enough to right" because the compiler desperately tries to make a useful program because that's what compilers are for. Hell, if it formatted my hard drive and blew up my monitor, it'd be much easier to figure out where the problem was! Hand-waving this flaw in the design of the programming tool with "oh, it's undefined behavior; you should never have relied on that in the first place" when so many valid statements in the language compile to undefined behavior, as if that is good enough, is building a house on sand.

... and quite frankly, our industry is full of sand houses and we could stand to respond to the amount of undefined behavior in C++ by ceasing to build on that shaky foundation.

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

#106
post #100
post #93

Earlier quoted context omitted.

Eh? This is a wild take. How do you draw the conclusion the default implementation is inadequate?

Because something like slotmap has to use `unsafe` to get around the inadequacies of the borrow checker...

A downside for sure, but one that, at least in this specific example, has limited downsides. If you can button it up into a safe abstraction that you can share with others, then I don't really see what the huge problem is. The fact that you might need to write `unsafe` inside of a well optimized data structure isn't a weakness of Rust, it's the entire point: you use it to encapsulate an unsafe core within a safe interface. The standard library is full of these things.

Now if you're trying to do something that you can't button up into a safe abstraction for others to use, then that's a different story.

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

#107
post #91
post #88

Earlier quoted context omitted.

> In fact the pattern described in the article is a common pattern in Rust and I make use of it all the time; the library for making use of it is `slotmap`. Slotmap uses unsafe everywhere, it's a memory usage pattern not supported by the borrow checker. It's basically hand-implementing use-after-free and double-free checks, which is what the borrow checker is supposed to do. Is that really a common pattern in Rust?

> 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

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

#108
The reason I use Rust is because I can bypass all this messy business altogether and have my sensible patterns wrapped in a usable syntax and enforced by the compiler out of the box.

Whenever people say "just follow these rules" I read "just add this extra mental burden and do not slip up". Computers were invented to automate things. Rust automates ownership and borrowing rules. Suggestions like "do not forget to initialize unique_ptr with something" are not intelligent solutions.

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

#110
post #98

Earlier quoted context omitted.

You can also say that the borrow checker works like a helpful editor, double checking your work, so that you can focus on the important details of performance issues, safety issues, and such, without needing to waste brain power on the low-level details.

This would be true if code using the borrow checker was easier to read than to write.

I think it’s generally accepted that writing code is nearly universally easier than reading code, in any language. That aside, getting a mechanical check on memory safety for the price of some extra language verbosity is obviously worth it IMO.

By the same token, it is common to see criticisms of the complexity of templates in C++, but templates are the cornerstone of “Modern C++” and many libraries could not exist without them.

Post reply on HN