Live data from Hacker News

Why Safety Profiles Failed

circle-lang.org

221–230 of 233 posts

Re: Why Safety Profiles Failed

#221
post #80

Earlier quoted context omitted.

Features C++ has that Rust doesn't: * template specialisations * function overloading * I believe const generics is still not there in Rust, or its necessarily more restricted. In general metaprogramming facilities are more expressive in C++, with different other tradeoffs to Rust. But the tradeoffs don't include memory safety.

You definitely can't have all of "Non-type template parameters" (the C++ equivalent of const generics) in Rust because some of it is unsound. You can certainly have more than you get today, it's much less frantically demanded but I should like to be able to have an enum of Hats and then make Goose Goose Goose and so on, which is sound but cannot exist today. For function overloading this serves two purposes in C++ an…

> You definitely can't have all of "Non-type template parameters" (the C++ equivalent of const generics) in Rust because some of it is unsound.

Just to clarify, does this mean NTTP in C++ is unsound as-is, or that trying to port C++ NTTP as-is to Rust would result in something unsound?

Re: Why Safety Profiles Failed

#222

Earlier quoted context omitted.

You definitely can't have all of "Non-type template parameters" (the C++ equivalent of const generics) in Rust because some of it is unsound. You can certainly have more than you get today, it's much less frantically demanded but I should like to be able to have an enum of Hats and then make Goose Goose Goose and so on, which is sound but cannot exist today. For function overloading this serves two purposes in C++ an…

> You definitely can't have all of "Non-type template parameters" (the C++ equivalent of const generics) in Rust because some of it is unsound. Just to clarify, does this mean NTTP in C++ is unsound as-is, or that trying to port C++ NTTP as-is to Rust would result in something unsound?

It is possible to write unsound code using NTTP in C++ unsurprisingly. In C++ that's just your fault as the programmer, don't make mistakes. So the compiler needn't check. I think NTTP abuse that's actually unsound is rare in production, but the problem is that's my insight as a human looking at the code, I'm not a compiler.

The Rust equivalent would need to be checked by the compiler and I think this only really delivers value if it's a feature in the safe Rust subset. So, the compiler must check what you wrote is sound, if it can't tell it must reject what you wrote. And that's why they decided to do the integer types first, that's definitely sound and it's a lot of value delivered.

As a whole concept you could probably say that the C++ NTTP is "unsound as-is" but that's so all-encompassing as to not be very useful, like saying C++ integer arithmetic is unsound. It's such a big thing that even though the problem is also big, it sort of drowns out the problem.

Noticing that std::abs is unsound has more impact because hey, that's a tiny function, why isn't it just properly defined for all inputs? But for the entire NTTP feature or arithmetic or ranges or something it's not a useful way to think about it IMO.

Re: Why Safety Profiles Failed

#223
post #210

Earlier quoted context omitted.

If it requires the programmer to bear the responsibility for proper usage (eg. must use checked_add not rely on panic), how's that different than the issues with undefined behavior? I'm also concerned with the differing functional behavior between debug and release, and the mistaken impression it could create (eg. code handles overflow in debug fine due to panic but blows up on release as the proper solution is not u…

> If it requires the programmer to bear the responsibility for proper usage (eg. must use checked_add not rely on panic), how's that different than the issues with undefined behavior? It comes down to the blast radius for a mistake. A mistake involving UB can potentially result in completely arbitrary behavior. A mistake in the safe subset of a language is still a mistake, but the universe of possible consequences is…

Thanks for the input and links. I'll need to test out the costs of the mitigations.

BTW, I found one of the rust rfc documents helpful for understanding the borrow checker. Do you know if there is a similar rust RFC document for the upcoming polonius borrowchecker, even if it's just a working copy? I'm having trouble finding anything beyond some blog posts.

Re: Why Safety Profiles Failed

#224

These considerations all seem so self-evident that I can't imagine the architects of Safety Profiles weren't aware of them; they are basically just the statement of the problem. And yet these smart people presumably thought they had some kind of solution to them. Why did they think that? What did this solution look like? I would be very interested to read more context on this.

I wonder if the unstated issue here is: C++ is so complex that it's hard to think through all the implications of design proposals like this. So practically speaking, the only way to prove a design change is to implement it and get lots of people to take it for a test drive. But it's hard to find enough people willing to do that in earnest, so the only real way to test the idea is to make it part of the language stan…

That is how we end up with stuff like GC added in C++11, and removed in C++23, because it was worthless for the only two C++ dialects that actually use a GC, namely Unreal C++ and C++/CLI.

So no one made use of it.

Re: Why Safety Profiles Failed

#225
post #133

Additionally I still cannot understand why they didn't make iterators safe from the very beginning. In the alias examples some iterators must alias and some not. With safe iterators the checks would be trivial, as just the base pointers need to be compared. This could be done even at compile-time, when all iterators bases are known at compile-time. Their argument then was that iterators are just simple pointers, not…

The C++ committee and standard library folks are in a hard spot. They have two goals: 1. Make primitives in the language as safe as they can. 2. Be as fast as corresponding completely unsafe C code. These goals are obviously in opposition. Sometimes, if you're lucky, you can improve safety completely at compile time and after the safety is proven, the compiler eliminates everything with no overhead. But often you can…

Ironically, in the early days of C, it was a good as Modula-2 or Pascal dialects "squeeze more out of a CPU than anyone else could".

All that squeezing was made possible by tons of inline Assembly extensions, that Modula-2 and Pascal dialects also had.

It only took off squeezing, when C compiler writers decided to turn to 11 the way UB gets exploited in the optimizer, with the consequences that we have to suffer 20 years later.

Re: Why Safety Profiles Failed

#226

Earlier quoted context omitted.

Making programmers manually annoate every single function is infinitely more costly.

Are you also a proponent of nonlocal type inference? Do you think annotating types is too costly for programmers?

I am a proponent of the auto return type for simple wrapper functions like this, yes.

Re: Why Safety Profiles Failed

#227

Earlier quoted context omitted.

Something I would like to know is how much lifetime annotation you can infer (recursively) from the function implementation itself. Compiler driven, IDE integrated, automatic annotation would be a good tool to have. Some amount of non-local inference might also be possible for templated C++ code that already lack a proper separate compilation story.

At the limit, the answer is “between zero and completely.” Zero because you may only have access to the prototype and not the body, say if the body is in another translation unit, or completely if a full solution could be found, which is certainly possible for trivial cases. The reason to not do this isn’t due to impossibility, but for other factors: it’s computationally expensive, I’d you think compile times are alr…

Translation units have long not been a boundary stopping static analyzers or even compiler optimizations with LTO. It's a semantic/namespace concept only.

Re: Why Safety Profiles Failed

#228

The assumption here seems to be that the compiler/analyzer is only able to look at one function at a time. This makes no sense. Safety is a whole-program concern and you should analyze the whole program to check it. If anything as simple as the following needs lifetime annotations then your proposed solution will not be used by anyone: const int& f4(std::map & map, const int& key) { return map[key]; }

Whole-program analysis is not tractable (i.e., not scalable), and Rust has already proven that function signatures are enough, and does actually scale. The analysis can be performed locally at each call site, and doesn't have to recurse into callees. Your function would look like this in Rust: fn f4 (map: &'a Map , key: &i32) -> &'a i32 { ... } You don't need much more than a superficial understanding of Rust's lifet…

> Whole-program analysis is not tractable (i.e., not scalable)

... in the general case. There are many function (sub-)graphs where this is not only tractable but actually trivial. Leave annotations for the tricky cases where the compiler needs help.

> fn f4(map: &'a Map, key: &i32) -> &'a i32 { ... }

The problem is not that you can't understand what this means but that it adds too much noise which is both needless busywork when writing the function and distracting when reading the code. There is a reason why many recent C++ additions have been reducing the amount of boilerplate you need to write.

There have been plenty attempts at safty annotations. There is a reason why they have not been adopted by most projects.

Re: Why Safety Profiles Failed

#229

Earlier quoted context omitted.

At the limit, the answer is “between zero and completely.” Zero because you may only have access to the prototype and not the body, say if the body is in another translation unit, or completely if a full solution could be found, which is certainly possible for trivial cases. The reason to not do this isn’t due to impossibility, but for other factors: it’s computationally expensive, I’d you think compile times are alr…

Translation units have long not been a boundary stopping static analyzers or even compiler optimizations with LTO. It's a semantic/namespace concept only.

Sure, you can do some things sometimes. It still means that you need access to everything, which isn't always feasible. And as I said, it's just not the only reason.

Re: Why Safety Profiles Failed

#230

Earlier quoted context omitted.

Whole-program analysis is not tractable (i.e., not scalable), and Rust has already proven that function signatures are enough, and does actually scale. The analysis can be performed locally at each call site, and doesn't have to recurse into callees. Your function would look like this in Rust: fn f4 (map: &'a Map , key: &i32) -> &'a i32 { ... } You don't need much more than a superficial understanding of Rust's lifet…

> Whole-program analysis is not tractable (i.e., not scalable) ... in the general case . There are many function (sub-)graphs where this is not only tractable but actually trivial. Leave annotations for the tricky cases where the compiler needs help. > fn f4 (map: &'a Map , key: &i32) -> &'a i32 { ... } The problem is not that you can't understand what this means but that it adds too much noise which is both needless…

I think you might be surprised how rare explicit lifetime annotations actually are in Rust code.

But, to the point: It precisely isn’t “noise”. It’s important information that any competent C++ developer will immediately look for in documentation, or worse, by analyzing the code. It’s not distracting - it’s essential.

Aside: I’m very confused by your assertion that C++ has been reducing boilerplate. It feels like every couple of years there’s another decoration that you need to care about. The only reduction I can think of is defaulted/deleted constructors and assignment ops? But that feels self-inflicted, from a language design perspective.

Post reply on HN