www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Tell us your DIP1000 woes

reply Mike Parker <aldacron gmail.com> writes:
DIP1000 keeps rearing its head in discussions touching on safety 
in our monthly meetings. So we recently held a planning session 
focused exclusively on DIP1000. Two things came out of it.

First, it's pretty clear that inference by default is necessary 
to make DIP1000 easier to use. It's not clear at the moment how 
this can be achieved, but it's something that we need to work out.

Second, we'd like to get a number of examples of problems people 
have had with using DIP1000 that haven't shown up in Bugzilla. 
Walter wants to be as certain as he can whether such issues are 
fixable or if the design is fundamentally flawed.

If you've had problems using DIP1000, please post here with a 
description of what you encountered and any details about it you 
think we may find helpful. Please provide example code where 
possible.

As an example of the sort of thing we're looking for, someone in 
the meeting brought up the problems encountered with trying to 
add DIP1000 support to vibe.d.

**Please do not reply to posts in this thread unless you have 
additional relevant information regarding the problem 
described.** We want to collect examples, and any side 
discussions will just be noise.

Thanks!
Aug 25
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
I'll include the items I said at the meeting just so we can hopefully 
minimize extras.

 From meeting:

- Reference counted types (not in language) get affected by scope, like 
any other pointer contained struct. Which is a problem since it has 
runtime lifetime, not a CT analyzed one.
- Only one output is described, (returned value or this pointer), this 
means ref and out parameters aren't outputs and therefore cannot be  safe.
- The abomination that is scope return + return scope (this is easier to 
resolve in terms of new UDA's, but does not solve communicability of 
behavior).

Other ones:

- It does not model owners and borrows, this isn't in its scope, it also 
means in practice it has to try and act as a guarantee both up the call 
stack and down it, which is not what such escape analysis is designed 
for. Which makes it a lot harder to use.
- Does not attempt at modelling globals including TLS, so with  safe 
code you can absolutely escape memory from the thread completely 
unintentionally.

As someone who has over 120k LOC fully annotated DIP1000 code, my only 
conclusion is that DIP1000 has some serious scope of proposal level 
issues and cannot be rectified. This has been my conclusion *after* 
trying to fix it for a few years and having no proposals that could.

Depending upon how DConf goes, I might be looking at dumping it, because 
as-is it has too many scope-level holes that will not be fixed. But in 
saying that, Dennis I sincerely hope you can do some serious magic here 
because it'll be impressive!
Aug 25
parent jmh530 <john.michael.hall gmail.com> writes:
On Sunday, 25 August 2024 at 16:23:40 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 [snip]
 - The abomination that is scope return + return scope (this is 
 easier to resolve in terms of new UDA's, but does not solve 
 communicability of behavior).
I think it would probably be useful to separate woes with DIP 1000 into woes with `scope` parameters and woes with `return scope`/`ref return scope` parameters. When it comes to mental complexity, I see that as less of an issue for `scope` parameters than for `return scope` or `ref return scope` parameters There might be some outstanding issues with `scope`, like what you and Dennis bring up, but ultimately I think what `return scope` is doing is kind of like Rust lifetimes-lite. It solves a particular use case, but isn't sophisticated enough for more complicated ones.
Aug 27
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:

 If you've had problems using DIP1000, please post here with a 
 description of what you encountered and any details about it 
 you think we may find helpful. Please provide example code 
 where possible.
Here, I just made a PR to mysql-native, with dip1000 enabled. The last time I went down this rabbit hole, it was clear this is unworkable. You are welcome to submit PRs to my PR to show me how to fix it, please explain how everything works. I don't have time to play around with dip1000 normally -- it generally just results in bizarre and unfixable errors. https://github.com/mysql-d/mysql-native/pull/283 -Steve
Aug 25
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 8/25/24 19:02, Steven Schveighoffer wrote:
 
 I don't have time to play around with dip1000 normally -- it generally 
 just results in bizarre and unfixable errors.
 
 https://github.com/mysql-d/mysql-native/pull/283
diff --git a/source/mysql/protocol/comms.d b/source/mysql/protocol/comms.d index ec2a3ea..0238926 100644 --- a/source/mysql/protocol/comms.d +++ b/source/mysql/protocol/comms.d -689,7 +689,7 package(mysql) ubyte[] getPacket(Connection conn) return packet; } -package(mysql) void send(MySQLSocket _socket, const(ubyte)[] packet) +package(mysql) void send(MySQLSocket _socket, scope const(ubyte)[] packet) in { assert(packet.length > 4); // at least 1 byte more than header diff --git a/source/mysql/protocol/sockets.d b/source/mysql/protocol/sockets.d index a582fc9..7da43d7 100644 --- a/source/mysql/protocol/sockets.d +++ b/source/mysql/protocol/sockets.d -39,7 +39,7 interface MySQLSocket safe: void close(); property bool connected() const; - void read(ubyte[] dst); + void read(scope ubyte[] dst); void write(const scope ubyte[] bytes); void acquire(); -78,7 +78,7 class MySQLSocketPhobos : MySQLSocket return socket.isAlive; } - void read(ubyte[] dst) + void read(scope ubyte[] dst) { // Note: I'm a little uncomfortable with this line as it doesn't // (and can't) update Connection._open. Not sure what can be done,
Aug 25
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On Sunday, 25 August 2024 at 20:02:01 UTC, Timon Gehr wrote:
 On 8/25/24 19:02, Steven Schveighoffer wrote:
 
 I don't have time to play around with dip1000 normally -- it 
 generally just results in bizarre and unfixable errors.
 
 https://github.com/mysql-d/mysql-native/pull/283
diff --git a/source/mysql/protocol/comms.d
Huh, this actually was it. I fully expected a deeper rabbit hole. Now I have to figure out how to only turn on dip1000 for certain versions of D, as older compilers didn't have proper marking on std.socket... -Steve
Aug 27
prev sibling next sibling parent Elias Batek <desisma heidel.beer> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 As an example of the sort of thing we're looking for, someone 
 in the meeting brought up the problems encountered with trying 
 to add DIP1000 support to vibe.d.
My first attempt at adding DIP1000-compatibility to oceandrift/http was a disaster. An hour or two of attribute tuning in a whack-a-mole fashion during which I was asking myself why I had to do this manually as I was mostly just making the changes suggested by the compiler. It all had an end when I ran into an presumably `return ref return scope` case; something that isn’t doable in D. The second attempt was a bit more successful. I’ve committed a scarily big number of patches to a feature branch, pushed it to GitHub and let it be: <https://github.com/oceandrift/http/commit/996b743f1a7b5f4799dcde802971bfeb5a675cf1> My third attempt was more pragmatic and meant “not doing anything that would make me bother with DIP1000’s scope gulasch myself”. I was quite happy with the end result.
Aug 25
prev sibling next sibling parent Elias Batek <desisma heidel.beer> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 If you've had problems using DIP1000, please post here with a 
 description of what you encountered and any details about it 
 you think we may find helpful.
One of the most common issues I run into when talking about DIP1000 is that DIP1000 is actually two things: 1. Plugging holes in ` safe`. 2. Providing a mechanism the return/ref/scope mechanism re-enables most of the sort of code that is flagged ` system` after implementing the former. Furthermore, I have yet to meet anyone who tells me that adding 2-word attributes (`scope return` vs `return scope`) to the soup was a great choice. I see a major comprehensibility problem with these. It’s also a bit funny that all of those things are still referred to as “DIP1000”. If one takes just a slight glimpse at the document of the same name, it clearly says “Superseded”. I do understand why the article “Coralling Wild Pointers With ref return scope” explains most of the concepts on raw pointers. But on the other hand I’m not sure how useful those are to the unsuspecting users who run into DIP1000 violations with static arrays or refs. Or they even just wanted to use `std.getopt` in ` safe` code and ended up dealing with DIP1000. That’s what made me turn on the compiler switch for first time.
Aug 25
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
I found the following inconsistency:

```
 safe ref int abc(int i) { return ghi(i); }
 trusted ref int ghi(ref int r) { return r; }

 safe int* foo(int i) { return bar(&i); } // Error: reference to local variable 
`i` assigned to non-scope parameter `p` calling `bar`
 trusted int* bar(int* p) { return p; }
```

Compiling with -dip1000 gives the error indicated. Note that it did not give an 
error for the semantically equivalent call to ghi(i).

Possible resolutions:

1. give an error for both

2. give no error for both, as  trusted implementations are the user's problem

3. disallow taking the address of a local variable in  safe code
Aug 26
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 26/08/2024 8:17 PM, Walter Bright wrote:
 I found the following inconsistency:
 
 ```
  safe ref int abc(int i) { return ghi(i); }
  trusted ref int ghi(ref int r) { return r; }
 
  safe int* foo(int i) { return bar(&i); } // Error: reference to local 
 variable `i` assigned to non-scope parameter `p` calling `bar`
  trusted int* bar(int* p) { return p; }
 ```
 
 Compiling with -dip1000 gives the error indicated. Note that it did not 
 give an error for the semantically equivalent call to ghi(i).
 
 Possible resolutions:
 
 1. give an error for both
 
 2. give no error for both, as  trusted implementations are the user's 
 problem
 
 3. disallow taking the address of a local variable in  safe code
There is a fourth option: Infer for `` trusted`` functions, but don't validate. Support describing the empty escape set separately from wanting it inferred. If set, trust it, otherwise infer. Will infer: ```d trusted ref int ghi(ref int r) { return r; } ``` Will use annotation: ```d trusted ref int ghi( escapevia(return) ref int r) { return r; } trusted ref int ghi( escapevia() ref int r) { return r; } ``` First and second will error, but third won't. This appears to be an interaction between DIP1000 and the fact that `` trusted`` functions have an interface that is required to be `` safe`` but not its body validated.
Aug 26
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/26/2024 1:33 AM, Richard (Rikki) Andrew Cattermole wrote:
 This appears to be an interaction between DIP1000 and the fact that
`` trusted`` 
 functions have an interface that is required to be `` safe`` but not its body 
 validated.
It not only appears to be, it is true that trusted functions are not validated.
Aug 26
parent Dom DiSc <dominikus scherkl.de> writes:
On Monday, 26 August 2024 at 18:54:26 UTC, Walter Bright wrote:
 On 8/26/2024 1:33 AM, Richard (Rikki) Andrew Cattermole wrote:
 This appears to be an interaction between DIP1000 and the fact 
 that `` trusted`` functions have an interface that is required 
 to be `` safe`` but not its body validated.
It not only appears to be, it is true that trusted functions are not validated.
But their API should be checked. That would actually make trusted *functions* (not trusted blocks) much more useful. That would make for a useful difference between safe and trusted - one is completely checked, for the other only the parameters and return values are checked. These checks would make it much more plausible why safe functions should be allowed to call trusted functions but not system functions. Having a (guaranteed) safe interface makes trusted functions a whole lot more trustworthy.
Aug 26
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 8/26/24 10:17, Walter Bright wrote:
 I found the following inconsistency:
 
 ```
  safe ref int abc(int i) { return ghi(i); }
  trusted ref int ghi(ref int r) { return r; }
 
  safe int* foo(int i) { return bar(&i); } // Error: reference to local 
 variable `i` assigned to non-scope parameter `p` calling `bar`
  trusted int* bar(int* p) { return p; }
 ```
 ...
This is exactly how it should work. What's inconsistent here is your code. The first ` trusted` function is invalid. The second ` trusted` function is fine because the parameter is not `scope`. The second function can even be ` safe`. The first memory safety bug is therefore not caught (because the bug is in the ` trusted` function), while the second one is caught (because the bug is in the ` safe` function).
 Compiling with -dip1000 gives the error indicated. Note that it did not 
 give an error for the semantically equivalent call to ghi(i).
 
If you want to attribute blame for a bug, not only the semantics matter, also the contracts at function interfaces matter. This is exactly why DIP1000 comes with annotations to make the intent clear and checkable modularly. There is no analogy between raw pointers and `ref`, there is an analogy between `scope` pointers and `ref`. (If you want to continue this discussion, we should probably take it out of the thread.)
Aug 26
next sibling parent Nick Treleaven <nick geany.org> writes:
On Monday, 26 August 2024 at 09:31:18 UTC, Timon Gehr wrote:
 On 8/26/24 10:17, Walter Bright wrote:
  trusted ref int ghi(ref int r) { return r; }
What's inconsistent here is your code. The first ` trusted` function is invalid.
Yes, though the compiler could actually error on the return statement for `ghi` (because a trusted function must have a safe interface). That would be consistent with erroring when returning a local variable as `ref`: ```d trusted ref int h() { int i; return i; // Error: returning `i` escapes a reference to local variable `i` } ```
Aug 26
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
That is correct, I was just thinking of using  trusted to avoid checks, rather 
than ensuring safe code.

The reason inference is not done automatically for all functions is because:

```
int* foo(int*);
```
```
int* foo(int* p) { return p; }
```

are different functions.

Inference affects the name mangling of D functions. So, for D functions, the 
program will fail to link with inferred attributes.

But with C and C++ linkage, the name mangling isn't reflected in the symbol
name.

I suppose it's a problem we can live with.

Attribute inference isn't perfect, either, but it may work well enough to make 
dip1000 more palatable.
Aug 26
prev sibling next sibling parent Juraj <zero vec4.xyz> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 **Please do not reply to posts in this thread unless you have 
 additional relevant information regarding the problem 
 described.** We want to collect examples, and any side 
 discussions will just be noise.

 Thanks!
I am probably adding to the noise, but I think this is relevant. I can not be the only one, that can not provide a bug report or a code snipped because I just lost interest in pursuing DIP1000 compatibility years ago. In the end the whole thing just feels off, and is one of my major concerns about going ` safe` by default. In all seriousness I am not willing to look at a function signature full of `return` keywords and figuring out what it means. More so, not gonna spend my energy writing such thing.
 First, it's pretty clear that inference by default is necessary 
 to make DIP1000 easier to use. It's not clear at the moment how 
 this can be achieved, but it's something that we need to work 
 out.
In my opinion, without this, there will just not be enough users. (I written a bigger rant, but lets keep it simple) Juraj
Aug 26
prev sibling next sibling parent Dennis <dkorpel gmail.com> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 Second, we'd like to get a number of examples of problems 
 people have had with using DIP1000 that haven't shown up in 
 Bugzilla.
1. `scope` on a struct variable makes all fields `scope`. This came up when making a tokenizer range: When parsing a stack-allocated string, the resulting tokens also become `scope` even when they don't refer to the input string. ```D class Token {} struct Tokenizer { char[] input; Token front; } Token getToken() safe { char[5] input = "1 + 2"; auto range = Tokenizer(input[]); // range as a whole becomes `scope` here return range.front; // Unnecessary error that `range.front` is `scope` } ``` 2. Scope inference on assignment doesn't remember the original lifetime, but shortens it to the assigned variable. ```D struct S { string name; void doSomething() scope; } void f(scope S s) { string prev = s.name; // prev simply becomes `scope`, doesn't remember it came from `s` s.doSomething(); s.name = prev; // Unnecessary error: `prev` assigned to s with longer lifetime } ``` 3. Lack of transitive scope I.e. you can't have an array of stack allocated strings, or a hashmap using scope keys/values. There are workarounds. For example, in my regex match function, I return indices of matches instead of slices of the input string. This does make the API a lot less ergonomic though.
Aug 26
prev sibling next sibling parent Dukc <ajieskola gmail.com> writes:
Mike Parker kirjoitti 25.8.2024 klo 16.10:
 DIP1000 keeps rearing its head in discussions touching on safety in our 
 monthly meetings. So we recently held a planning session focused 
 exclusively on DIP1000. Two things came out of it.
 
 First, it's pretty clear that inference by default is necessary to make 
 DIP1000 easier to use. It's not clear at the moment how this can be 
 achieved, but it's something that we need to work out.
 
 Second, we'd like to get a number of examples of problems people have 
 had with using DIP1000 that haven't shown up in Bugzilla. Walter wants 
 to be as certain as he can whether such issues are fixable or if the 
 design is fundamentally flawed.
 
 If you've had problems using DIP1000, please post here with a 
 description of what you encountered and any details about it you think 
 we may find helpful. Please provide example code where possible.
 
 As an example of the sort of thing we're looking for, someone in the 
 meeting brought up the problems encountered with trying to add DIP1000 
 support to vibe.d.
 
 **Please do not reply to posts in this thread unless you have additional 
 relevant information regarding the problem described.** We want to 
 collect examples, and any side discussions will just be noise.
 
 Thanks!
 
 
That `scope` can get inferred to a local variable due to a LATER assignment to it leads to pretty confusing situations. I think only the initialisation should infer. I'm not sure if it's worth a change anymore though, as it'll lead to more breakage. Maybe over an edition switch. I still think `scope`/`return scope` inference should only happen on a ` safe` function (whether explicitly safe or inferred as so). In ` trusted` / ` system` code it is critical you don't have those attributes added and removed under your nose or in an implementation-defined manner. Yet it [can happen](https://forum.dlang.org/post/edtbjavjzkwogvutxpho forum.dlang.org). Design by introspection also doesn't [fully work with](https://issues.dlang.org/show_bug.cgi?id=24003) `scope` / `return scope`, as I discovered when I tried to make a [Phobos function DIP1000 - compilant](https://issues.dlang.org/show_bug.cgi?id=23300). Dennis did make a pull request to enable this but it stalled.
Aug 26
prev sibling next sibling parent Juraj <zero vec4.xyz> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 Second, we'd like to get a number of examples of problems 
 people have had with using DIP1000 that haven't shown up in 
 Bugzilla. Walter wants to be as certain as he can whether such 
 issues are fixable or if the design is fundamentally flawed.
As stated in my previous reply, I dont have problems with DIP1000 as I just ignore it, because of bad ergonomics. But I felt guilty for bashing something I haven't used in years, so here is first thing I tried. All my shenanigans are catched without `-dip1000`. (I over-use ` safe` just to be sure) ```d safe: struct S { int x; S* s_ptr_0; S* s_ptr_1; } S* g_ptr = null; S* goo(return ref S sp) safe => &sp; S foo(int v) safe { S s0 = S(333); S s1 = S(v); void ez_escape() safe { auto res = goo(s1); g_ptr = res; } ez_escape(); () safe { S* moo(return ref S sp) safe => &sp; s1.s_ptr_0 = moo(s0); }(); () safe { S* moo(S* sp) safe => sp; s1.s_ptr_1 = moo(&s0); }(); return s1; } ```
Aug 26
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 If you've had problems using DIP1000, please post here with a 
 description of what you encountered and any details about it 
 you think we may find helpful. Please provide example code 
 where possible.
Here's an issue I ran into when working on my proof-of-concept allocator/container library. When writing exception-safe code, a common pattern is to save the original value of a variable before performing an operation that might throw, and then restore that value if the operation fails. Unfortunately, it is impossible to do this in ` safe` code if the variable you want to save and restore is `scope`, because the compiler will treat the saved copy as having a shorter lifetime than the original. For example, this code fails to compile when using `-preview=dip1000`: safe void example(ref scope int[] var) { int[] savedValue = var; scope (failure) var = savedValue; // Do stuff with a that might throw... } ...with the following error message: onlineapp.d(4): Error: scope variable `savedValue` assigned to `ref` variable `var` with longer lifetime Ideally, the compiler should be able to infer that `savedValue` has the *exact same* lifetime as `var` in this code, and allow the assignment.
Aug 27
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 First, it's pretty clear that inference by default is necessary 
 to make DIP1000 easier to use. It's not clear at the moment how 
 this can be achieved, but it's something that we need to work 
 out.
In addition to inference, it is vitally important to have detailed error messages showing exactly where and why inference failed. Here's an illustrative example: int* global; void foo()(int* p) { global = p; } void bar()() { int n; foo(&n); } safe void main() { bar(); } Currently, compiling this program with `-preview=dip1000` gives the following error message: example.d(16): Error: ` safe` function `D main` cannot call ` system` function `example.bar!().bar` example.d(11): which wasn't inferred ` safe` because of: example.d(11): reference to local variable `n` assigned to non-scope parameter `p` calling `foo` The problem with this message is that it does not explain *why* `p` is a non-scope parameter. In order to get that information, we have to modify the definition of `foo` and add `scope` manually: -void foo()(int* p) +void foo()(scope int* p) When we do, we get an error message that points to the *real* source of the problem: example2.d(16): Error: ` safe` function `D main` cannot call ` system` function `example2.bar!().bar` example2.d(3): which calls `example2.foo!().foo` example2.d(5): which wasn't inferred ` safe` because of: example2.d(5): scope variable `p` assigned to global variable `global` In order for normal D programmers to have any hope of using DIP 1000 successfully, all of the relevant information *must* be present in the original error message. Users must not be required to modify their code (or, worse, the code of libraries they depend on) in order to reveal this information.
Aug 27
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 Second, we'd like to get a number of examples of problems 
 people have had with using DIP1000 that haven't shown up in 
 Bugzilla. Walter wants to be as certain as he can whether such 
 issues are fixable or if the design is fundamentally flawed.
Here's an issue that stems from a fundamental shortcoming of DIP1000's design: you can't write a ` safe` `swap` function. Consider the following example: safe void swap(ref /* ??? */ int* a, ref return scope int* b); safe unittest { int local; static int global; // Ensure both pointers have identical lexical scopes static struct Pair { int* a, b; } auto p1 = Pair(&local, &local); auto p2 = Pair(&global, &global); auto p3 = Pair(&local, &global); auto p4 = Pair(&global, &local); swap(p1.a, p1.b); swap(p2.a, p2.b); static assert(!__traits(compiles, swap(p3.a, p3.b))); static assert(!__traits(compiles, swap(p4.a, p4.b))); } A correct, ` safe` function signature for `swap` should be able to pass this unit test. However, under the DIP1000 system, there is no possible combination of attributes for the parameter `a` that will not cause one of the test cases to fail. - `ref int* a` causes `swap(p1.a, p1.b)` to fail. - Both `ref scope int* a` and `ref return scope int* a` cause `swap(p3.a, p3.b)` to compile when it shouldn't.
Aug 28
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 29/08/2024 2:48 AM, Paul Backus wrote:
 On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 Second, we'd like to get a number of examples of problems people have 
 had with using DIP1000 that haven't shown up in Bugzilla. Walter wants 
 to be as certain as he can whether such issues are fixable or if the 
 design is fundamentally flawed.
Here's an issue that stems from a fundamental shortcoming of DIP1000's design: you can't write a ` safe` `swap` function. Consider the following example:     safe void swap(ref /* ??? */ int* a, ref return scope int* b);     safe unittest     {         int local;         static int global;         // Ensure both pointers have identical lexical scopes         static struct Pair { int* a, b; }         auto p1 = Pair(&local, &local);         auto p2 = Pair(&global, &global);         auto p3 = Pair(&local, &global);         auto p4 = Pair(&global, &local);         swap(p1.a, p1.b);         swap(p2.a, p2.b);         static assert(!__traits(compiles, swap(p3.a, p3.b)));         static assert(!__traits(compiles, swap(p4.a, p4.b)));     } A correct, ` safe` function signature for `swap` should be able to pass this unit test. However, under the DIP1000 system, there is no possible combination of attributes for the parameter `a` that will not cause one of the test cases to fail. - `ref int* a` causes `swap(p1.a, p1.b)` to fail. - Both `ref scope int* a` and `ref return scope int* a` cause `swap(p3.a, p3.b)` to compile when it shouldn't.
This is a good example of what I mean by multiple outputs are not supported. Both parameters are outputs, not just the first one. ``` onlineapp.d(9): Error: scope variable `temp` assigned to `ref` variable `a` with longer lifetime onlineapp.d(25): Error: scope variable `p1` assigned to non-scope parameter `a` calling `swap` onlineapp.d(8): which is not `scope` because of `b = a` ``` ```d safe void swap(ref /* ??? */ int* a, ref return scope int* b) { int* temp = b; b = a; a = temp; } safe unittest { int local; static int global; // Ensure both pointers have identical lexical scopes static struct Pair { int* a, b; } auto p1 = Pair(&local, &local); auto p2 = Pair(&global, &global); auto p3 = Pair(&local, &global); auto p4 = Pair(&global, &local); swap(p1.a, p1.b); swap(p2.a, p2.b); static assert(!__traits(compiles, swap(p3.a, p3.b))); static assert(!__traits(compiles, swap(p4.a, p4.b))); } ``` The line ``b = a;`` should also be erroring, but because ``b`` isn't seen as an error, its accepted.
Aug 28
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
I've been thinking about swap/move functions some more for my own proposal.

The following applies to swap just as much as move, but I'll use move as 
its simpler to understand here.

Lets say you don't have owner escape analysis (so just DIP1000).
And you have some stack memory that then gets borrowed from.
You can corrupt the borrowed memory, by calling a function like move.

Simply because a dependency exists of the thing being moved/swapped.

```d
int* value = ...;
int** ptr = &value; // ptr depends upon value

int* another = move(value);
// ugh oh, ptr is now corrupted
```

Now lets say you are able to annotate the escape sets fully (DIP1000 can 
do move).

```d
T move(T)(ref return T input);
```

Thing is, this signature just says the parameter contributes towards the 
return, it doesn't consume and invalidate the original input variable.

So a solution is to throw a new attribute into the mix like 
`` escapeas(return)``. To show that it'll consume the input and it will 
go to a specific variable.

```d
void swap(T)(ref  escapeas(input2) T input1, ref  escapeas(input1) T 
input2);
```

Ok great, you modeled the escapes, congratulations.

Or you could use owner escape analysis to say "if have borrow, make 
effectively const" there cannot be assigned to.

Boom, neither swap nor move could modify the original and it all works.

So there are three scenarios here that I can think of:

1. Attribute the exact variable it'll escape to, to know that it will 
invalidate the input, preventing any relationships from it from being 
corrupted
2. Make these functions `` system`` and say its a rare enough thing that 
it doesn't need to be made `` safe``
3. Use owner escape analysis to provide the effectively const guarantees 
to prevent the mutation

Full example of a move in `` safe`` code, that can corrupt memory (null, 
wrong length, wrong value ext.):

```d
 safe:

void main() {
     int*[1] value;
     int** ptr = &value[0];

     int* another = move(value[0]);
}

T move(T)(scope ref return T input) {
     T temp = input;
     input = T.init;
     return temp;
}
```
Aug 29
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 29/08/2024 8:53 PM, Richard (Rikki) Andrew Cattermole wrote:
 Thing is, this signature just says the parameter contributes towards the 
 return, it doesn't consume and invalidate the original input variable.
To clarify, "invalidate the original input variable" means that from the caller function, after the function call the argument variables will no longer be the values that the compiler will be expecting. Potentially causing memory corruption that would otherwise have been caught.
Aug 29
prev sibling parent reply Atila Neves <atila.neves gmail.com> writes:
On Wednesday, 28 August 2024 at 14:48:04 UTC, Paul Backus wrote:
 On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 [...]
Here's an issue that stems from a fundamental shortcoming of DIP1000's design: you can't write a ` safe` `swap` function. [...]
Interesting, thank you. I went and looked at how Rust fixes this and as I expected it's with an `unsafe` block: https://doc.rust-lang.org/1.80.1/src/core/mem/mod.rs.html#728
Aug 29
parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 29 August 2024 at 16:10:37 UTC, Atila Neves wrote:
 On Wednesday, 28 August 2024 at 14:48:04 UTC, Paul Backus wrote:
 On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 [...]
Here's an issue that stems from a fundamental shortcoming of DIP1000's design: you can't write a ` safe` `swap` function. [...]
Interesting, thank you. I went and looked at how Rust fixes this and as I expected it's with an `unsafe` block: https://doc.rust-lang.org/1.80.1/src/core/mem/mod.rs.html#728
To be clear, the issue here is not that the *implementation* requires `unsafe`/` trusted`. The issue is that under the current system, it is impossible to give `swap` a ` safe` *function signature* that accepts `scope` arguments. That's why I left the body of the `swap` function out of my example. It's completely irrelevant.
Aug 29
next sibling parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Aug 29, 2024 at 07:38:50PM +0000, Paul Backus via Digitalmars-d wrote:
[...]
 To be clear, the issue here is not that the *implementation* requires
 `unsafe`/` trusted`. The issue is that under the current system, it is
 impossible to give `swap` a ` safe` *function signature* that accepts
 `scope` arguments.
 
 That's why I left the body of the `swap` function out of my example.
 It's completely irrelevant.
Exactly; this is why 'scope' is an inadequate solution to the wrong problem. The whole discussion about lifetimes in D sounds like D really, badly, wants a Rust-like lifetime system, but, not wanting to ape Rust, which is considered too complex, we're trying instead to shoehorn various simple but incomplete solutions to try to pretend to solve the original problem. I appreciate Walter's efforts to find simple solutions to problems -- that's the quality of a worthy engineer -- but some problems are simply inherently complex, and any solution that's simpler than the minimum complexity simply cannot be made to work, no matter how hard you try. More and more, lifetime tracking is appearing to be one of these problems. Any solution less complex than Rust's seems doomed to fail on some obscure corner case that will nevertheless compromise the entire solution. T -- Nearly all men can stand adversity, but if you want to test a man's character, give him power. -- Abraham Lincoln
Aug 29
prev sibling parent Atila Neves <atila.neves gmail.com> writes:
On Thursday, 29 August 2024 at 19:38:50 UTC, Paul Backus wrote:
 On Thursday, 29 August 2024 at 16:10:37 UTC, Atila Neves wrote:
 On Wednesday, 28 August 2024 at 14:48:04 UTC, Paul Backus 
 wrote:
 [...]
Interesting, thank you. I went and looked at how Rust fixes this and as I expected it's with an `unsafe` block: https://doc.rust-lang.org/1.80.1/src/core/mem/mod.rs.html#728
To be clear, the issue here is not that the *implementation* requires `unsafe`/` trusted`. The issue is that under the current system, it is impossible to give `swap` a ` safe` *function signature* that accepts `scope` arguments. That's why I left the body of the `swap` function out of my example. It's completely irrelevant.
Ah. Yes, that's bad.
Aug 30
prev sibling parent reply IchorDev <zxinsworld gmail.com> writes:
On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 Second, we'd like to get a number of examples of problems 
 people have had with using DIP1000 that haven't shown up in 
 Bugzilla. Walter wants to be as certain as he can whether such 
 issues are fixable or if the design is fundamentally flawed.
A problem I’ve often encountered with the design of DIP1000 is that factory functions returning `scope` variables isn’t really possible. I often want to construct something in a function and then return it, which allows me to make my code more modular. Technically you can do this with a `ref` parameter, but then you have to always remember to create the variable and then populate it with the factory function… and then I remember that I can just turn DIP1000 off, so I do. It’s great that I *could* put a class instance on the stack with `scope`, but I don’t want to deal with not even being able to return it. In short, my experience with DIP1000 `scope` is like if `pure` was upgraded to the final boss of D; declaring ‘thou shalt write a program that never returns anything!’
Aug 29
parent IchorDev <zxinsworld gmail.com> writes:
On Thursday, 29 August 2024 at 21:30:31 UTC, IchorDev wrote:
 On Sunday, 25 August 2024 at 13:10:22 UTC, Mike Parker wrote:
 Second, we'd like to get a number of examples of problems 
 people have had with using DIP1000 that haven't shown up in 
 Bugzilla. Walter wants to be as certain as he can whether such 
 issues are fixable or if the design is fundamentally flawed.
In short, my experience with DIP1000 `scope` is like if `pure` was upgraded to the final boss of D; declaring ‘thou shalt write a program that never returns anything!’
Just wanted to add that I do think `scope` is a fantastic and powerful tool for library APIs to indicate that they’re not going to hold on to your data, meaning you don’t have to worry about it being modified from outside. I think any replacement to DIP1000’s `scope` should provide some way to make the same guarantee about whether a parameter is retained or not. (And obviously it should be inferred by default!)
Aug 29