Bug 17578 – Propagate the common qualifier of fields to the containing type

Status
NEW
Severity
enhancement
Priority
P4
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2017-06-30T21:30:28Z
Last change time
2024-12-13T18:52:58Z
Assigned to
No Owner
Creator
Andrei Alexandrescu
Moved to GitHub: dmd#17802 →

Comments

Comment #0 by andrei — 2017-06-30T21:30:28Z
This is a language enhancement that simplifies the use of qualifiers and eliminates the necessity of boilerplate code. Consider: struct Widget { // state { immutable int a; immutable double b; // } state void fun() { ... } void gun() immutable { ... } } The important tidbit is that all fields have the immutable qualifier. Now fun can only be called against a mutable Widget, whereas gun can only be called against an immutable Widget. However, note that the difference between Widget and immutable(Widget) is artificial because fun and gun have the same exact constraints. Even though type Widget is de jure "mutable", no mutation can be done within it. This proposes that Widget and immutable(Widget) are the same type, i.e. the artificial distinction between the two types is removed. That means the two may be use interchangeably, same as e.g. applying immutable(T) to a template parameter T that already was immutable yields T. This enhancement relaxes constraints on qualified functions (both free and methods) and eliminate the need for copies, casts, and boilerplate to vacuously adjust qualifiers. There is generalization to all qualifiers and also to combinations of qualifiers. Consider: struct Midget { // state { immutable int a; const double b; // } state ... } In this case, the common qualifier of const and immutable is const, so Midget and const(Midget) are the same type. The rules for figuring the common qualifier of all fields in an object are derived from https://dlang.org/spec/const3.html#implicit_conversions.
Comment #1 by issues.dlang — 2017-06-30T23:19:47Z
This seems reasonable, though I expect that it would be of limited usefulness. Certanily, I normally recommend to people that they not make any members in a struct const or immutable if they can help it, because it gets in the way of basic operations like copying and assignment (e.g. not being able to assign to a mutable struct is the sort of thing that causes confusion, but that's exactly what happens when you have a const member variable). So, I doubt that this change would affect much, but I see no conceptual reason why it would be a problem, and given that you've created this enhancement, I expect that you've found at least one use case where it makes sense.
Comment #2 by aliloko — 2017-07-01T15:56:07Z
Does this mean having shared field will turn the entire containing struct/class into shared itself? This would propagate to the whole application I fear.
Comment #3 by timon.gehr — 2017-07-01T20:25:57Z
Refined design from discussion with Walter and Andrei (whenever I say 'shared', the same applies for the other qualifiers): Rationale: - Implicitly changing semantics based on (possibly private) field types breaks encapsulation: adding an unshared private member can break client code. This is bad. - There is already the possibility to declare a struct or class as shared, which currently just makes all of its members shared. For such types, it is documented on the outside that all members are shared, so it is fair game to change behaviour based on those external qualifiers. Proposal: - For a shared struct S, S and shared S should be the same type. This type behaves the same as the current type shared(C). For a shared class C, C and shared(C) should be different but interconvertible types (reflecting the difference between shared(T)* and shared(T*)). Methods of a shared class C (which are shared methods) can be called on receivers of type C in addition to receivers of type shared(C). Methods of a shared class can override/implement unshared methods of the parent class/interfaces. If both shared and unshared overloads are present in the parent class/interfaces, it overrides/implements /both/ of them. abstract class ICounter{ abstract int get(); abstract void increment(); } shared class SharedCounter: ICounter{ int x; // shared int get(){ // shared, but implements unshared method return x; } void increment(){ atomicIncrement(x); } } class UnsharedCounter: ICounter{ int x; int get(){ return x; } void increment(){ x++; } } interface IWidget{ ICounter getCounter(); } shared class SharedWidget: IWidget{ SharedCounter c; // note: shared(SharedCounter)==SharedCounter SharedCounter getCounter(){ return c; } } class UnsharedWidget: IWidget{ UnsharedCounter c; UnsharedCounter getCounter(){ return c; } } - Justification of soundness: Because all members of a shared class C are shared, conversion between C and shared(C) is sound (this is like conversion between shared(T)* and shared(T*).) In particular, it is sound for shared methods of C to override/implement unshared methods, because this just amounts to a conversion C->shared(C) when the parent method is called.
Comment #4 by timon.gehr — 2017-07-01T20:27:24Z
Typo: (In reply to timon.gehr from comment #3) > ... > - For a shared struct S, S and shared S should be the same type. This type > behaves the same as the current type shared(C). Typo: should say shared(S) instead of shared S and shared(C).
Comment #5 by timon.gehr — 2017-07-01T20:37:10Z
(In reply to timon.gehr from comment #3) > ... > > shared class SharedWidget: IWidget{ > SharedCounter c; // note: shared(SharedCounter)==SharedCounter Stale comment. Actually shared(SharedCounter)!=SharedCounter but they convert to each other.
Comment #6 by andrei — 2017-07-01T21:02:20Z
Love where this is going, thanks Timon!
Comment #7 by robert.schadek — 2024-12-13T18:52:58Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/17802 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB