Date: Thu, 22 Apr 2021 23:33:51 +0300
On Thu, 22 Apr 2021 at 23:02, Thomas Neumann via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> Hi,
>
> I would like to improve memory safety in C++, but before writing a real
> proposal (and implementing a prototype) I would like to get some
> feedback whether you think this is a useful direction.
>
> Basically I would like to integrate a kind of borrow checker into C++
> for data types that opt-in to that mechanism. For these types the
> compiler enforces the invariant that you can have either one mutable
> reference or arbitrary many immutable references to an object.
>
> A straw man example could look like this:
>
> class MyIntVector [[borrowchecker]] {
> ...
> void push_back(int element);
> [[borrows]] const_iterator begin() const;
> };
>
> void foo(MyIntVector&);
>
> void bar() {
> MyIntVector v;
> v.push_back(1);
> v.push_back(2);
> for (int i:v) {
> cerr << i;
> v.push_back(3); // error: the const_iterator borrowed the object
> for (int j:v) {
> // this is ok, we can have arbitrary many readers
> }
> }
> v.push_back(2); // ok now, the lifetime of the iterators has ended
>
> foo(v); // error, implicit additional reference
> foo(borrow(v)); // ok, we explicit borrow an exclusive reference
> }
>
>
> This is not fleshed out, of course, there are many issues and corner
> cases to consider, in particular in interaction with existing code. My
> question is: Would you consider this a useful direction to explore? And
> if yes, would someone be interested in discussing potential design
> choices (e.g., whether or not the [[borrows]] annotation of begin is
> required, etc.)? These all have trade-offs and I would be happy to get
> some feedback.
The idea generalizes:
1) ..while the result of an operation with particular characteristic
$foo on an object X is within its lifetime..
2) ..operations with another particular characteristic $bar on X are ill-formed
or the opposite,
1) ..unless an operation with characteristic $foo has been performed
on an object X and the result is within its lifetime..
2) ..operations with a particular characteristic $bar on X are ill-formed
An example of this would be "unless you hold a lock on X, you can't
invoke its non-thread-safe modifiers
(thread-safe modifiers are fine)".
C++ doesn't have a whole lot of this. A piece of code is either
ill-formed or well-formed, and such half-dynamic
context doesn't play into it. I can imagine various useful things that
could be done with such a facility, as in that
example I mentioned. With a facility like this, we could possibly even
express "you didn't call .join() or .detach()
on a thread before it will be destroyed, this is an error, fix your
code". And who knows what else.
I guess in some ways this brings class invariants/states into the
realm of well-formedness. Operations that would be
otherwise fine are ill-formed if they're known to violate the
invariants of a state that we statically know to have been
entered/established by another operation. Or, of course, from another
perspective, they're well-formed only if
they are known not to perform such violations.
This is screaming for a semantic facility, and attributes aren't it.
Other than that, I don't have all that great suggestions
how to try and fit that general idea into C++. I would wager a guess
that it would be more attractive if it can do more
than just the borrowing you illustrate. On the other hand, if it tries
to do a whole lot more, it may become too complex
to digest, for some. I think the general(ized) notion is worth talking
about when trying to propose something in this space.
<std-proposals_at_[hidden]> wrote:
>
> Hi,
>
> I would like to improve memory safety in C++, but before writing a real
> proposal (and implementing a prototype) I would like to get some
> feedback whether you think this is a useful direction.
>
> Basically I would like to integrate a kind of borrow checker into C++
> for data types that opt-in to that mechanism. For these types the
> compiler enforces the invariant that you can have either one mutable
> reference or arbitrary many immutable references to an object.
>
> A straw man example could look like this:
>
> class MyIntVector [[borrowchecker]] {
> ...
> void push_back(int element);
> [[borrows]] const_iterator begin() const;
> };
>
> void foo(MyIntVector&);
>
> void bar() {
> MyIntVector v;
> v.push_back(1);
> v.push_back(2);
> for (int i:v) {
> cerr << i;
> v.push_back(3); // error: the const_iterator borrowed the object
> for (int j:v) {
> // this is ok, we can have arbitrary many readers
> }
> }
> v.push_back(2); // ok now, the lifetime of the iterators has ended
>
> foo(v); // error, implicit additional reference
> foo(borrow(v)); // ok, we explicit borrow an exclusive reference
> }
>
>
> This is not fleshed out, of course, there are many issues and corner
> cases to consider, in particular in interaction with existing code. My
> question is: Would you consider this a useful direction to explore? And
> if yes, would someone be interested in discussing potential design
> choices (e.g., whether or not the [[borrows]] annotation of begin is
> required, etc.)? These all have trade-offs and I would be happy to get
> some feedback.
The idea generalizes:
1) ..while the result of an operation with particular characteristic
$foo on an object X is within its lifetime..
2) ..operations with another particular characteristic $bar on X are ill-formed
or the opposite,
1) ..unless an operation with characteristic $foo has been performed
on an object X and the result is within its lifetime..
2) ..operations with a particular characteristic $bar on X are ill-formed
An example of this would be "unless you hold a lock on X, you can't
invoke its non-thread-safe modifiers
(thread-safe modifiers are fine)".
C++ doesn't have a whole lot of this. A piece of code is either
ill-formed or well-formed, and such half-dynamic
context doesn't play into it. I can imagine various useful things that
could be done with such a facility, as in that
example I mentioned. With a facility like this, we could possibly even
express "you didn't call .join() or .detach()
on a thread before it will be destroyed, this is an error, fix your
code". And who knows what else.
I guess in some ways this brings class invariants/states into the
realm of well-formedness. Operations that would be
otherwise fine are ill-formed if they're known to violate the
invariants of a state that we statically know to have been
entered/established by another operation. Or, of course, from another
perspective, they're well-formed only if
they are known not to perform such violations.
This is screaming for a semantic facility, and attributes aren't it.
Other than that, I don't have all that great suggestions
how to try and fit that general idea into C++. I would wager a guess
that it would be more attractive if it can do more
than just the borrowing you illustrate. On the other hand, if it tries
to do a whole lot more, it may become too complex
to digest, for some. I think the general(ized) notion is worth talking
about when trying to propose something in this space.
Received on 2021-04-22 15:34:05