www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - relax inout rules?

reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
Currently, the rules of inout say you cannot put inout on a parameter  
without putting it on the return type as well.  This makes sense, it gives  
the compiler one place to worry about whether implicit casting rules will  
properly work.

But Timon Gehr brought up in another thread (started by Merdhad) that we  
can separate the resolution of inout from the applicability of the  
parameters.  Essentially, calling an inout function goes through two  
phases:

1. determine what inout resolves to
2. try and call the function.

inout rules don't have to change regarding resolving inout's actual  
value.  In other words, if all inout places are matched as int, immutable,  
or inout, then inout resolves to that value.  Otherwise inout resolves to  
const.

But what's different is (at least in my mind) the function call might not  
necessarily succeed even when the resolution is completed.

Take for example:

inout(int)* foo(inout(int)** a, inout(int)* b);

Now, if we call foo like this:

int a;
int b;
int* pa = &a;

foo(&pa, &b);

This means foo is called with (int **, int *).  This means inout resolves  
to mutable (no qualifier).  *NOW* we try calling foo as if it were written:

int *foo(int **a, int *b)

And it can be done.  Not only that, but there is nothing bad that could be  
happening in foo that should be disallowed by the compiler.

Now let's see what happens were we *could* do something bad:

immutable(int) c;
auto pc = &c;

foo(&pc, &b);

Now, foo is being called with (immutable(int)**, int *).  Inout resolves  
to const (due to the mix of mutable and immutable).  *NOW* we try calling  
foo as if it were written:

const(int)* foo(const(int)** a, const(int)* b);

And it *FAILS*.  This is because you cannot implicitly convert  
immutable(int)** to const(int)** (well, at least it *shouldn't* compile,  
I'm not sure if it does currently).

What this does is allow more possibilities for inout than we currently  
do.  Because inout is not now tied to returning something, we can create  
functions that have inout parameters but no inout return value.

The example we were discussing on the other thread was this:

void foo(ref inout(int)* a, inout(int)* b) { a = b;}

This would compile as long as you call with const(int)* as the first  
parameter, or if both parameters matched in constancy (i.e. both were  
immutable, both were mutable, or both were inout).

This gives us more cases where you don't have to repeat functions for the  
sake of handling different types of constancy, particularly when we have  
mutable references 2 levels deep.  I can't see anything technically wrong  
with this, can anyone else?

The one thing that I think still should be required is if you have inout  
on the return value, there *must* be an inout on a parameter.  Otherwise,  
the compiler has no idea what it should be (since we don't overload on  
return type in D).

If this ends up being viable, this is actually easier to explain than the  
current rules for inout.  We just have to make sure the rules are sound  
before doing something like this.

-Steve
Dec 12 2011
next sibling parent reply Jesse Phillips <jessekphillips+d gmail.com> writes:
On Mon, 12 Dec 2011 13:56:43 -0500, Steven Schveighoffer wrote:

 The example we were discussing on the other thread was this:
 
 void foo(ref inout(int)* a, inout(int)* b) { a = b;}
Is this just so it is easier to explain inout and won't have to explain that const will do the same thing? Or does this actually do something using const there doesn't? void f(ref const(int)* a, const(int)* b) {a = b; } void main() { const(int)* a; immutable(int)* b; auto c = (new int[](5)).ptr; f(a, c); f(b, c); //test.d(7): Error: cast(const(int)*)b is not an lvalue }
Dec 12 2011
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 13 Dec 2011 00:57:28 -0500, Jesse Phillips  
<jessekphillips+d gmail.com> wrote:

 On Mon, 12 Dec 2011 13:56:43 -0500, Steven Schveighoffer wrote:

 The example we were discussing on the other thread was this:

 void foo(ref inout(int)* a, inout(int)* b) { a = b;}
Is this just so it is easier to explain inout and won't have to explain that const will do the same thing? Or does this actually do something using const there doesn't? void f(ref const(int)* a, const(int)* b) {a = b; } void main() { const(int)* a; immutable(int)* b; auto c = (new int[](5)).ptr; f(a, c); f(b, c); //test.d(7): Error: cast(const(int)*)b is not an lvalue }
Yes it does: immutable(int)* d; foo(b, d); // OK f(b, d); // error (as you cited above). -Steve
Dec 14 2011
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/12/2011 10:56 AM, Steven Schveighoffer wrote:
 If this ends up being viable, this is actually easier to explain than the
 current rules for inout. We just have to make sure the rules are sound before
 doing something like this.
I don't understand the point of doing this. Just make it const.
Dec 12 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/13/2011 07:26 AM, Walter Bright wrote:
 On 12/12/2011 10:56 AM, Steven Schveighoffer wrote:
 If this ends up being viable, this is actually easier to explain than the
 current rules for inout. We just have to make sure the rules are sound
 before
 doing something like this.
I don't understand the point of doing this. Just make it const.
It does not work with const. (the fact that it currently does with DMD is a severe bug). Consider: void bad(const(int)** x, const(int)* y){ *x=y; } void main(){ immutable(int) i = 1; int* x; bad(&x, &i); // should error int** does not convert to const(int)** // currently: assert(x is &i); // mutable reference to immutable data *x = 2; // BOOM! } On the other hand: void main(){ immutable(int) i = 1; immutable(int)* x; bad(&x, &i); // fine, but the type checker cannot know that } Therefore it is sensible to allow void good(inout(int)** x, inout(int)* y){ *x=y; } void main(){ immutable(int) i = 1; int* x; good(&x, &i); // error, no value for inout makes this typecheck } On the other hand: void good(inout(int)** x, inout(int)* y){ *x=y; } void main(){ immutable(int) i = 1; immutable(int)* x; good(&x, &i); // this can now typecheck: use inout as immutable }
Dec 13 2011
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
I never thought of that. It's a sensible argument.
Dec 13 2011
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Is this bug and enhancement suggestion on bugzilla?
Dec 13 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/13/2011 08:06 PM, Walter Bright wrote:
 Is this bug and enhancement suggestion on bugzilla?
I think the bug has just been fixed by Kenji Hara: https://github.com/D-Programming-Language/dmd/pull/558 I have filed an enhancement: http://d.puremagic.com/issues/show_bug.cgi?id=7105
Dec 13 2011
parent Walter Bright <newshound2 digitalmars.com> writes:
On 12/13/2011 11:28 AM, Timon Gehr wrote:
 On 12/13/2011 08:06 PM, Walter Bright wrote:
 Is this bug and enhancement suggestion on bugzilla?
I think the bug has just been fixed by Kenji Hara: https://github.com/D-Programming-Language/dmd/pull/558 I have filed an enhancement: http://d.puremagic.com/issues/show_bug.cgi?id=7105
Thank you (and thanks to Kenji, too!)
Dec 13 2011
prev sibling next sibling parent reply kenji hara <k.hara.pg gmail.com> writes:
2011/12/13 Steven Schveighoffer <schveiguy yahoo.com>:
 Currently, the rules of inout say you cannot put inout on a parameter
 without putting it on the return type as well. =A0This makes sense, it gi=
ves
 the compiler one place to worry about whether implicit casting rules will
 properly work.

 But Timon Gehr brought up in another thread (started by Merdhad) that we =
can
 separate the resolution of inout from the applicability of the parameters=
.
 =A0Essentially, calling an inout function goes through two phases:

 1. determine what inout resolves to
 2. try and call the function.

 inout rules don't have to change regarding resolving inout's actual value=
.
 =A0In other words, if all inout places are matched as int, immutable, or
 inout, then inout resolves to that value. =A0Otherwise inout resolves to
 const.

 But what's different is (at least in my mind) the function call might not
 necessarily succeed even when the resolution is completed.

 Take for example:

 inout(int)* foo(inout(int)** a, inout(int)* b);

 Now, if we call foo like this:

 int a;
 int b;
 int* pa =3D &a;

 foo(&pa, &b);

 This means foo is called with (int **, int *). =A0This means inout resolv=
es to
 mutable (no qualifier). =A0*NOW* we try calling foo as if it were written=
:
 int *foo(int **a, int *b)

 And it can be done. =A0Not only that, but there is nothing bad that could=
be
 happening in foo that should be disallowed by the compiler.

 Now let's see what happens were we *could* do something bad:

 immutable(int) c;
 auto pc =3D &c;

 foo(&pc, &b);

 Now, foo is being called with (immutable(int)**, int *). =A0Inout resolve=
s to
 const (due to the mix of mutable and immutable). =A0*NOW* we try calling =
foo
 as if it were written:

 const(int)* foo(const(int)** a, const(int)* b);

 And it *FAILS*. =A0This is because you cannot implicitly convert
 immutable(int)** to const(int)** (well, at least it *shouldn't* compile, =
I'm
 not sure if it does currently).
Separating phases is good. Current implementation does not separate the two phases, and I agree that has a bug you appears. And, we need fixing issue 4251 and 5493 to fill the hole of const system. Today, I've posted a pull for it. https://github.com/D-Programming-Language/dmd/pull/558
 What this does is allow more possibilities for inout than we currently do=
.
 =A0Because inout is not now tied to returning something, we can create
 functions that have inout parameters but no inout return value.

 The example we were discussing on the other thread was this:

 void foo(ref inout(int)* a, inout(int)* b) { a =3D b;}

 This would compile as long as you call with const(int)* as the first
 parameter, or if both parameters matched in constancy (i.e. both were
 immutable, both were mutable, or both were inout).

 This gives us more cases where you don't have to repeat functions for the
 sake of handling different types of constancy, particularly when we have
 mutable references 2 levels deep. =A0I can't see anything technically wro=
ng
 with this, can anyone else?

 The one thing that I think still should be required is if you have inout =
on
 the return value, there *must* be an inout on a parameter. =A0Otherwise, =
the
 compiler has no idea what it should be (since we don't overload on return
 type in D).

 If this ends up being viable, this is actually easier to explain than the
 current rules for inout. =A0We just have to make sure the rules are sound
 before doing something like this.

 -Steve
Against an inout function that does not return inout type: - the number of inout parameters should be 2 more? - at least one parameter should have 'out' or 'ref' storage class as a *return parameter*? But I'm not sure these restrictions are necessarily required. Kenji Hara
Dec 13 2011
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/13/2011 09:41 AM, kenji hara wrote:
 2011/12/13 Steven Schveighoffer<schveiguy yahoo.com>:
 Currently, the rules of inout say you cannot put inout on a parameter
 without putting it on the return type as well.  This makes sense, it gives
 the compiler one place to worry about whether implicit casting rules will
 properly work.

 But Timon Gehr brought up in another thread (started by Merdhad) that we can
 separate the resolution of inout from the applicability of the parameters.
   Essentially, calling an inout function goes through two phases:

 1. determine what inout resolves to
 2. try and call the function.

 inout rules don't have to change regarding resolving inout's actual value.
   In other words, if all inout places are matched as int, immutable, or
 inout, then inout resolves to that value.  Otherwise inout resolves to
 const.

 But what's different is (at least in my mind) the function call might not
 necessarily succeed even when the resolution is completed.

 Take for example:

 inout(int)* foo(inout(int)** a, inout(int)* b);

 Now, if we call foo like this:

 int a;
 int b;
 int* pa =&a;

 foo(&pa,&b);

 This means foo is called with (int **, int *).  This means inout resolves to
 mutable (no qualifier).  *NOW* we try calling foo as if it were written:

 int *foo(int **a, int *b)

 And it can be done.  Not only that, but there is nothing bad that could be
 happening in foo that should be disallowed by the compiler.

 Now let's see what happens were we *could* do something bad:

 immutable(int) c;
 auto pc =&c;

 foo(&pc,&b);

 Now, foo is being called with (immutable(int)**, int *).  Inout resolves to
 const (due to the mix of mutable and immutable).  *NOW* we try calling foo
 as if it were written:

 const(int)* foo(const(int)** a, const(int)* b);

 And it *FAILS*.  This is because you cannot implicitly convert
 immutable(int)** to const(int)** (well, at least it *shouldn't* compile, I'm
 not sure if it does currently).
Separating phases is good. Current implementation does not separate the two phases, and I agree that has a bug you appears. And, we need fixing issue 4251 and 5493 to fill the hole of const system. Today, I've posted a pull for it. https://github.com/D-Programming-Language/dmd/pull/558
 What this does is allow more possibilities for inout than we currently do.
   Because inout is not now tied to returning something, we can create
 functions that have inout parameters but no inout return value.

 The example we were discussing on the other thread was this:

 void foo(ref inout(int)* a, inout(int)* b) { a = b;}

 This would compile as long as you call with const(int)* as the first
 parameter, or if both parameters matched in constancy (i.e. both were
 immutable, both were mutable, or both were inout).

 This gives us more cases where you don't have to repeat functions for the
 sake of handling different types of constancy, particularly when we have
 mutable references 2 levels deep.  I can't see anything technically wrong
 with this, can anyone else?

 The one thing that I think still should be required is if you have inout on
 the return value, there *must* be an inout on a parameter.  Otherwise, the
 compiler has no idea what it should be (since we don't overload on return
 type in D).

 If this ends up being viable, this is actually easier to explain than the
 current rules for inout.  We just have to make sure the rules are sound
 before doing something like this.

 -Steve
Against an inout function that does not return inout type: - the number of inout parameters should be 2 more?
If we required that, then IFTI would also have to replace inout with const in case there is only one of them. That potentially introduces counter-intuitive behaviour.
 - at least one parameter should have 'out' or 'ref' storage class as a
 *return parameter*?
Not necessarily.
 But I'm not sure these restrictions are necessarily required.

 Kenji Hara
I think we should indeed drop the restrictions, because they complicate the language for little gain.
Dec 13 2011
parent reply kenji hara <k.hara.pg gmail.com> writes:
2011/12/13 Timon Gehr <timon.gehr gmx.ch>:
 On 12/13/2011 09:41 AM, kenji hara wrote:
[snip]
 Against an inout function that does not return inout type:
 - the number of inout parameters should be 2 more?
If we required that, then IFTI would also have to replace inout with const in case there is only one of them. That potentially introduces counter-intuitive behaviour.
Maybe it is an enhancement filed as 6809.
 - at least one parameter should have 'out' or 'ref' storage class as a
 *return parameter*?
Not necessarily.
 But I'm not sure these restrictions are necessarily required.
I think we should indeed drop the restrictions, because they complicate the language for little gain.
Indeed. Kenji Hara
Dec 13 2011
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 13 Dec 2011 06:07:49 -0500, kenji hara <k.hara.pg gmail.com> wrote:

 2011/12/13 Timon Gehr <timon.gehr gmx.ch>:
 On 12/13/2011 09:41 AM, kenji hara wrote:
[snip]
 Against an inout function that does not return inout type:
 - the number of inout parameters should be 2 more?
If we required that, then IFTI would also have to replace inout with const in case there is only one of them. That potentially introduces counter-intuitive behaviour.
Maybe it is an enhancement filed as 6809.
I think if we loosen the restriction of inout so that it can be only one one parameter, then it becomes simply a new type of immutable. This makes moot that enhancement request I filed. I think you have suggested similar things in the past and I argued against it (I'm sorry for changing my mind!) The idea that inout simply resolves to something, and then the call falls under normal rules is really attractive. I even thought of another idea: If inout is solely on return value, then there is no way to resolve what inout should be matched to. But what if it simply becomes polyconst (can bind to any flavor of const)? Because there are no inout parameters, and global inout variables are illegal, it can be deduced that the value must be unique. I had previously suggested that only having inout on the return type should be illegal, so this would supersede that.
 - at least one parameter should have 'out' or 'ref' storage class as a
 *return parameter*?
Not necessarily.
 But I'm not sure these restrictions are necessarily required.
I think we should indeed drop the restrictions, because they complicate the language for little gain.
Indeed.
I agree with Timon here. -Steve
Dec 14 2011
prev sibling next sibling parent reply Don <nospam nospam.com> writes:
On 12.12.2011 19:56, Steven Schveighoffer wrote:
 Currently, the rules of inout say you cannot put inout on a parameter
 without putting it on the return type as well. This makes sense, it
 gives the compiler one place to worry about whether implicit casting
 rules will properly work.

 But Timon Gehr brought up in another thread (started by Merdhad) that we
 can separate the resolution of inout from the applicability of the
 parameters. Essentially, calling an inout function goes through two phases:

 1. determine what inout resolves to
 2. try and call the function.

 inout rules don't have to change regarding resolving inout's actual
 value. In other words, if all inout places are matched as int,
 immutable, or inout, then inout resolves to that value. Otherwise inout
 resolves to const.
 If this ends up being viable, this is actually easier to explain than
 the current rules for inout. We just have to make sure the rules are
 sound before doing something like this.
Looks as though 'inout' is about to become a misnomer...
Dec 27 2011
parent "Kagamin" <spam here.lot> writes:
On Tuesday, 27 December 2011 at 08:30:53 UTC, Don wrote:
 Looks as though 'inout' is about to become a misnomer...
Not the first to be so, so no problem.
Dec 27 2011
prev sibling next sibling parent kenji hara <k.hara.pg gmail.com> writes:
The patch
https://github.com/D-Programming-Language/dmd/pull/558
implements the two phases of inout resolution.

Until merging of it, I'll not post a patch to relax inout rule.

Kenji Hara

2011/12/13 Steven Schveighoffer <schveiguy yahoo.com>:
 Currently, the rules of inout say you cannot put inout on a parameter
 without putting it on the return type as well. =A0This makes sense, it gi=
ves
 the compiler one place to worry about whether implicit casting rules will
 properly work.

 But Timon Gehr brought up in another thread (started by Merdhad) that we =
can
 separate the resolution of inout from the applicability of the parameters=
.
 =A0Essentially, calling an inout function goes through two phases:

 1. determine what inout resolves to
 2. try and call the function.

 inout rules don't have to change regarding resolving inout's actual value=
.
 =A0In other words, if all inout places are matched as int, immutable, or
 inout, then inout resolves to that value. =A0Otherwise inout resolves to
 const.

 But what's different is (at least in my mind) the function call might not
 necessarily succeed even when the resolution is completed.

 Take for example:

 inout(int)* foo(inout(int)** a, inout(int)* b);

 Now, if we call foo like this:

 int a;
 int b;
 int* pa =3D &a;

 foo(&pa, &b);

 This means foo is called with (int **, int *). =A0This means inout resolv=
es to
 mutable (no qualifier). =A0*NOW* we try calling foo as if it were written=
:
 int *foo(int **a, int *b)

 And it can be done. =A0Not only that, but there is nothing bad that could=
be
 happening in foo that should be disallowed by the compiler.

 Now let's see what happens were we *could* do something bad:

 immutable(int) c;
 auto pc =3D &c;

 foo(&pc, &b);

 Now, foo is being called with (immutable(int)**, int *). =A0Inout resolve=
s to
 const (due to the mix of mutable and immutable). =A0*NOW* we try calling =
foo
 as if it were written:

 const(int)* foo(const(int)** a, const(int)* b);

 And it *FAILS*. =A0This is because you cannot implicitly convert
 immutable(int)** to const(int)** (well, at least it *shouldn't* compile, =
I'm
 not sure if it does currently).

 What this does is allow more possibilities for inout than we currently do=
.
 =A0Because inout is not now tied to returning something, we can create
 functions that have inout parameters but no inout return value.

 The example we were discussing on the other thread was this:

 void foo(ref inout(int)* a, inout(int)* b) { a =3D b;}

 This would compile as long as you call with const(int)* as the first
 parameter, or if both parameters matched in constancy (i.e. both were
 immutable, both were mutable, or both were inout).

 This gives us more cases where you don't have to repeat functions for the
 sake of handling different types of constancy, particularly when we have
 mutable references 2 levels deep. =A0I can't see anything technically wro=
ng
 with this, can anyone else?

 The one thing that I think still should be required is if you have inout =
on
 the return value, there *must* be an inout on a parameter. =A0Otherwise, =
the
 compiler has no idea what it should be (since we don't overload on return
 type in D).

 If this ends up being viable, this is actually easier to explain than the
 current rules for inout. =A0We just have to make sure the rules are sound
 before doing something like this.

 -Steve
Dec 27 2011
prev sibling parent kenji hara <k.hara.pg gmail.com> writes:
2011/12/13 Steven Schveighoffer <schveiguy yahoo.com>:
 immutable(int) c;
 auto pc =3D &c;

 foo(&pc, &b);

 Now, foo is being called with (immutable(int)**, int *). =A0Inout resolve=
s to
 const (due to the mix of mutable and immutable). =A0*NOW* we try calling =
foo
 as if it were written:

 const(int)* foo(const(int)** a, const(int)* b);

 And it *FAILS*. =A0This is because you cannot implicitly convert
 immutable(int)** to const(int)** (well, at least it *shouldn't* compile, =
I'm
 not sure if it does currently).
Today, with git head, this code might be rejected correctly. So relaxing inout rule is more reasonable now. Kenji Hara
Jan 18 2012