
C++ Lifetime profile v1.0 posted - nikbackm
https://herbsutter.com/2018/09/20/lifetime-profile-v1-0-posted/
======
Spartan-S63
I don’t know the real narrative driving so much investment in safe C++, but it
does appear that Rust is really driving C++ in this respect.

~~~
steveklabnik
This proposal and Rust's guarantees are different. For example:

> We do not attempt to address all aliasing cases or make concurrency safety
> guarantees. Programmers are still responsible for eliminating race
> conditions.

Some stuff is the same:

> The analysis is local each to function (no whole program analysis),

Some stuff is in the middle:

> We do not currently attempt to check of the internals of Owner types,
> including that we do not attempt to validate the correctness of pointer-
> based data structures.

(Often, pointer-based data structures in Rust are implemented with unsafe,
which isn't checked. Rust's lifetimes also don't interact with owners, in a
sense. But Rust's ownership system does, which is together as one thing in
this paper. But all of this is possibly splitting hairs.)

This is based on a CFG; Rust's historically has been based on lexical scope,
but is moving to a CFG soon (NLL is slated for 1.31.)

Regardless, I'm glad to see anything that makes C++ safer, regardless of
motivations. It's all about making software better, not cheering on the home
team and booing the away team.

~~~
duneroadrunner
> This proposal and Rust's guarantees are different.

A little different, but only in the sense of being, for the moment, less
ambitious (than Rust) about the completeness of the checker's implementation.
But I think it substantially demonstrates a straightforward path to achieving
the same level of memory safety in C++ as Rust.

> For example: > > We do not attempt to address all aliasing cases

It seems to me that a main principle behind the design of the Rust language
was to eliminate the aliasing issue, by imposing the "mutable references are
exclusive" restriction, as a prerequisite to addressing the memory safety
issue. And there was an implication (and sometimes more than that) that as
long as C++ can't address the aliasing issue it can't address the memory
safety issue (like Rust can).

I think the key thing that the lifetime checker demonstrates is that this
premise is not (quite) right. You don't need to completely eliminate the
aliasing issue to achieve (efficient) memory safety, you just need to address
it "enough". And that can be done for C++.

Now, whether the complete elimination of the aliasing issue is, apart from the
memory safety implications, a virtue that's worth the (flexibility) cost of
the "mutable references are exclusive" restriction, is as far as I'm aware,
still a matter of opinion. (Given the existence of the _RefCell_ wrapper in
Rust, and the ease (if not elegance) of implementing an "anti-RefCell" wrapper
in C++, I'm guessing it's mostly a wash.)

> > or make concurrency safety guarantees. Programmers are still responsible
> for eliminating race conditions.

Data races are a separate issue, but relative to the (single-threaded) memory
safety issue, I think there'd be less controversy that it can be addressed in
C++ in vaguely similar fashion to Rust. [1]

But both in the case of Rust and the C++ lifetime checker, I don't think that
the cost of the imposed (compile-time) restrictions in terms of
code/algorithmic flexibility is being adequately acknowledged. For example,
say you have a list (or whatever container) of references to (pre-)existing
objects. In both cases, the restrictions require that all the objects must (be
known to) outlive the list container even if there is a reference to them in
the list for only a short time [2]. Imo this is an impractical restriction. In
C++, you could use run-time checked pointers[3][4] to alleviate the
restriction without sacrificing memory safety [5]. It's not immediately
obvious to me that Rust couldn't have such a run-time checked reference as
well. But others would be more qualified to make that assessment.

[1] shameless plug:
[https://github.com/duneroadrunner/SaferCPlusPlus#multithread...](https://github.com/duneroadrunner/SaferCPlusPlus#multithreading)

[2]
[https://github.com/duneroadrunner/misc/blob/master/201/8/Jul...](https://github.com/duneroadrunner/misc/blob/master/201/8/Jul/implications%20of%20the%20lifetime%20checker%20restrictions.md#snippet-4)

[3]
[https://github.com/duneroadrunner/SaferCPlusPlus#registered-...](https://github.com/duneroadrunner/SaferCPlusPlus#registered-
pointers)

[4] [https://github.com/duneroadrunner/SaferCPlusPlus#norad-
point...](https://github.com/duneroadrunner/SaferCPlusPlus#norad-pointers)

[5]
[https://github.com/duneroadrunner/misc/blob/master/201/8/Jul...](https://github.com/duneroadrunner/misc/blob/master/201/8/Jul/implications1.cpp#L92)

~~~
steveklabnik
> I think it substantially demonstrates a straightforward path to achieving
> the same level of memory safety in C++ as Rust.

What path is that? It's not clear to me how this is possible without breaking
backwards compatibility. This statement is directly at odds with

> You don't need to completely eliminate the aliasing issue to achieve
> (efficient) memory safety, you just need to address it "enough"

Safe Rust isn't "enough" safe, it is 100% (proofs pending, of course) safe. A
"safe enough" system is not equivalent.

 _Maybe_ if you mean that the existence of Unsafe Rust means that it's
"enough", well fair. I still think it's substantially different; this proposal
doesn't even get to being 100% safe.

That said, as I said above, I very much welcome _any_ sort of incremental
improvement here.

~~~
duneroadrunner
> It's not clear to me how this is possible without breaking backwards
> compatibility.

What do you mean? Presumably most existing sizable codebases will not satisfy
the requirements of the (eventual completed) lifetime checker, even if the
code is actually safe.

> > You don't need to completely eliminate the aliasing issue to achieve
> (efficient) memory safety, you just need to address it "enough"

> Safe Rust isn't "enough" safe, it is 100% (proofs pending, of course) safe.
> A "safe enough" system is not equivalent.

I'm asserting (perhaps mistakenly) that you don't need to address the aliasing
issue as completely as (Safe) Rust does in order to achieve the same memory
safety that (Safe) Rust does. Or maybe "completely" is not exactly the right
word. I'm saying that the universal imposition of the "mutable references are
exclusive" restriction is not a necessary prerequisite to (fully) achieving
the same type of ("zero overhead") memory safety that (Safe) Rust does.

I'm not that familiar with Rust, but for example, if you have a reference to
an element in a dynamic container, like a vector, (Safe) Rust ensures that
that element is not prematurely deallocated by ensuring that there is no
simultaneously existing mutable reference to the container. I.e. the container
is immutable while an (immutable) reference to one of its elements exists.
Right? (I mean, assuming you didn't "split" it first.) And if the reference to
the element is a mutable reference, then the container cannot be referenced at
all, right?

From a memory safety perspective, this is overkill. The container does not
have to be immutable (or inaccessible) while a reference to an element exists,
only its _structure_ needs to be immutable. The C++ lifetime checker imposes
this lesser restriction.

And for simple objects that do not have dynamic structure (or any indirect
references), then the "mutable references are exclusive" restriction provides
no memory safety benefit at all, right?

As I said, even this lesser restriction will presumably break most existing
C++ codebases, and you could (I think legitimately) argue that that makes this
new "Safe" C++ a substantially different language than traditional C++. But
the fact that the restrictions are much less severe than Rust's means that a
lot fewer code modifications will be required than if a Rust-style universal
"mutable references are exclusive" restriction had been adopted.

Am I making sense here? Maybe I'm mistaken and these "lesser" restrictions are
somehow inadequate, but I don't see it.

~~~
steveklabnik
> I'm asserting (perhaps mistakenly) that you don't need to address the
> aliasing issue as completely as (Safe) Rust does in order to achieve the
> same memory safety that (Safe) Rust does.

You need to address the aliasing, or address the mutability. They're two sides
of the same coin.

> only its structure

I am not 100% sure what distinction you're making here, sorry. What's "the
container" vs "its structure"?

> From a memory safety perspective, this is overkill.

Yes, in general, Rust takes a soundness-based approach. If you can't prove
that it's safe, then it's not safe. This takes the other path, which is
totally valid, mind you! But that means it will allow cases that are not safe.

> And for simple objects that do not have dynamic structure (or any indirect
> references), then the "mutable references are exclusive" restriction
> provides no memory safety benefit at all, right?

That's not right. You can have a data race to a plain old integer.

~~~
duneroadrunner
> That's not right. You can have a data race to a plain old integer.

Sure, if your language allows unprotected access to any object from any
thread. Which, I guess traditional C++ essentially does, but presumably a
"Safe" C++ would eventually have an "asynchronous sharing" checker that would
require any shared objects to be appropriately "protected".

> > only its structure

> I am not 100% sure what distinction you're making here, sorry. What's "the
> container" vs "its structure"?

For example:

    
    
        std::vector<int> vec1 {1, 2};
        {
            const auto& cref1 = vec1.at(0);
            auto& ref2 = vec1.at(1);
            ref2 = 3;
            auto& ref1 = vec1.at(0);
            std::cout << cref1;
            ref1 = 4;
            
            // co-existing const and non-const references are permitted and memory safe here
            
            std::cout << vec1.size();
                    
            vec1.at(0) = 5;
    
            vec1.clear(); // <---- Rejected by the lifetime checker
            
            // because the clear() call mutates the structure.
            
            // Mutating the data contained in the vector is permitted though.
            
            std::cout << cref1;
        }
    

> Yes, in general, Rust takes a soundness-based approach. If you can't prove
> that it's safe, then it's not safe.

The approach is not that different. The lifetime checker applies (or will
apply) basically the same sorts of restrictions that the Rust compiler does
(and "break" backward compatibility in the process), but only when necessary
to enforce memory safety.

I mean, the way the lifetime checker works is that it basically keeps track,
at compile-time, of the latest possible death-time of every reference and the
earliest possible death-time of the target object (or potential target
objects) that each reference points at, and complains anytime the former is
later than the latter.

~~~
Jweb_Guru
I think you will be disappointed if you expect an approach that doesn't do
roughly what Rust does aliasing-wise, and doesn't do something very
conservative on >1 word sized updates, to be memory safe in the presence of
concurrency. People have been working on that problem for a _really_ long time
and I frankly don't see _any_ approach that is going to work in a C++
environment other than Rust's. For the single threaded case, sure, you can
probably get close with something much more relaxed. But the Rust core team is
not stupid, they didn't insist on such stringent aliasing rules just so you
could use restrict.

------
kvark
> It aims to detect common local cases of dangling
> pointers/iterators/string_views/spans/etc

Hmm, a good tool to have, but wouldn't be quite enough to sleep well, since
it's only for "common cases".

------
usefulcat
"I love C++. I also love safe code and not having to worry about dangling
pointers and iterators and views. So I’ve been doing some work to make my life
less conflicted"

I love the honesty there :)

------
IloveHN84
Good guy Herb!

