May 01, 2014
Walter Bright:

> Actually, I think inference of the ability to implicitly and safely convert from mutable to immutable or shared to be very powerful. I also think it is superior than requiring the user to add ever more annotations, which some research (and my experience) shows that users are reluctant to do.

I agree the people are reluctant to add annotations, but I think it's also depends on the return of investment of the specific annotation. I am annotating all my code carefully with "pure", but the ROI of all those purity annotations is not great.

And I agree the piling of special cases is a not clean design. It's now simpler to ask the compiler if a certain assignment to immutable is allowed or not. Because I can't remember the supported cases.

So I suggest a more principled and visible approach at the problem of ownership and uniqueness.

Bye,
bearophile
May 01, 2014
Andrei Alexandrescu:

> We're considering deprecating ~this() for classes in the future.

Such changes could happen in parallel to the (planned?) removal of some of the methods of Object.

Bye,
bearophile
May 01, 2014
On 4/30/2014 11:57 PM, bearophile wrote:
> I agree the people are reluctant to add annotations, but I think it's also
> depends on the return of investment of the specific annotation. I am annotating
> all my code carefully with "pure", but the ROI of all those purity annotations
> is not great.

That's why I want to move more towards attribute inference.


> And I agree the piling of special cases is a not clean design. It's now simpler
> to ask the compiler if a certain assignment to immutable is allowed or not.
> Because I can't remember the supported cases.
> So I suggest a more principled and visible approach at the problem of ownership
> and uniqueness.

The "supported cases" are not random special case hacks. They are based on a solid principle - if the expression represents a unique reference to an object, then it is implicitly convertible to immutable or shared.

For example,

    new int;

Formerly, that created a pointer to a mutable int object. It could not be implicitly cast to a pointer to an immutable int. But we know that operator new returns the only pointer to what it created, so it should be convertible. This knowledge was simply added to the compiler.

I don't see anything unprincipled about that, or hard to understand, or hackish, or whatever.




May 01, 2014
On 05/01/2014 12:48 AM, deadalnix wrote:
> On Wednesday, 30 April 2014 at 22:24:29 UTC, Timon Gehr wrote:
>> However, then, whether to do const(S!T) => S!(const(T)) or const(S!T)
>> => S!(TailConst!T) should maybe be specified on a per-parameter basis,
>> because this is in general not easy to figure out for the compiler.
>
> That is the whole problem :D

Well, actually, just check whether there is a field that would need to change type, of the exact type of some parameter. If so, try to do the second thing for that parameter, otherwise try to do the first.
May 01, 2014
On Wed, 30 Apr 2014 13:21:33 -0700
Andrei Alexandrescu via Digitalmars-d <digitalmars-d@puremagic.com>
wrote:

> Walter and I have had a long chat in which we figured our current offering of abstractions could be improved. Here are some thoughts. There's a lot of work ahead of us on that and I wanted to make sure we're getting full community buy-in and backup.
> 
> First off, we're considering eliminating destructor calls from within the GC entirely. It makes for a faster and better GC, but the real reason here is that destructors are philosophically bankrupt in a GC environment. I think there's no need to argue that in this community. The GC never guarantees calling destructors even today, so this decision would be just a point in the definition space (albeit an extreme one).

I really don't like the fact that struct destructors are not called by the GC, and if anything, I'd be inclined to argue for finding a way to guarantee that they get run rather than guaranteeing that they never get run. It's just far too easy to have a struct expect that its destructor will be run and then have issues when it's not run. But it would be better to define struct destructors as never getting run rather than having it be undefined as it is now.

> We're considering deprecating ~this() for classes in the future.

While it's not good to rely on finalizers, they're good to have as backup if the appropriate cleanup function doesn't get called like it's supposed to. They're not as critical as they'd be in Java, since we have structs, but I'd be disinclined to remove finalizers from D without a really good reason.

> Also, we're considering a revamp of built-in slices, as follows. Slices of types without destructors stay as they are.
> 
> Slices T[] of structs with destructors shall be silently lowered into RCSlice!T, defined inside object.d. That type would occupy THREE words, one of which being a pointer to a reference count. That type would redefine all slice primitives to update the reference count accordingly.
> 
> RCSlice!T will not convert implicitly to void[]. Explicit cast(void[]) will be allowed, and will ignore the reference count (so if a void[] extracted from a T[] via a cast outlives all slices, dangling pointers will ensue).
> 
> I foresee any number of theoretical and practical issues with this approach. Let's discuss some of them here.

I'm really going to have to think about this one. It's such a radical change that I really don't know what to think about it. It will be interesting to see what others have to say about.

- Jonathan M Davis
May 01, 2014
On Wed, 30 Apr 2014 14:00:31 -0700
Andrei Alexandrescu via Digitalmars-d <digitalmars-d@puremagic.com>
wrote:

> On 4/30/14, 1:57 PM, Timon Gehr wrote:
> > On 04/30/2014 10:45 PM, Andrei Alexandrescu wrote:
> >>>
> >>> An extreme one indeed, it would break a lot of my code. Every D project I wrote that does networking manages memory using a class that resides on the managed heap, but holds the actual wrapped data in the unmanaged heap.
> >>
> >> So should I take it those classes all have destructors? -- Andrei
> >
> > (Yes, those destructors free the unmanaged memory.)
> 
> Thanks... that would need to change :o). -- Andrei

And it doesn't even work now, because it's not guaranteed that finalizers get run. And IIRC, based on some of the discussions at dconf last year, dealing with the GC and unloading shared libraries would probably make the situation even worse.

But not being able to rely on finalizers running does put us in a bit of a pickle, because it basically means that any case where you need a finalizer, you should probably be using reference counting rather than the GC. That would tend to mean that either classes are going to need to be wrapped in a struct that reference-counts them and/or they're going to need to be allocated with a custom allocator rather than the GC.

This problem makes me think of C#'s using blocks where the object is created at the beginning of the using block, and it's dispose method is called when that block is exited (it may also be collected then, but I don't remember). I don't think that that's quite what we want, since there are plenty of cases where you want to pass a class around, so some kind of reference counting would be better, but we probably should consider having some kind of standard function similar to dispose so that there's a standard method to call when a class needs to be cleaned up. And that could tie into a struct in Phobos that we would have to do the reference counting by wrapping the class.

- Jonathan M Davis
May 01, 2014
On Wednesday, 30 April 2014 at 20:21:33 UTC, Andrei Alexandrescu wrote:
>
> First off, we're considering eliminating destructor calls from within the GC entirely. It makes for a faster and better GC, but the real reason here is that destructors are philosophically bankrupt in a GC environment. I think there's no need to argue that in this community. The GC never guarantees calling destructors even today, so this decision would be just a point in the definition space (albeit an extreme one).
>
> That means classes that need cleanup (either directly or by having fields that are structs with destructors) would need to garner that by other means, such as reference counting or manual. We're considering deprecating ~this() for classes in the future.
>

I'm all for it, this will break code that was not correct in the first place, and was working by accident.

Also, being called by the GC from any thread prevent many kinds of cleanup, such as those with a thread-wise bound context (CUDA, OpenGL, etc...).

May 01, 2014
On Wednesday, 30 April 2014 at 22:48:28 UTC, Andrei Alexandrescu wrote:
> On 4/30/14, 3:08 PM, John Colvin wrote:
>> On Wednesday, 30 April 2014 at 21:51:17 UTC, Andrei Alexandrescu wrote:
>>> On 4/30/14, 2:47 PM, John Colvin wrote:
>>>> On Wednesday, 30 April 2014 at 20:57:26 UTC, Andrei Alexandrescu wrote:
>>>>>> Finally, immutable is sharable accross thread. That mean, even if we
>>>>>> bypass the type system, that RC must be atomic for immutable.
>>>>>> As they
>>>>>> convert automatically for co,st, that mean that all const code will be
>>>>>> full of atomic increment/decrement. They are bad for the CPU, and
>>>>>> cannot
>>>>>> be optimized away.
>>>>>
>>>>> Good point. I see that as a problem, albeit a solvable one.
>>>>
>>>> How? Having lock; instructions implicitly appearing in normal looking
>>>> slice code is unacceptable.
>>>
>>> I'm thinking e.g. non-interlocked refcounts go like 1, 3, 5, ... and
>>> interlocked refcounts go like 2, 4, 6, ...
>>>
>>> Then you do an unprotected read of the refcount. If it's odd, then
>>> it's impossible to having originated as an interlocked one. So proceed
>>> with simple increment. If it's even, do an interlocked increment.
>>>
>>>
>>> Andrei
>>
>> I don't think I fully understand.
>>
>> Either all RC changes for a given type need to be atomic or none do, and
>> that information is given by the type (everything that is
>> immutable/const/shared). I don't see any feasible way of escaping this,
>> or any advantage to a runtime convention like the odd/even trick above.
>
> An object starting as shared or immutable would always need to be atomically refcounted. That information is statically known. For those we'd initialize the refcount to 2.
>
> An object starting as regular mutable would always be refcounted non-atomically. That's also known statically. So that constructor initializes the refcount to 1.
>
> Then a const object would dynamically choose the approach to refcounting depending on the counter's evenness.
>
>
> Andrei

Ah ok, that makes sense.
May 01, 2014
On Wednesday, 30 April 2014 at 23:19:18 UTC, H. S. Teoh via Digitalmars-d wrote:
> On Wed, Apr 30, 2014 at 03:55:38PM -0700, Andrei Alexandrescu via Digitalmars-d wrote:
>> On 4/30/14, 3:47 PM, H. S. Teoh via Digitalmars-d wrote:
> [...]
>> >I don't like the sound of that. I haven't found myself in a place
>> >where I needed to do something like this, but if I had to, I'd be
>> >very unhappy if struct dtors only work when they're not class
>> >members. Can we make them always work, and if necessary prohibit
>> >using them as class members?
>> 
>> Then we're back to effectively class destructors. I think we've
>> gathered quite a bit of evidence there are pernicious issues
>> associated with them. -- Andrei
> [...]
>
> How so? If we prohibit structs with dtors from being class members,
> then it could work.

Why would we prohibit structs with destructors from being class members? That don't make sense to me.

"A class is allowed to have a destructor" <=> "A class can have members with destructors".

So we either kill off class destructor entirelly (which would mean no members with destructors) (But that seems like a bad idea), or we keep both.

Or did I miss something in the argument?
May 01, 2014
On Thursday, 1 May 2014 at 01:04:08 UTC, Steven Schveighoffer wrote:
>> That means classes that need cleanup (either directly or by having fields that are structs with destructors) would need to garner that by other means, such as reference counting or manual. We're considering deprecating ~this() for classes in the future.
>
> So essentially, any class with a dtor needs reference counting? How does one clean up a cycle of them?

Yeah, what he said. This has me very worried. Making cycles is actually incredibly easy. Any "inteligently" implemented "node-based" data structure, implemented with classes, such as a linked list, a tree or a graph, is virtually guaranteed to have a cycle somewhere...

*Or*

is the idea that reference counting only works for finalization, but the memory is still managed by the GC? In that case, the destructors would leak, but the memory still be released?

That's still bad, of course, but not "as" bad...