Re: Why do "const inout" and "const inout shared" exist?
On Tue, Jul 04, 2017 at 12:30:20AM -0700, Walter Bright via Digitalmars-d wrote: > On 7/3/2017 5:29 PM, H. S. Teoh via Digitalmars-d wrote: > > So what stops us from actually exploiting them in dmd's optimizer? > > Nothing. Pull requests are welcome! Sure, but it would be helpful if we knew what are some of the things about D semantics that the optimizer could take advantage of, that it currently doesn't yet. Do you have a (not necessarily complete) list somewhere? T -- Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. -- Brian W. Kernighan
Re: Why do "const inout" and "const inout shared" exist?
On 7/3/2017 5:29 PM, H. S. Teoh via Digitalmars-d wrote: So what stops us from actually exploiting them in dmd's optimizer? Nothing. Pull requests are welcome!
Re: Why do "const inout" and "const inout shared" exist?
On Mon, Jul 03, 2017 at 11:49:56AM -0700, Walter Bright via Digitalmars-d wrote: [...] > Keep in mind that today's optimizers are pretty much tuned to what > works for C++. While D's particular semantics offer opportunities for > optimizations, they are currently pretty much unexploited. So what stops us from actually exploiting them in dmd's optimizer? T -- Knowledge is that area of ignorance that we arrange and classify. -- Ambrose Bierce
Re: Why do "const inout" and "const inout shared" exist?
On Monday, 3 July 2017 at 15:48:26 UTC, Petar Kirov [ZombineDev] wrote: but can you explain exactly what part of Rust's type system provides extra benefits in terms of optimization over D's type system? In safe Rust, a reference to mutable data has to be unique [1]. So the optimizer could assume no aliasing as long as the mutable borrow lasts. [1] https://rustbyexample.com/scope/borrow/alias.html
Re: Why do "const inout" and "const inout shared" exist?
On 7/3/2017 8:48 AM, Petar Kirov [ZombineDev] wrote: Unlike C++, in D objects can't be shared across threads, unless they are marked as `shared` (modulo un-`@safe` code - like casts and `__gshared` - and compiler bugs). I.e. non-`shared` objects can't be mutated by more than one thread. Combined with `pure` functions, the guarantees provided by D's type system are quite useful: ``` void main() { int x = globalArray.foo(); } // module-level non-shared variables are thread-local int[] globalArray = [1, 2, 3, 4]; int foo(const(int)[] array) pure { // globalArray[0] = 42; doesn't compile // For all intents and purposes, the elements of `array` can // be viewed as immutable here and they are *not* aliased. // ... } Keep in mind that today's optimizers are pretty much tuned to what works for C++. While D's particular semantics offer opportunities for optimizations, they are currently pretty much unexploited.
Re: Why do "const inout" and "const inout shared" exist?
On 07/03/2017 04:01 PM, Jonathan M Davis via Digitalmars-d wrote: Fortunately, it looks like your assertion that shared may stand for inout is wrong, because this code fails to compile: class C { } inout(C) foo(inout(C) c) { return c; } void main() { shared a = new C; auto b = foo(a); } and gives the error test.d(13): Error: function test.foo (inout(C) c) is not callable using argument types (shared(C)) Thanks, Jonathan. You're absolutely right. Checking the spec, it even says: "Note: Shared types are not overlooked. Shared types cannot be matched with inout." [1] I think I've only tested with something like `foo(new shared C)`. That is accepted, but not because of inout. The compiler sees that the argument is unique and can't actually be shared with another thread yet, so it strips shared off. Sorry for the noise. Nothing to see here. [1] https://dlang.org/spec/function.html#inout-functions
Re: Why do "const inout" and "const inout shared" exist?
On Saturday, 1 July 2017 at 21:47:20 UTC, Andrei Alexandrescu wrote: Walter looked at http://erdani.com/conversions.svg and said actually "const inout" and "const inout shared" should not exist as distinct qualifier groups, leading to the simplified qualifier hierarcy in http://erdani.com/conversions-simplified.svg. Are we missing something? Is there a need for combining const and inout? Yes. inout == mutable, const or immutable const inout == const or immutable
Re: Why do "const inout" and "const inout shared" exist?
On Monday, 3 July 2017 at 15:48:26 UTC, Petar Kirov [ZombineDev] wrote: Unlike C++, in D objects can't be shared across threads, unless they are marked as `shared` (modulo un-`@safe` code - like I understand the intent, but since everything is "shared" in C++, unless you annotate variables with optimization-constraints, it doesn't make sense to compare C++ const to D const. One should either compare C++ const do D shared const, or compare C++ const-with-constraints with D const. int foo(const(int)[] array) pure { // globalArray[0] = 42; doesn't compile // For all intents and purposes, the elements of `array` can // be viewed as immutable here and they are *not* aliased. They aren't aliased because you only have one parameter with non-reference values. That's a rather narrow use-case… I've watched a presentation on Pony's type system and it is indeed interesting, Yes, e.g. Pony have "isolated" (e.g. no aliasing) with transitions to less constrained types. but can you explain exactly what part of Rust's type system provides extra benefits in terms of optimization over D's type system? As I understand it Rust's static analysis is designed to track aliasing using linear/affine typing for objects.
Re: Why do "const inout" and "const inout shared" exist?
On Monday, 3 July 2017 at 07:22:08 UTC, Ola Fosheim Grøstad wrote: On Monday, 3 July 2017 at 01:15:47 UTC, Walter Bright wrote: D const is different in that the compiler is allowed to generate code as if const was never cast to immutable. Such casts are also not allowed in @safe code. You probably meant mutable, but how does that bring any advantages if any other thread can mutate it? Unlike C++, in D objects can't be shared across threads, unless they are marked as `shared` (modulo un-`@safe` code - like casts and `__gshared` - and compiler bugs). I.e. non-`shared` objects can't be mutated by more than one thread. Combined with `pure` functions, the guarantees provided by D's type system are quite useful: ``` void main() { int x = globalArray.foo(); } // module-level non-shared variables are thread-local int[] globalArray = [1, 2, 3, 4]; int foo(const(int)[] array) pure { // globalArray[0] = 42; doesn't compile // For all intents and purposes, the elements of `array` can // be viewed as immutable here and they are *not* aliased. // ... } ``` Seems like you would want something closer to Pony's or Rust's type system to gain any real benefits in terms of optimization. I've watched a presentation on Pony's type system and it is indeed interesting, but can you explain exactly what part of Rust's type system provides extra benefits in terms of optimization over D's type system?
Re: Why do "const inout" and "const inout shared" exist?
On Sunday, July 02, 2017 01:16:13 ag0aep6g via Digitalmars-d wrote: > On 07/01/2017 11:47 PM, Andrei Alexandrescu wrote: > > Walter looked at http://erdani.com/conversions.svg and said actually > > "const inout" and "const inout shared" should not exist as distinct > > qualifier groups, leading to the simplified qualifier hierarcy in > > http://erdani.com/conversions-simplified.svg. > > This may be a stupid question, but those graphs say: inout -> const, but > inout may stand for shared and there's no shared -> const. > > How can inout -> const be allowed while shared -> const is forbidden? It sounds _very_ broken to me if inout can mean shared. After all, the compiler will treat various operations as illegal if a variable is marked shared which would be legal if it weren't. So, if you have an inout parameter accepting a shared argument, then you can break the protections that shared is supposed to be giving. Also, the compiler is supposed to be able to optimize based on the fact that a variable is thread-local and guaranteed not to be shared across threads, and if inout can mean shared, then a function using inout no longer has the guarantee that the data is thread-local and can't optimize based on that inforamtion. Fortunately, it looks like your assertion that shared may stand for inout is wrong, because this code fails to compile: class C { } inout(C) foo(inout(C) c) { return c; } void main() { shared a = new C; auto b = foo(a); } and gives the error test.d(13): Error: function test.foo (inout(C) c) is not callable using argument types (shared(C)) - Jonathan M Davis
Re: Why do "const inout" and "const inout shared" exist?
I also think it would be a good idea for the D crowd to be specific when they compare to C++ const, which should be compared to D's shared version of const and not to the local variety. C/C++ compilers provide other means to provide the optimizer with information about actual mutation/aliasing than const-types. So it doesn't really compare well. Besides, none of this makes a lot of sense until you have fully specified a sound memory model + type system for shared...
Re: Why do "const inout" and "const inout shared" exist?
On Monday, 3 July 2017 at 01:15:47 UTC, Walter Bright wrote: D const is different in that the compiler is allowed to generate code as if const was never cast to immutable. Such casts are also not allowed in @safe code. You probably meant mutable, but how does that bring any advantages if any other thread can mutate it? Seems like you would want something closer to Pony's or Rust's type system to gain any real benefits in terms of optimization.
Re: Why do "const inout" and "const inout shared" exist?
On 7/2/2017 5:46 AM, Shachar Shemesh wrote: Second, there are optimizations that can take place over "const" that cannot over "shared const" even assuming aliasing (such as if the compiler knows no other pointer was changed between two accesses). The last point is that assuming no pointer aliasing is a fairly common optimization to take in C and C++, simply because of the huge performance gains it provides. It is so huge that it is sometimes turned on by default despite the fact it changes language semantics. It would be a pity to block any potential to have it in D. Pointer aliasing is indeed a big deal for optimization. But that really has nothing to do with const in C++. The trouble with C++ const is you can legally cast it away in C++. Chandler Carruth (LLVM optimizer) told me that const was basically ignored by the optimizer because of that. D const is different in that the compiler is allowed to generate code as if const was never cast to immutable. Such casts are also not allowed in @safe code.
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 04:08 PM, Jack Stouffer wrote: On Sunday, 2 July 2017 at 18:49:29 UTC, Walter Bright wrote: Thank you. This explanation makes sense (given that applying const to immutable => immutable). Since seemly everyone is confused about it, this topic looks like a great subject for a blog post. We have top people on that (i.e. Timon!). Also, I just improved the spec: https://github.com/dlang/dlang.org/pull/1787. Andrei
Re: Why do "const inout" and "const inout shared" exist?
On Sunday, 2 July 2017 at 18:49:29 UTC, Walter Bright wrote: Thank you. This explanation makes sense (given that applying const to immutable => immutable). Since seemly everyone is confused about it, this topic looks like a great subject for a blog post.
Re: Why do "const inout" and "const inout shared" exist?
On 7/2/2017 6:33 AM, Timon Gehr wrote: The best way to think about inout is that it enables a function to have three distinct signatures: inout(int)[] foo(inout(int)[] arg); "expands" to: int[] foo(int[] arg); immutable(int)[] foo(immutable(int)[] arg); const(int)[] foo(const(int)[] arg); const inout /does not change this in any way/: const(inout(int))[] foo(inout(int)[] arg); expands to: const(int)[] foo(int[] arg); const(immutable(int))[] foo(immutable(int)[] arg); const(const(int))[] foo(const(int)[] arg); Thank you. This explanation makes sense (given that applying const to immutable => immutable).
Re: Why do "const inout" and "const inout shared" exist?
On 7/2/2017 2:34 AM, ag0aep6g wrote: On 07/02/2017 10:55 AM, Walter Bright wrote: If it is declared as: inout(char)[] foo(bool condition, inout(char)[] chars); your specific case will work as expected. Perhaps you meant: No, it doesn't. The function doesn't compile with that signature. inout(char)[] foo(bool condition, inout(char)[] chars) { if (!condition) return "condition failed!"; /* Error: cannot implicitly convert expression "condition failed!" of type string to inout(char)[] */ return chars; } You're right.
Re: Why do "const inout" and "const inout shared" exist?
On 7/2/2017 3:31 AM, Steven Schveighoffer wrote: On 7/2/17 5:15 AM, Walter Bright wrote: On 7/1/2017 3:12 PM, Timon Gehr wrote: It used to be the case that const(inout(T)) = const(T), Anyone want to run git bisect and see when this changed? This would help in figuring out the rationale. Here's the code to test with it: inout(const char)* foo(inout(const(char))* p) { pragma(msg, typeof(p)); static assert(is(typeof(p) == const(char)*)); return p; } https://issues.dlang.org/show_bug.cgi?id=6930 -Steve Ah, thanks to you and Vladimir! This is just what I wanted to see.
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 10:12, Sönke Ludwig wrote: So I'd probably still opt for simplifying the conversion hierarchy. It may indeed be a good idea to completely remove inout from the conversion hierarchy in the documentation: const const shared / \ / \ (unqualified) immutable shared The extended hierarchy can be presented in the inout documentation alongside its derivation from the standard conversion hierarchy.
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 10:00 AM, Timon Gehr wrote: On 02.07.2017 15:33, Timon Gehr wrote: On 02.07.2017 10:55, Walter Bright wrote: Neither I nor anyone here seems to understand its purpose. The opposite is true. I understand it, and you seem to understand it partially: Also, there are actually at least two other people in this thread who have demonstrated their understanding. So I count two confused people and three people who are not confused. Still not a great ratio, but I'll try to improve it with a blog post. That's fantastic, Timon. Thanks in advance! -- Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 15:33, Timon Gehr wrote: On 02.07.2017 10:55, Walter Bright wrote: Neither I nor anyone here seems to understand its purpose. The opposite is true. I understand it, and you seem to understand it partially: Also, there are actually at least two other people in this thread who have demonstrated their understanding. So I count two confused people and three people who are not confused. Still not a great ratio, but I'll try to improve it with a blog post.
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 15:48, Andrei Alexandrescu wrote: On 07/02/2017 09:39 AM, Timon Gehr wrote: In general, depending on the hardware memory model and the language memory model, data transfer from one thread to another requires cooperation from both parties. We don't want the thread that has the unshared data to need to participate in such a cooperation. Yes, there must be a handshake. Oh, I see your point. Let me illustrate: void fun(const shared int* p1) { auto a = atomicLoad(p1); ... } void gun() { int* p = new int; shared const int* p1 = p; // assume this passes spawn(, p); *p = 42; // should be a shared write, it's not } Is this what you're referring to? ... Yes, precisely. So it seems like the hierarchy in http://erdani.com/conversions.svg is minimal? Yes, I think there is no way to collapse it without bad consequences.
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 09:49 AM, Andrei Alexandrescu wrote: On 07/02/2017 09:48 AM, Andrei Alexandrescu wrote: *p = 42; // should be a shared write, it's not I meant: p = new int; // should be a shared write, it's not Dognabbit. No, I meant the previous one! The pointer itself is private to gun. -- Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 09:48 AM, Andrei Alexandrescu wrote: *p = 42; // should be a shared write, it's not I meant: p = new int; // should be a shared write, it's not Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 09:39 AM, Timon Gehr wrote: In general, depending on the hardware memory model and the language memory model, data transfer from one thread to another requires cooperation from both parties. We don't want the thread that has the unshared data to need to participate in such a cooperation. Yes, there must be a handshake. Oh, I see your point. Let me illustrate: void fun(const shared int* p1) { auto a = atomicLoad(p1); ... } void gun() { int* p = new int; shared const int* p1 = p; // assume this passes spawn(, p); *p = 42; // should be a shared write, it's not } Is this what you're referring to? So it seems like the hierarchy in http://erdani.com/conversions.svg is minimal? Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 14:31, Andrei Alexandrescu wrote: On 07/02/2017 02:49 AM, Shachar Shemesh wrote: On 02/07/17 02:08, Andrei Alexandrescu wrote: Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. I don't see how it can. They provide different guarantees. If anything, it should be the other way around. If you hold a pointer to const, you know the data will not change during the function's execution. No such guarantees for const shared. That supports the case for allowing the conversion. const: "You have a view to data that this thread may or may not change." const shared: "You have a view to data that any thread may or may not change." So the set of const is included in the set of const shared - texbook inclusion polymorphism. This is not the whole story. The best way to think about 'shared' is that it enables guarantees about data that does /not/ have the qualifier. So we need to look at what this change does to the meaning of data being unqualified: It changes from: unqualified: This data can be read and written exclusively by the current thread. to unqualified: This data can be read and written by this thread and read by any other thread. This disallows some program transformations that were allowed before unless reading from a const shared reference while a mutable thread is writing to it has an undefined result, in which case the change just removes /all/ guarantees from const shared just in order to place it at the top of the hierarchy. At this point you can just use void*.
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 15:29, Andrei Alexandrescu wrote: On 07/02/2017 09:07 AM, Timon Gehr wrote: On 02.07.2017 14:41, Andrei Alexandrescu wrote: On 07/01/2017 07:55 PM, Timon Gehr wrote: On 02.07.2017 01:08, Andrei Alexandrescu wrote: Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. That would be nice because we have "const shared" as the unique root of the qualifier hierarchy. This means that there can be aliasing between an unqualified reference and a const shared reference. Therefore, you can have code that mutates unshared data while another thread is reading it. What should the semantics of this be? The only potential issue is that it could restrict code operating on unshared data because it needs to play nice in some way to allow consistent data to be read by another thread. Well const shared exists already with the semantics of "you can't modify this and you must load it atomically to look at it". The question is whether the conversion from const to const shared can be allowed. -- Andrei If the data is not written atomically, how can it be loaded atomically? Then you atomically load parts of it, the point being that the matter is present in the type. I must not be understanding the question. -- Andrei In general, depending on the hardware memory model and the language memory model, data transfer from one thread to another requires cooperation from both parties. We don't want the thread that has the unshared data to need to participate in such a cooperation.
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 10:55, Walter Bright wrote: Neither I nor anyone here seems to understand its purpose. The opposite is true. I understand it, and you seem to understand it partially: On 02.07.2017 05:55, Walter Bright wrote: On 7/1/2017 6:22 PM, Stefan Koch wrote: I cannot think so a single time I ever used const inout. The math needs to work whether it is ever used or not, otherwise we wind up with bizarre, intractable absurdities. It's my fault as I should have noticed this getting slipped into the compiler. No, it is Kenji Hara's and my achievement. This is one of the things Kenji slipped into the language that actually should be there. If you have 'inout' there is no way around 'const inout'. The existence of it is likely a significant contributor to peoples' confusion about inout. The opposite is true. Many people are confused about inout and by extension about const inout. The best way to think about inout is that it enables a function to have three distinct signatures: inout(int)[] foo(inout(int)[] arg); "expands" to: int[] foo(int[] arg); immutable(int)[] foo(immutable(int)[] arg); const(int)[] foo(const(int)[] arg); const inout /does not change this in any way/: const(inout(int))[] foo(inout(int)[] arg); expands to: const(int)[] foo(int[] arg); const(immutable(int))[] foo(immutable(int)[] arg); const(const(int))[] foo(const(int)[] arg); It would be confusing if it worked any differently.
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 09:07 AM, Timon Gehr wrote: On 02.07.2017 14:41, Andrei Alexandrescu wrote: On 07/01/2017 07:55 PM, Timon Gehr wrote: On 02.07.2017 01:08, Andrei Alexandrescu wrote: Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. That would be nice because we have "const shared" as the unique root of the qualifier hierarchy. This means that there can be aliasing between an unqualified reference and a const shared reference. Therefore, you can have code that mutates unshared data while another thread is reading it. What should the semantics of this be? The only potential issue is that it could restrict code operating on unshared data because it needs to play nice in some way to allow consistent data to be read by another thread. Well const shared exists already with the semantics of "you can't modify this and you must load it atomically to look at it". The question is whether the conversion from const to const shared can be allowed. -- Andrei If the data is not written atomically, how can it be loaded atomically? Then you atomically load parts of it, the point being that the matter is present in the type. I must not be understanding the question. -- Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 14:41, Andrei Alexandrescu wrote: On 07/01/2017 07:55 PM, Timon Gehr wrote: On 02.07.2017 01:08, Andrei Alexandrescu wrote: Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. That would be nice because we have "const shared" as the unique root of the qualifier hierarchy. This means that there can be aliasing between an unqualified reference and a const shared reference. Therefore, you can have code that mutates unshared data while another thread is reading it. What should the semantics of this be? The only potential issue is that it could restrict code operating on unshared data because it needs to play nice in some way to allow consistent data to be read by another thread. Well const shared exists already with the semantics of "you can't modify this and you must load it atomically to look at it". The question is whether the conversion from const to const shared can be allowed. -- Andrei If the data is not written atomically, how can it be loaded atomically?
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 08:46 AM, Shachar Shemesh wrote: Second, there are optimizations that can take place over "const" that cannot over "shared const" even assuming aliasing (such as if the compiler knows no other pointer was changed between two accesses). Wouldn't that also fall within the realm of inclusion polymorphism? The last point is that assuming no pointer aliasing is a fairly common optimization to take in C and C++, simply because of the huge performance gains it provides. It is so huge that it is sometimes turned on by default despite the fact it changes language semantics. It would be a pity to block any potential to have it in D. Allowing the conversion does not preclude any optimization; after all there is no replacement of one with another. The conversion simply removes unnecessary restrictions. Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 02/07/17 15:31, Andrei Alexandrescu wrote: That supports the case for allowing the conversion. const: "You have a view to data that this thread may or may not change." const shared: "You have a view to data that any thread may or may not change." So the set of const is included in the set of const shared - texbook inclusion polymorphism. It does, with two (or is that three?) caveats. First, I have 0 (zero) experience with shared, so I don't know what barriers are used on access. If they're expensive, this combining of the type system might be a problem. Second, there are optimizations that can take place over "const" that cannot over "shared const" even assuming aliasing (such as if the compiler knows no other pointer was changed between two accesses). The last point is that assuming no pointer aliasing is a fairly common optimization to take in C and C++, simply because of the huge performance gains it provides. It is so huge that it is sometimes turned on by default despite the fact it changes language semantics. It would be a pity to block any potential to have it in D. Just my humble opinion. Shachar
Re: Why do "const inout" and "const inout shared" exist?
On 07/01/2017 07:55 PM, Timon Gehr wrote: On 02.07.2017 01:08, Andrei Alexandrescu wrote: Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. That would be nice because we have "const shared" as the unique root of the qualifier hierarchy. This means that there can be aliasing between an unqualified reference and a const shared reference. Therefore, you can have code that mutates unshared data while another thread is reading it. What should the semantics of this be? The only potential issue is that it could restrict code operating on unshared data because it needs to play nice in some way to allow consistent data to be read by another thread. Well const shared exists already with the semantics of "you can't modify this and you must load it atomically to look at it". The question is whether the conversion from const to const shared can be allowed. -- Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 02:49 AM, Shachar Shemesh wrote: On 02/07/17 02:08, Andrei Alexandrescu wrote: Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. I don't see how it can. They provide different guarantees. If anything, it should be the other way around. If you hold a pointer to const, you know the data will not change during the function's execution. No such guarantees for const shared. That supports the case for allowing the conversion. const: "You have a view to data that this thread may or may not change." const shared: "You have a view to data that any thread may or may not change." So the set of const is included in the set of const shared - texbook inclusion polymorphism. Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 7/2/17 5:15 AM, Walter Bright wrote: On 7/1/2017 3:12 PM, Timon Gehr wrote: It used to be the case that const(inout(T)) = const(T), Anyone want to run git bisect and see when this changed? This would help in figuring out the rationale. Here's the code to test with it: inout(const char)* foo(inout(const(char))* p) { pragma(msg, typeof(p)); static assert(is(typeof(p) == const(char)*)); return p; } https://issues.dlang.org/show_bug.cgi?id=6930 -Steve
Re: Why do "const inout" and "const inout shared" exist?
On Sunday, 2 July 2017 at 09:15:45 UTC, Walter Bright wrote: On 7/1/2017 3:12 PM, Timon Gehr wrote: It used to be the case that const(inout(T)) = const(T), Anyone want to run git bisect and see when this changed? This would help in figuring out the rationale. Here's the code to test with it: inout(const char)* foo(inout(const(char))* p) { pragma(msg, typeof(p)); static assert(is(typeof(p) == const(char)*)); return p; } This code doesn't compile in any version going as far back as 2.038, and before that one this code gets a syntax error. This line: static assert(is(const(inout(int)) == const(int))); stops working after: https://github.com/dlang/dmd/pull/2992 The same pull request changes the type of p in the above example from inout(char)* to inout(const(char))* (it was never const(char)*).
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 10:55 AM, Walter Bright wrote: If it is declared as: inout(char)[] foo(bool condition, inout(char)[] chars); your specific case will work as expected. Perhaps you meant: No, it doesn't. The function doesn't compile with that signature. inout(char)[] foo(bool condition, inout(char)[] chars) { if (!condition) return "condition failed!"; /* Error: cannot implicitly convert expression "condition failed!" of type string to inout(char)[] */ return chars; }
Re: Why do "const inout" and "const inout shared" exist?
On 07/02/2017 08:39 AM, H. S. Teoh via Digitalmars-d wrote: On Sun, Jul 02, 2017 at 06:49:39AM +0200, Timon Gehr via Digitalmars-d wrote: On 02.07.2017 06:45, Walter Bright wrote: On 7/1/2017 9:12 PM, Timon Gehr wrote: [...] const(inout(char))[] foo(bool condition, inout(char)[] chars){ if(!condition) return "condition failed!"; return chars; } [...] I think the example demonstrate the reason. It either returns the argument or an immutable global. If the argument is immutable, so is the return value, otherwise the return value is const. [...] In your example, inout makes no sense at all. It should be written as: const(char)[] foo(bool condition, const(char)[] chars) because if it returns the argument, then it's const(char)[], and if it returns an immutable global, then immutable(char)[] implicitly converts to const(char)[]. Timon's example makes perfect sense. I don't know if there's an actual need for const(inout), but it enables something you can't do with const: string s = foo(true, "bar"); Using inout doesn't make sense here because you cannot assume that the return value is mutable if the parameter is mutable -- the function may return immutable instead. You don't get a mutable result for a mutable argument, but you get an immutable result for an immutable argument. That's what const(inout) enables.
Re: Why do "const inout" and "const inout shared" exist?
On 7/1/2017 3:12 PM, Timon Gehr wrote: It used to be the case that const(inout(T)) = const(T), Anyone want to run git bisect and see when this changed? This would help in figuring out the rationale. Here's the code to test with it: inout(const char)* foo(inout(const(char))* p) { pragma(msg, typeof(p)); static assert(is(typeof(p) == const(char)*)); return p; }
Re: Why do "const inout" and "const inout shared" exist?
On 7/2/2017 1:12 AM, Sönke Ludwig wrote: The idea is to be able to write: string result = foo(true, "bar"); If it is declared as: inout(char)[] foo(bool condition, inout(char)[] chars); your specific case will work as expected. Perhaps you meant: char[] s; string result = foo(true, s); which will never work, even if const inout is a type, because the returns in the function body do NOT determine the return type's inout meaning. Only the type of the argument supplied to the inout parameter does that. However, in this case it can be also argued that supporting this specific case adds it's own complexity. The concept of const(inout(T)) definitely sounds like one of the harder ones to explain. So I'd probably still opt for simplifying the conversion hierarchy. Neither I nor anyone here seems to understand its purpose. The existence of it is likely a significant contributor to peoples' confusion about inout. It's my fault as I should have noticed this getting slipped into the compiler.
Re: Why do "const inout" and "const inout shared" exist?
Am 02.07.2017 um 08:39 schrieb H. S. Teoh via Digitalmars-d: In your example, inout makes no sense at all. It should be written as: const(char)[] foo(bool condition, const(char)[] chars) because if it returns the argument, then it's const(char)[], and if it returns an immutable global, then immutable(char)[] implicitly converts to const(char)[]. Using inout doesn't make sense here because you cannot assume that the return value is mutable if the parameter is mutable -- the function may return immutable instead. T The idea is to be able to write: string result = foo(true, "bar"); There are arguments for both sides. On one hand, inout is already a very specific hack in the language that often isn't applicable when it would be handy, and a restriction here would probably not change much. On the other hand it's always desirable to generalize a language feature as much as possible, to get the maximum out of the complexity investment. However, in this case it can be also argued that supporting this specific case adds it's own complexity. The concept of const(inout(T)) definitely sounds like one of the harder ones to explain. So I'd probably still opt for simplifying the conversion hierarchy.
Re: Why do "const inout" and "const inout shared" exist?
On 02/07/17 02:08, Andrei Alexandrescu wrote: Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. I don't see how it can. They provide different guarantees. If anything, it should be the other way around. If you hold a pointer to const, you know the data will not change during the function's execution. No such guarantees for const shared. On second thought, aliasing means that the first is not true either. I retract the above comment, sending it out on the off-chance someone can turn it into a useful insight :-) Shachar
Re: Why do "const inout" and "const inout shared" exist?
On Sun, Jul 02, 2017 at 06:49:39AM +0200, Timon Gehr via Digitalmars-d wrote: > On 02.07.2017 06:45, Walter Bright wrote: > > On 7/1/2017 9:12 PM, Timon Gehr wrote: > > > On 02.07.2017 05:13, Walter Bright wrote: > > > > On 7/1/2017 3:12 PM, Timon Gehr wrote: > > > > > const(const(T)) = const(T) > > > > > const(immutable(T)) = immutable(T) > > > > > const(inout(T)) = ? > > > > > > > > > > It used to be the case that const(inout(T)) = const(T), but > > > > > this is wrong, because if we replace 'inout' by 'immutable', > > > > > the result should be immutable(T), not const(T). Hence > > > > > const(inout(T)) cannot be reduced further. > > > > > > > > If const(inout(T)) is reduced to inout(T), it works. > > > > > > Counterexample: > > > > > > const(inout(char))[] foo(bool condition, inout(char)[] chars){ > > > if(!condition) return "condition failed!"; > > > return chars; > > > } > > > > > > Turn const(inout(char)) into inout(char) and the example no longer > > > compiles. (Nor should it.) > > > > I don't think that matters. There's no reason to write const(inout) > > for a return value. > > > I think the example demonstrate the reason. It either returns the > argument or an immutable global. If the argument is immutable, so is > the return value, otherwise the return value is const. Whoa, wait a second here. Isn't this completely over-engineering something that's actually quite simple?! The idea of inout is that a function's parameter is essentially const, and the function body treats it like const, and returns it as-is. Therefore, it is it safe for the *caller* to assume that if the argument is mutable, then the function's return value is also mutable. This last part is essentially the only reason inout exists; otherwise we would just write it as const. If the function may possibly return something other than the inout argument, then it is actually wrong to annotate the parameter as inout (e.g., pass in a mutable object, get an immutable back which the caller thinks is mutable). In such a case, the correct annotation is const, not inout. In your example, inout makes no sense at all. It should be written as: const(char)[] foo(bool condition, const(char)[] chars) because if it returns the argument, then it's const(char)[], and if it returns an immutable global, then immutable(char)[] implicitly converts to const(char)[]. Using inout doesn't make sense here because you cannot assume that the return value is mutable if the parameter is mutable -- the function may return immutable instead. T -- "Holy war is an oxymoron." -- Lazarus Long
Re: Why do "const inout" and "const inout shared" exist?
On 7/1/2017 9:49 PM, Timon Gehr wrote: On 02.07.2017 06:45, Walter Bright wrote: On 7/1/2017 9:12 PM, Timon Gehr wrote: On 02.07.2017 05:13, Walter Bright wrote: On 7/1/2017 3:12 PM, Timon Gehr wrote: const(const(T)) = const(T) const(immutable(T)) = immutable(T) const(inout(T)) = ? It used to be the case that const(inout(T)) = const(T), but this is wrong, because if we replace 'inout' by 'immutable', the result should be immutable(T), not const(T). Hence const(inout(T)) cannot be reduced further. If const(inout(T)) is reduced to inout(T), it works. Counterexample: const(inout(char))[] foo(bool condition, inout(char)[] chars){ if(!condition) return "condition failed!"; return chars; } Turn const(inout(char)) into inout(char) and the example no longer compiles. (Nor should it.) I don't think that matters. There's no reason to write const(inout) for a return value. I think the example demonstrate the reason. It either returns the argument or an immutable global. If the argument is immutable, so is the return value, otherwise the return value is const. The purpose of inout is to transmit the mutable/const/immutable attribute of the argument to the return type. If you want the return type to be const, mark it const, not const inout. I can't think of any useful purpose to const inout. The foo() example is bogus anyway. The return type inout calculus has nothing to do with the return expressions. It's not going to be immutable if the `return "condition failed";` is executed. It only depends on the attribute of the argument to the `chars` parameter.
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 06:45, Walter Bright wrote: On 7/1/2017 9:12 PM, Timon Gehr wrote: On 02.07.2017 05:13, Walter Bright wrote: On 7/1/2017 3:12 PM, Timon Gehr wrote: const(const(T)) = const(T) const(immutable(T)) = immutable(T) const(inout(T)) = ? It used to be the case that const(inout(T)) = const(T), but this is wrong, because if we replace 'inout' by 'immutable', the result should be immutable(T), not const(T). Hence const(inout(T)) cannot be reduced further. If const(inout(T)) is reduced to inout(T), it works. Counterexample: const(inout(char))[] foo(bool condition, inout(char)[] chars){ if(!condition) return "condition failed!"; return chars; } Turn const(inout(char)) into inout(char) and the example no longer compiles. (Nor should it.) I don't think that matters. There's no reason to write const(inout) for a return value. I think the example demonstrate the reason. It either returns the argument or an immutable global. If the argument is immutable, so is the return value, otherwise the return value is const.
Re: Why do "const inout" and "const inout shared" exist?
On 7/1/2017 9:12 PM, Timon Gehr wrote: On 02.07.2017 05:13, Walter Bright wrote: On 7/1/2017 3:12 PM, Timon Gehr wrote: const(const(T)) = const(T) const(immutable(T)) = immutable(T) const(inout(T)) = ? It used to be the case that const(inout(T)) = const(T), but this is wrong, because if we replace 'inout' by 'immutable', the result should be immutable(T), not const(T). Hence const(inout(T)) cannot be reduced further. If const(inout(T)) is reduced to inout(T), it works. Counterexample: const(inout(char))[] foo(bool condition, inout(char)[] chars){ if(!condition) return "condition failed!"; return chars; } Turn const(inout(char)) into inout(char) and the example no longer compiles. (Nor should it.) I don't think that matters. There's no reason to write const(inout) for a return value.
Re: Why do "const inout" and "const inout shared" exist?
On Sunday, 2 July 2017 at 03:55:37 UTC, Walter Bright wrote: On 7/1/2017 6:22 PM, Stefan Koch wrote: I cannot think so a single time I ever used const inout. The math needs to work whether it is ever used or not, otherwise we wind up with bizarre, intractable absurdities. I agree. I may add though that we already have a few absurdities ... inout itself for example :)
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 05:13, Walter Bright wrote: On 7/1/2017 3:12 PM, Timon Gehr wrote: const(const(T)) = const(T) const(immutable(T)) = immutable(T) const(inout(T)) = ? It used to be the case that const(inout(T)) = const(T), but this is wrong, because if we replace 'inout' by 'immutable', the result should be immutable(T), not const(T). Hence const(inout(T)) cannot be reduced further. If const(inout(T)) is reduced to inout(T), it works. Counterexample: const(inout(char))[] foo(bool condition, inout(char)[] chars){ if(!condition) return "condition failed!"; return chars; } Turn const(inout(char)) into inout(char) and the example no longer compiles. (Nor should it.)
Re: Why do "const inout" and "const inout shared" exist?
On 7/1/2017 6:22 PM, Stefan Koch wrote: I cannot think so a single time I ever used const inout. The math needs to work whether it is ever used or not, otherwise we wind up with bizarre, intractable absurdities.
Re: Why do "const inout" and "const inout shared" exist?
On Sunday, 2 July 2017 at 01:51:05 UTC, Timon Gehr wrote: On 02.07.2017 03:22, Stefan Koch wrote: ... I cannot think so a single time I ever used const inout. I'll boldly claim that this is true mostly because you have never used 'inout'. ;) affirmative
Re: Why do "const inout" and "const inout shared" exist?
On 7/1/2017 3:12 PM, Timon Gehr wrote: const(const(T)) = const(T) const(immutable(T)) = immutable(T) const(inout(T)) = ? It used to be the case that const(inout(T)) = const(T), but this is wrong, because if we replace 'inout' by 'immutable', the result should be immutable(T), not const(T). Hence const(inout(T)) cannot be reduced further. If const(inout(T)) is reduced to inout(T), it works.
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 03:22, Stefan Koch wrote: ... I cannot think so a single time I ever used const inout. I'll boldly claim that this is true mostly because you have never used 'inout'. ;)
Re: Why do "const inout" and "const inout shared" exist?
On Saturday, 1 July 2017 at 23:08:40 UTC, Andrei Alexandrescu wrote: On 07/01/2017 06:12 PM, Timon Gehr wrote: On 01.07.2017 23:47, Andrei Alexandrescu wrote: Walter looked at http://erdani.com/conversions.svg and said actually "const inout" and "const inout shared" should not exist as distinct qualifier groups, leading to the simplified qualifier hierarcy in http://erdani.com/conversions-simplified.svg. Are we missing something? I don't think so. Is there a need for combining const and inout? ... In DMD's implementation, yes. (Combinations of qualifiers are represented as integers instead of nested AST nodes.) const(const(T)) = const(T) const(immutable(T)) = immutable(T) const(inout(T)) = ? It used to be the case that const(inout(T)) = const(T), but this is wrong, because if we replace 'inout' by 'immutable', the result should be immutable(T), not const(T). Hence const(inout(T)) cannot be reduced further. The simplified hierarchy is enough though. The more complex one can be derived from it. Since S -> const(S) for all S, it directly follows that inout(T) -> const(inout(T)) for all T (we can choose S=inout(T)). Thanks! Well I do want to have a hierarchy with all possible qualifier combinations for utmost clarity. Only the combinations listed are valid, e.g. there's no "immutable inout" or whatever. Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. That would be nice because we have "const shared" as the unique root of the qualifier hierarchy. Andrei I cannot think so a single time I ever used const inout.
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 01:08, Andrei Alexandrescu wrote: Thanks! Well I do want to have a hierarchy with all possible qualifier combinations for utmost clarity. Only the combinations listed are valid, e.g. there's no "immutable inout" or whatever. ... immutable(inout(T)) is valid syntax, but this type is equal to immutable(T). Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. That would be nice because we have "const shared" as the unique root of the qualifier hierarchy. This means that there can be aliasing between an unqualified reference and a const shared reference. Therefore, you can have code that mutates unshared data while another thread is reading it. What should the semantics of this be? The only potential issue is that it could restrict code operating on unshared data because it needs to play nice in some way to allow consistent data to be read by another thread.
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 00:26, Stefan Koch wrote: On Saturday, 1 July 2017 at 22:16:12 UTC, Timon Gehr wrote: struct S{ int x; ref inout(int) foo()inout{ return x; } } void main(){ S s; s.foo()++; // ok! const(S) t = s; import std.stdio; writeln(t.foo()); // t.foo()++; // error } Oh damn. I was not aware that it could behave non-constly. since when does it do that ? Since the beginning. :) The point of inout is in essence to allow writing an identity function that can operate on data of any mutability qualifier with support for virtual calls and without duplicating code in the binary.
Re: Why do "const inout" and "const inout shared" exist?
On 07/01/2017 11:47 PM, Andrei Alexandrescu wrote: Walter looked at http://erdani.com/conversions.svg and said actually "const inout" and "const inout shared" should not exist as distinct qualifier groups, leading to the simplified qualifier hierarcy in http://erdani.com/conversions-simplified.svg. This may be a stupid question, but those graphs say: inout -> const, but inout may stand for shared and there's no shared -> const. How can inout -> const be allowed while shared -> const is forbidden?
Re: Why do "const inout" and "const inout shared" exist?
On 07/01/2017 06:12 PM, Timon Gehr wrote: On 01.07.2017 23:47, Andrei Alexandrescu wrote: Walter looked at http://erdani.com/conversions.svg and said actually "const inout" and "const inout shared" should not exist as distinct qualifier groups, leading to the simplified qualifier hierarcy in http://erdani.com/conversions-simplified.svg. Are we missing something? I don't think so. Is there a need for combining const and inout? ... In DMD's implementation, yes. (Combinations of qualifiers are represented as integers instead of nested AST nodes.) const(const(T)) = const(T) const(immutable(T)) = immutable(T) const(inout(T)) = ? It used to be the case that const(inout(T)) = const(T), but this is wrong, because if we replace 'inout' by 'immutable', the result should be immutable(T), not const(T). Hence const(inout(T)) cannot be reduced further. The simplified hierarchy is enough though. The more complex one can be derived from it. Since S -> const(S) for all S, it directly follows that inout(T) -> const(inout(T)) for all T (we can choose S=inout(T)). Thanks! Well I do want to have a hierarchy with all possible qualifier combinations for utmost clarity. Only the combinations listed are valid, e.g. there's no "immutable inout" or whatever. Vaguely related question: should "const" convert implicitly to "const shared"? The intuition is that the latter offers even less guarantees than the former so it's the more general type. See http://erdani.com/conversions3.svg. That would be nice because we have "const shared" as the unique root of the qualifier hierarchy. Andrei
Re: Why do "const inout" and "const inout shared" exist?
On 07/01/2017 02:47 PM, Andrei Alexandrescu wrote: > the simplified qualifier hierarcy in > http://erdani.com/conversions-simplified.svg. Can't we simplify it by cutting it in half and adding that immutable is implicitly shared? 1) unqualified -> const 2) inout -> const 3) immutable -> const 4) shared is implicit only for immutable > Is there a need for combining const and inout? I think there is a reason but only one person knows. :) Ali
Re: Why do "const inout" and "const inout shared" exist?
On Saturday, 1 July 2017 at 22:16:12 UTC, Timon Gehr wrote: struct S{ int x; ref inout(int) foo()inout{ return x; } } void main(){ S s; s.foo()++; // ok! const(S) t = s; import std.stdio; writeln(t.foo()); // t.foo()++; // error } Oh damn. I was not aware that it could behave non-constly. since when does it do that ?
Re: Why do "const inout" and "const inout shared" exist?
On 02.07.2017 00:10, Stefan Koch wrote: On Saturday, 1 July 2017 at 21:47:20 UTC, Andrei Alexandrescu wrote: Walter looked at http://erdani.com/conversions.svg and said actually "const inout" and "const inout shared" should not exist as distinct qualifier groups, leading to the simplified qualifier hierarcy in http://erdani.com/conversions-simplified.svg. Are we missing something? Is there a need for combining const and inout? Thanks, Andrei inout is bascially the same as const for all parctical purposes. struct S{ int x; ref inout(int) foo()inout{ return x; } } void main(){ S s; s.foo()++; // ok! const(S) t = s; import std.stdio; writeln(t.foo()); // t.foo()++; // error }
Re: Why do "const inout" and "const inout shared" exist?
On 01.07.2017 23:47, Andrei Alexandrescu wrote: Walter looked at http://erdani.com/conversions.svg and said actually "const inout" and "const inout shared" should not exist as distinct qualifier groups, leading to the simplified qualifier hierarcy in http://erdani.com/conversions-simplified.svg. Are we missing something? I don't think so. Is there a need for combining const and inout? ... In DMD's implementation, yes. (Combinations of qualifiers are represented as integers instead of nested AST nodes.) const(const(T)) = const(T) const(immutable(T)) = immutable(T) const(inout(T)) = ? It used to be the case that const(inout(T)) = const(T), but this is wrong, because if we replace 'inout' by 'immutable', the result should be immutable(T), not const(T). Hence const(inout(T)) cannot be reduced further. The simplified hierarchy is enough though. The more complex one can be derived from it. Since S -> const(S) for all S, it directly follows that inout(T) -> const(inout(T)) for all T (we can choose S=inout(T)).
Re: Why do "const inout" and "const inout shared" exist?
On Saturday, 1 July 2017 at 21:47:20 UTC, Andrei Alexandrescu wrote: Walter looked at http://erdani.com/conversions.svg and said actually "const inout" and "const inout shared" should not exist as distinct qualifier groups, leading to the simplified qualifier hierarcy in http://erdani.com/conversions-simplified.svg. Are we missing something? Is there a need for combining const and inout? Thanks, Andrei inout is bascially the same as const for all parctical purposes.