November 14, 2021
On 14.11.21 22:50, Elronnd wrote:
> On Sunday, 14 November 2021 at 18:24:00 UTC, Timon Gehr wrote:
>>> 1. ref counted objects are mutable
>> Works, but you may have to extend it to "anything that's not allocated with the GC is mutable".
> 
> Static data can be immutable too.
> ...

True, and you can also just leak. I did mention that in my other post, but I forgot to add it here, thanks!

> 
>>> 2. ref counted objects are outside of the type system
>> For this to work, there still needs to be a careful definition what @trusted functions are allowed to do with data of certain qualification. I think adding some type system support that is strictly @system, like __mutable variables and functions is better than having no language support at all, because otherwise, in order to support reference counting, you may end up penalizing code that does not actually use it.
> 
> I assumed that 'outside of the type system' meant 'language-level support for reference counting'; a a type with opaque representation, like a hash table.
> ...

Yes, I guess that's another way to go about it, but I think less language magic is better. Especially with reference counting, you may want to have detailed control over the allocator and/or the strategy for storing reference counts.

> 
>>> PS. I suspect that ref counted shared mutable objects are an indicator of insanity.
>> Possibly, though it's hard to know everyone's use cases in advance.
> 
> GC really makes more sense for multithreaded programs.  Travis downs mentions at https://travisdowns.github.io/blog/2020/07/06/concurrency-costs.html.  IMO if you are sharing mutable data between threads, and you can't stand gc for some reason, then it's perfectly legitimate to make you manage the lifetime yourself.

Also fair.

However, I think having the language provide some tools to implement stuff that's at the same level as the language runtime does make some sense for a systems programming language. E.g., see std.experimental.allocator. It would be a pity if the language hobbled that library by having uncircumventable pitfalls.
November 15, 2021
On 15/11/2021 9:04 AM, H. S. Teoh wrote:
> IOW, const becomes unusable with any object that could potentially
> contain a ref-counted sub-object.

I've mentioned this before, but this pattern could be rectified with scope + ARC.
November 15, 2021

On Sunday, 14 November 2021 at 20:04:40 UTC, H. S. Teoh wrote:

>

However, it does have far-reaching implications. For example, it implies that refcounted objects cannot be used inside any type that may get passed to a const-receiving interface.

While this is a problem, it is not as extreme as you make it sound.

Why? Because borrowing does not involve writing to the reference count, and most functions just need access to, not ownership of, their arguments. So const will often work.

Nevertheless, you still have a strong point. I think the better option is to sacrifice pure in order to make transitive const and immutable work properly with reference counting.

I can't think of any real problems this would cause; generic code usually infers purity, anyway.

November 14, 2021
On 11/14/2021 12:18 PM, Timon Gehr wrote:
> On 14.11.21 20:51, Walter Bright wrote:
>> On 11/14/2021 10:25 AM, Timon Gehr wrote:
>>> How do you manually deallocate an immutable payload?
>>
>> The same way it is done now. Call free().
>>
>> Calling free() on an object ends its lifetime. As I mentioned to Steven, lifetime and mutability are independent attributes.
>>
> 
> I agree that they are independent attributes, but how does the compiler know that something is an allocation/deallocation function? (My suggestion was to annotate such functions __mutable.)

It's done in @live functions. The functions aren't annotated specially, it's just that passing a pointer argument to a non-scope parameter means the pointer value is transferred, and is no longer 'live' in the calling function.
November 15, 2021
On Sunday, 14 November 2021 at 21:59:23 UTC, Timon Gehr wrote:
>> I assumed that 'outside of the type system' meant 'language-level support for reference counting'; a a type with opaque representation, like a hash table.
>> ...
>
> Yes, I guess that's another way to go about it, but I think less language magic is better. Especially with reference counting, you may want to have detailed control over the allocator and/or the strategy for storing reference counts.

Definitely agree!

That said I think there may be a middle ground, involving some magic but also some control.  In particular, compared with __mutable, language-level support for intrusive rc can be sound without leading to surprising results, as long as you're not allowed to read from the reference count.  Saying 'build me a reference-counted object from this chunk of memory' should be pretty easy, and ditto specifying how the memory is disposed of once the reference count reaches 0.  Being polymorphic over intrusive/extrusive representation is harder, though.
November 15, 2021

On Sunday, 14 November 2021 at 13:30:28 UTC, deadalnix wrote:

>

Dude, I don't want to be rude, but...

If you didn't, you wouldn't have been. Please don't go saying I made you.

>

Please do not clutter this thread.

Oh don't worry, this is the last of me in this thread. Rejoice and break out the drinks.

November 15, 2021
On 15.11.21 03:04, Walter Bright wrote:
> On 11/14/2021 12:18 PM, Timon Gehr wrote:
>> On 14.11.21 20:51, Walter Bright wrote:
>>> On 11/14/2021 10:25 AM, Timon Gehr wrote:
>>>> How do you manually deallocate an immutable payload?
>>>
>>> The same way it is done now. Call free().
>>>
>>> Calling free() on an object ends its lifetime. As I mentioned to Steven, lifetime and mutability are independent attributes.
>>>
>>
>> I agree that they are independent attributes, but how does the compiler know that something is an allocation/deallocation function? (My suggestion was to annotate such functions __mutable.)
> 
> It's done in @live functions. The functions aren't annotated specially, it's just that passing a pointer argument to a non-scope parameter means the pointer value is transferred, and is no longer 'live' in the calling function.

I am aware, but I don't see how it helps. Where is the curtain and how do you go behind it?
November 14, 2021
On 11/14/2021 1:12 PM, Steven Schveighoffer wrote:
> First, I'll note that `__mutable` on its own is perfectly sound.

I don't think so. It produces two kinds of immutability - one with mutable members, one with not. Different rules apply.

But then what do you do with opaque types? The compiler doesn't know which kind of immutable they are. So it is forced to go with the worst case - immutables are mutable. And it all falls apart.


>      2. Qualifiers compound problems with interlocking: mutable data is known to be single-threaded, so no need for interlocking. Immutable data may be multi-threaded, meaning reference counting needs atomic operations. Const data has unknown origin, which means the information of how data originated (mutable or not) must be saved at runtime.

Right.

> We need a change of requirements if we want to give up on immutable reference counting.

Yup.

November 14, 2021
On 11/14/2021 12:04 PM, H. S. Teoh wrote:
> Given D's emphasis on generic code, (1) seems like the only viable
> option.  Which makes const in D even narrower in scope than ever.

C++ const doesn't actually work, which is why C++ ref counted objects can be const.
November 15, 2021
On Monday, 15 November 2021 at 07:14:12 UTC, Walter Bright wrote:
> On 11/14/2021 12:04 PM, H. S. Teoh wrote:
>> Given D's emphasis on generic code, (1) seems like the only viable
>> option.  Which makes const in D even narrower in scope than ever.
>
> C++ const doesn't actually work, which is why C++ ref counted objects can be const.

Uh? C++ shared_ptr stores the count in a separate object.

The reason you can cast away const is so that you can call C-APIs that actually have const-behaviour, but not a const signature.

You have to be able to do this in D to.