On Wednesday, 9 October 2013 at 04:31:55 UTC, Ali Çehreli wrote:
On 10/08/2013 03:12 PM, qznc wrote:

> On Monday, 7 October 2013 at 17:57:11 UTC, Ali Çehreli wrote:
>> To look at just one usage example, the following line
carries two
>> requirements:
>>
>>     auto a = T();
>>     immutable b = a;
>>
>> 1) b will be an immutable copy of a.
>>
>> 2) T will always be usable as in that fashion.
>>
>> If T appears on an API, it is the responibility of the user
to ensure
>> whether they are allowed to treat T in that way. Otherwise,
they risk
>> maintainability if the module decides to change T in any way
that fits
>> the module's needs. If they have not yet advertised that T
can be used
>> as immutable, it should not be.
>
> I do not agree with you, that the user has the responsibility.

I have difficulty agreeing with myself as well. :) However, the power of immutable makes me think so. Interestingly, once a user creates an immutable variable of a type, that type must support that use.

> Rather I
> think the provider of T has the responsibility to maintain
backwards
> compatibility.

Agreed but I don't know how. Here is challenge: Let's start with the following program:

// Library type
struct MyInt
{
    int i;
}

void main()
{
    // User code
    auto a = MyInt(1);
    immutable b = a;
}

Let's assume that the library adds a private dynamic array of ints to that type:

// Library type
struct MyInt
{
    int i;
    private int[] history;    // <-- Added
}

void main()
{
    // User code
    auto a = MyInt(1);
    immutable b = a;          // <-- Existing code breaks
}

Error: cannot implicitly convert expression (a) of type MyInt to immutable(MyInt)

Apparently, the library made a change that made its type non-immutable-able. :p What is missing in MyInt? How should it be defined instead of simply adding 'history'?


The example is great. I would say it like "Apparently the library made a change that added mutable aliasing and therefore broke the special logic the compiler applies to types without aliasing". I think the root cause is the language special casing structs with no mutable aliasing by allowing the "immutable b = a" to work in the first place. One step toward a solution would be the existence of a generalized dup. Then calls to "immutable b = a" with mutable aliasing could lower to a call to the generalized dup, causing a deep copy and therefore still allowing assignment from non-immutable to immutable. This has its own set of problems - like what if the library designer wants sharing and not deep copies (perhaps they employ copy on write semantics on their own).

What are other solutions or ways to prevent this jump from no aliasing to mutable aliasing breaking code? Maybe assume mutable aliasing on all your structs from the start, then those statements like "immutable b = a" won't appear in the first place.

struct T {
  ...
  version(unittest) int[] _justToAddMutableAliasing;
}

Now you know that assignment of "immutable b = a" will fail in unittests. But this is crazy and it would only help your users if they run unittests.


Technically, MyInt is in a breaking state because the user used it as immutable. One way to look at this situation is to observe that the user took some freedom of the library just by using its type as immutable.


I don't quite understand the terminology "using it's type as immutable". I think the breaking state comes from copying in general having two flavors - shallow and deep. Transitive deep copy always offers the option of immutable source and target. Shallow copy also offers the option of immutable source and target, as long as the type has no mutable aliasing, simply because a shallow copy is also a deep copy if no aliasing. And, shallow copy does not offer the option of immutable in the face of types with mutable aliasing. It is when types go from having no mutable aliasing to having mutable aliasing (and the reverse) that the rules of the compiler change the game.

> Unfortunately, there is no way for the provider to allow or
disallow
> immutable(T). Maybe there should be a UDA or something for
this?


One way might be to disallow copy and assignment (is this a D capability?) and provide pure factory that only returns immutable(T).

I think the language is lacking tools to support the use case above.


I think if the default assignment and copy constructor for structs with aliasing were automatic transitive deep copy this issue would disappear. Other issues would come up. Also, I'm pretty sure Walter is not a fan of this concept because he does not advocate copying any data in postblits.

Ali

Reply via email to