Jump to page: 1 2 3
Thread overview
Friends don't let friends use inout with scope and -dip1000
Aug 17, 2018
Atila Neves
Aug 17, 2018
Dukc
Aug 20, 2018
Atila Neves
Aug 20, 2018
Nicholas Wilson
Aug 20, 2018
Atila Neves
Aug 20, 2018
Jonathan M Davis
Aug 20, 2018
Kagamin
Aug 20, 2018
Kagamin
Aug 20, 2018
Atila Neves
Aug 20, 2018
Kagamin
Aug 20, 2018
Dgame
Aug 21, 2018
Atila Neves
Aug 21, 2018
Atila Neves
Aug 21, 2018
Nicholas Wilson
Aug 21, 2018
Atila Neves
Aug 21, 2018
Kagamin
Aug 21, 2018
Kagamin
Aug 22, 2018
Kagamin
Aug 22, 2018
Kagamin
Aug 21, 2018
Nick Treleaven
Aug 21, 2018
Kagamin
Aug 21, 2018
Kagamin
August 17, 2018
Here's a struct:

-----------------
struct MyStruct {
    import core.stdc.stdlib;
    int* ints;
    this(int size) @trusted { ints = cast(int*) malloc(size); }
    ~this() @trusted { free(ints); }
    scope int* ptr() { return ints; }
}
-----------------

Let's try and be evil with -dip1000:

-----------------
@safe:

// struct MyStruct ...

const(int) *gInt;

void main() {
    auto s = MyStruct(10);
    gInt = s.ptr;
}
-----------------

% dmd -dip1000 scope_inout.d
scope_inout.d(26): Error: scope variable this may not be returned


Yay!

What if instead of `auto` I write `const` instead (or immutable)? This is D we're talking about, so none of this boilerplate nonsense of writing two (or three) basically identical functions. So:

-----------------
// used to be scope int* ptr() { return ints; }
scope inout(int)* ptr() inout { return ints; }
-----------------

% dmd -dip1000 scope_inout.d
% echo $?
0
# nope, no error here

Wait, what? Turns out now it compiles. After some under-the-breath mumbling I go hit issues.dlang.org and realise that the issue already exists:


https://issues.dlang.org/show_bug.cgi?id=17935


For reasons unfathomable to me, this is considered the _correct_ behaviour. Weirder still, writing out the boilerplate that `inout` is supposed to save us (mutable, const and immutable versions) doesn't compile, which is what one would expect.

So: @safe + inout + scope + dip1000 + custom memory allocation in D gets us to the usability of C++ circa 1998. At least now we have valgrind and asan I guess.

"What about template this?", I hear you ask. It kinda works. Sorta. Kinda. Behold:

------------
scope auto ptr(this T)() { return ints; }
------------

After changing the definition of `ptr` this way the code compiles fine and `ints` is escaped. Huh. However, if you change `auto s` to `scope s`, it fails to compile as <insert deity> intended. Very weird.

If you change the destructor to `scope` then it also fails to compile even if it's `auto s`. Because, _obviously_, that's totally different.

I'd file an issue but given that the original one is considered not a bug for some reason, I have no idea about what I just wrote is right or not.

What I do know is I found multiple ways to do nasty things to memory under the guise of @safe and -dip1000, and my understanding was that the compiler would save me from myself. In the meanwhile I'm staying away from `inout` and putting `scope` on my destructors even if I don't quite understand when destructors should be `scope`. Probably always? I have no idea.


August 17, 2018
On 8/17/18 3:36 AM, Atila Neves wrote:
> Here's a struct:
> 
> -----------------
> struct MyStruct {
>      import core.stdc.stdlib;
>      int* ints;
>      this(int size) @trusted { ints = cast(int*) malloc(size); }
>      ~this() @trusted { free(ints); }
>      scope int* ptr() { return ints; }
> }
> -----------------
> 
> Let's try and be evil with -dip1000:
> 
> -----------------
> @safe:
> 
> // struct MyStruct ...
> 
> const(int) *gInt;
> 
> void main() {
>      auto s = MyStruct(10);
>      gInt = s.ptr;
> }
> -----------------
> 
> % dmd -dip1000 scope_inout.d
> scope_inout.d(26): Error: scope variable this may not be returned
> 
> 
> Yay!
> 
> What if instead of `auto` I write `const` instead (or immutable)? This is D we're talking about, so none of this boilerplate nonsense of writing two (or three) basically identical functions. So:
> 
> -----------------
> // used to be scope int* ptr() { return ints; }
> scope inout(int)* ptr() inout { return ints; }

Does scope apply to the return value or the `this` reference?

What happens if you remove the return type? (i.e. scope auto)

> -----------------
> 
> % dmd -dip1000 scope_inout.d
> % echo $?
> 0
> # nope, no error here
> 
> Wait, what? Turns out now it compiles. After some under-the-breath mumbling I go hit issues.dlang.org and realise that the issue already exists:
> 
> 
> https://issues.dlang.org/show_bug.cgi?id=17935

I don't see what this bug report has to do with the given case.

> 
> 
> For reasons unfathomable to me, this is considered the _correct_ behaviour. Weirder still, writing out the boilerplate that `inout` is supposed to save us (mutable, const and immutable versions) doesn't compile, which is what one would expect.
> 
> So: @safe + inout + scope + dip1000 + custom memory allocation in D gets us to the usability of C++ circa 1998. At least now we have valgrind and asan I guess.
> 
> "What about template this?", I hear you ask. It kinda works. Sorta. Kinda. Behold:
> 
> ------------
> scope auto ptr(this T)() { return ints; }
> ------------
> 
> After changing the definition of `ptr` this way the code compiles fine and `ints` is escaped. Huh. However, if you change `auto s` to `scope s`, it fails to compile as <insert deity> intended. Very weird.

This seems like a straight up bug.

> 
> If you change the destructor to `scope` then it also fails to compile even if it's `auto s`. Because, _obviously_, that's totally different.
> 
> I'd file an issue but given that the original one is considered not a bug for some reason, I have no idea about what I just wrote is right or not.
> 
> What I do know is I found multiple ways to do nasty things to memory under the guise of @safe and -dip1000, and my understanding was that the compiler would save me from myself. In the meanwhile I'm staying away from `inout` and putting `scope` on my destructors even if I don't quite understand when destructors should be `scope`. Probably always? I have no idea.
> 
> 

This doesn't surprise me. I'm beginning to question whether scope shouldn't have been a type constructor instead of a storage class. It's treated almost like a type constructor in most places, but the language grammar makes it difficult to be specific as to what part it applies.

-Steve
August 17, 2018
On Friday, 17 August 2018 at 13:39:29 UTC, Steven Schveighoffer wrote:
> On 8/17/18 3:36 AM, Atila Neves wrote:
>> Here's a struct:
>> -----------------
>> // used to be scope int* ptr() { return ints; }
>> scope inout(int)* ptr() inout { return ints; }
>
> Does scope apply to the return value or the `this` reference?
>

This reference. putting it like:

inout(int)* ptr() inout scope { return ints; }

...does not change anything.

Another thing it should AFAIK catch but doesn't:

import std.stdio;
@safe:

struct MyStruct {
    int* intP;
    this(int val) { intP = new int(val); }
    int* ptr() return scope { return intP; }
}


int *gInt;

void main() {
    auto s = MyStruct(10);
    gInt = s.ptr;
    writeln(*gInt);
}
August 20, 2018
On Friday, 17 August 2018 at 13:39:29 UTC, Steven Schveighoffer wrote:
> On 8/17/18 3:36 AM, Atila Neves wrote:
>> Here's a struct:
>> 
>> -----------------
>> struct MyStruct {
>>      import core.stdc.stdlib;
>>      int* ints;
>>      this(int size) @trusted { ints = cast(int*) malloc(size); }
>>      ~this() @trusted { free(ints); }
>>      scope int* ptr() { return ints; }
>> }
>> -----------------
>> 
>> Let's try and be evil with -dip1000:
>> 
>> -----------------
>> @safe:
>> 
>> // struct MyStruct ...
>> 
>> const(int) *gInt;
>> 
>> void main() {
>>      auto s = MyStruct(10);
>>      gInt = s.ptr;
>> }
>> -----------------
>> 
>> % dmd -dip1000 scope_inout.d
>> scope_inout.d(26): Error: scope variable this may not be returned
>> 
>> 
>> Yay!
>> 
>> What if instead of `auto` I write `const` instead (or immutable)? This is D we're talking about, so none of this boilerplate nonsense of writing two (or three) basically identical functions. So:
>> 
>> -----------------
>> // used to be scope int* ptr() { return ints; }
>> scope inout(int)* ptr() inout { return ints; }
>
> Does scope apply to the return value or the `this` reference?

I assumed the return value. I think I've read DIP1000 about a dozen times now and I still get confused. As opposed to `const` or `immutable`, `scope(T)` isn't a thing so... I don't know?

> What happens if you remove the return type? (i.e. scope auto)

And write what instead?

>
>> -----------------
>> 
>> % dmd -dip1000 scope_inout.d
>> % echo $?
>> 0
>> # nope, no error here
>> 
>> Wait, what? Turns out now it compiles. After some under-the-breath mumbling I go hit issues.dlang.org and realise that the issue already exists:
>> 
>> 
>> https://issues.dlang.org/show_bug.cgi?id=17935
>
> I don't see what this bug report has to do with the given case.

That's because I'm an idiot and I meant this one:

https://issues.dlang.org/show_bug.cgi?id=17927


> This seems like a straight up bug.

I agree, but I also think #17935 is a straight up bug as well...

> This doesn't surprise me. I'm beginning to question whether scope shouldn't have been a type constructor instead of a storage class. It's treated almost like a type constructor in most places, but the language grammar makes it difficult to be specific as to what part it applies.

I'm so confused it's not even funny.


August 20, 2018
On Monday, 20 August 2018 at 09:31:09 UTC, Atila Neves wrote:
> On Friday, 17 August 2018 at 13:39:29 UTC, Steven Schveighoffer wrote:
>>> // used to be scope int* ptr() { return ints; }
>>> scope inout(int)* ptr() inout { return ints; }
>>
>> Does scope apply to the return value or the `this` reference?
>
> I assumed the return value. I think I've read DIP1000 about a dozen times now and I still get confused. As opposed to `const` or `immutable`, `scope(T)` isn't a thing so... I don't know?
>
What usually happens is that qualifiers to the left of the name apply to the return type and those to the right apply `this`. Not that that _should_ make any difference since lifetime ints == lifetime this

>> What happens if you remove the return type? (i.e. scope auto)
>
> And write what instead?
>

scope ptr() inout { return ints; } ?

August 20, 2018
You need `return` attribute there, not `scope`:

struct MyStruct
{
    import core.stdc.stdlib;
    int* ints;
    this(int size) @trusted { ints = cast(int*) malloc(size); }
    ~this() @trusted { free(ints); }
    inout(int)* ptr() return inout { return ints; }
}
August 20, 2018
AIU, `return` for `scope` is what `inout` is for `const`. I proposed to extend `inout` to mean `return`, but Walter said that they are independent.
August 20, 2018
On Monday, 20 August 2018 at 12:56:42 UTC, Kagamin wrote:
> You need `return` attribute there, not `scope`:
>
> struct MyStruct
> {
>     import core.stdc.stdlib;
>     int* ints;
>     this(int size) @trusted { ints = cast(int*) malloc(size); }
>     ~this() @trusted { free(ints); }
>     inout(int)* ptr() return inout { return ints; }
> }

I need `return` for what exactly? Your code still compiles, and my point is it shouldn't. It sure isn't memory safe.
August 20, 2018
On Monday, 20 August 2018 at 09:43:46 UTC, Nicholas Wilson wrote:
> On Monday, 20 August 2018 at 09:31:09 UTC, Atila Neves wrote:
>> On Friday, 17 August 2018 at 13:39:29 UTC, Steven Schveighoffer wrote:
>>>> [...]
>>>
>>> Does scope apply to the return value or the `this` reference?
>>
>> I assumed the return value. I think I've read DIP1000 about a dozen times now and I still get confused. As opposed to `const` or `immutable`, `scope(T)` isn't a thing so... I don't know?
>>
> What usually happens is that qualifiers to the left of the name apply to the return type and those to the right apply `this`. Not that that _should_ make any difference since lifetime ints == lifetime this
>
>>> What happens if you remove the return type? (i.e. scope auto)
>>
>> And write what instead?
>>
>
> scope ptr() inout { return ints; } ?

I guess you meant `scope ptr(this This)() { return this; }`. Nothing changes from the behaviour I described.
August 20, 2018
On Monday, August 20, 2018 3:43:46 AM MDT Nicholas Wilson via Digitalmars-d wrote:
> On Monday, 20 August 2018 at 09:31:09 UTC, Atila Neves wrote:
> > On Friday, 17 August 2018 at 13:39:29 UTC, Steven Schveighoffer
> >
> > wrote:
> >>> // used to be scope int* ptr() { return ints; }
> >>> scope inout(int)* ptr() inout { return ints; }
> >>
> >> Does scope apply to the return value or the `this` reference?
> >
> > I assumed the return value. I think I've read DIP1000 about a dozen times now and I still get confused. As opposed to `const` or `immutable`, `scope(T)` isn't a thing so... I don't know?
>
> What usually happens is that qualifiers to the left of the name apply to the return type and those to the right apply `this`. Not that that _should_ make any difference since lifetime ints == lifetime this

I don't know what happens with scope with -dip1000, but that's not how D works with any other qualifier that can affect the return type. If you don't put parens on the qualifier on the return type, it refers to the this pointer/reference. And arguably, if we're now putting scope on return types, scope(T) should be a thing, and scope should have exactly the same behavior as const, shared, etc. with regards to placement, or it's just going to cause problems.

- Jonathan M Davis



« First   ‹ Prev
1 2 3