Thread overview | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
August 17, 2018 Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Atila Neves | 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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | 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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | 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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Atila Neves | 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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Atila Neves | 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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Kagamin | 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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Kagamin | 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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Nicholas Wilson | 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 Re: Friends don't let friends use inout with scope and -dip1000 | ||||
---|---|---|---|---|
| ||||
Posted in reply to Nicholas Wilson | 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
|
Copyright © 1999-2021 by the D Language Foundation