Debugging a proc macro failure is miles and above easier than debugging template errors.
The article states "A C++ compiler can infer nothing about aliasing from a function declaration." Which is true, but assumes that the compiler only looks at the function declaration. In the examples given, an analyzer could look at the function bodies and propagate the aliasing requirements upward, attaching them to the function declaration in some internal data structure. Then the analyzer ensures that those functions are used correctly at every call site. Start at leaf functions and walk your way back up the program until you're done. If you run into a situation where there is an ambiguity, you throw an error and let the developer know. Do the same for lifetimes. Heck, we just got 'auto' type inference working in C++11, shouldn't we be able to do this too?
I like not having to see and think about lifetimes and aliasing problems most of the time, and it would be nice if the compiler (or borrow checker) just kept track of those without requiring me to explicitly annotate them everywhere.
From P1179: "This paper ... shows how to efficiently diagnose many common cases of dangling (use-after-free) in C++ code, using only local analysis to report them as deterministic readable errors at compile time."
Local analysis only. It's not looking in function definitions.
Whole program analysis is extremely complicated and costly to compute. It's not comparable to return type deduction or something like that.
This assumes no recursive functions, no virtual functions/function pointers, no external functions etc etc
> Heck, we just got 'auto' type inference working in C++11, shouldn't we be able to do this too?
Aliasing is much trickier than type inference.
For example aliasing can change over time (i.e. some variables may alias at some point but not at a later point, while types are always the same) and you want any analysis to reflect it because you will likely rely on that.
Granularity is also much more important: does a pointer alias with every element of a vector or only one? The former is surely easier to represent, but it may unnecessary propagate and result in errors.
So effectively you have an infinite domain of places that can alias, while type inference is limited to locals, parameters, functions, etc etc. And even then, aliasing is quadratic, because you want to know which pairs of places alias.
I hope you can see how this can quickly get impractical, both due to the complexity of the analysis and the fact that small imprecisions can result in very big false positives.
Even if a sufficiently advanced proof assistant could internally maintain and propagate constraints up through functions (eg. 'vec must not alias x'), your point about small imprecisions cascading into large false positives is well made.
Bottom up constraints become increasingly difficult to untangle the further away they get from their inception, whereas top down rules such as "no mutable aliasing" are much easier to reason about locally.
The above is forced by Rust, which would be nice, but the conventions are easy enough if you try at all. But most developers refuse to write anything more than C++98.
I think the bigger mistake is equating memory safety with C++11 smart pointers. They buy you a little, but not the whole buffet. There are a lot of C++ developers that think memory safety is a skill issue and if you just use "best practices with C++11 or higher" then you get it - when evidence proves to the contrary.
Which is why safety profiles are so interesting, they are something I should be able to turn on/off on a file by file basis and thus easily force the issue.
Of course profiles don't exist yet (and what is proposed is very different from what this article is arguing against) and so it remains to be seen if they will be adopted and if so how useful they will be.
I swear I'm not trying to be snarky or rude here, but is it actually a "convention" if almost nobody follows it? This seems like one example of my general issue with C++, in that it could be great if everyone agreed to a restricted subset, but of course nobody can coordinate such agreement and it doesn't exist outside companies large and important enough to enforce their own in-house C++ standards (e.g. Google).
And don't get me started on C itself. Jesus Christ.
They are the only C++ compiler that properly supports all C++20 modules use cases, while clang still doesn't do Parallel STL from C++17, for example.
They support C17 nowadays, where many embedded folks are slowly adopting C99.
And the UNIX story outside clang and GCC is quite lame, most still stuck in C++14, catching up to C++17.
Likewise, consoles, C++17.
Also, if we do move the discussion outside GCC and Clang, then I don't know what to say man. Why not use GCC or Clang? Are there many UNIX out there not having either? Seems unlikely.
Every once in a while someone who writes a lot of Rust will blog about some code they discovered that was 'unsafe' and after looking close they realized it wasn't doing something that fundamentally required unsafe (and often fixing the code to be safe fixed real bugs). C++ and Rust have to leave people enough rope to hang themselves in order to solve the problems they want to solve, but that means people will find a way to do stupid things.
That’s not a human problem. It’s like saying “this motorway is pitch black, frequently wet and slippery and has no safety barriers between sides, so crashes are frequent and fatal. What we have is a human problem - drivers should follow the convention of driving at 10mph, when it doesn’t rain and make sure they are on the right side of the road at all times”.
In other words: you can try limiting all cars to 10mph, closing the road, automatically switching out all car tyres with skid-proof versions while in motion, or anything else.
But… just turn the god damn lights on and put up a barrier between lanes. It works on every other road.
https://devblogs.microsoft.com/oldnewthing/20241023-00/?p=11...
Notice the use of C's memcpy() function.
This is exactly the kind of posts where showing best practises would be quite helpful, as education.
[0]: https://doc.rust-lang.org/reference/lifetime-elision.html
https://learn.microsoft.com/en-us/cpp/code-quality/understan...
Not annotating is not making anything easier.
pub struct Step<'a, 'b> {
pub name: &'a str,
pub stage: &'b str,
pub is_last: bool,
}
struct Request<'a, 'b, 'c, 'd, 'e> {
step: &'a Step<'d, 'e>,
destination: &'c mut [u8],
size: &'b Cell<Option<usize>>,
}
To be sure, they were seeking advice on how to simplify it, but I imagine those with a more worse-is-better technical sensibility arguing that a language simply should not allow code like that to ever be written.I also hear that higher-ranked trait bounds can get scary even within a single function signature, but I haven't had cause to actually work with them.
I think you two are ultimately talking about slightly different things, your parent is trying to point out that, even if this signature is complex, it can’t get more complex than this: one lifetime per reference means the complexity has an upper bound.
Likewise, pushing half solutions like profiles that are still pretty much a paper idea, other than what already exists in static analysers, might decrease C++'s relevance in some domains, and eventually those pushing for them might find themselves in the position that adopting Safe C++ (circle's design) would have been a much better decision.
The problem with ISO driven languages, is who's around in the room when voting takes place.
This is a not a jab against static analyzers, by all means use them, but I don't think they are a good fit as part of the language.
Further, the clang tidy and VC++ analysis based on some of the previous work, e.g. lifetime analysis paper from 2015, barely work, full of false positives.
I was looking forward to it in VC++, and to this day in VC++ latest, it still leaves too much on the table.