On Wednesday, 13 May 2015 at 17:49:38 UTC, Steven Schveighoffer wrote:
OK, then consider that this:

void main()
{
   string x;
   x ~= "hello";
   x ~= " world";
}

would require locking. That's unacceptable. Nobody would append with strings if this all required locking for no reason. The runtime currently does NOT lock for this case, it considers immutable and const to be thread-local.

Well, it's necessary because the design of druntime arrays is incompatible with D2's type system. Without locking, multi-threaded applications that use dynamic array operations could easily contain some particularly hard to track concurrency bugs.

Simply not doing the locking and hoping that everything is fine doesn't sound like a good plan (which I think we agree on, since shared(immutable(T)) would solve it).

No, I think the answer is simpler. Introduce shared(immutable), and then we can distinguish between immutable data that is shared and data that is not shared. It also makes implementing local heaps easier. Shared really is orthogonal to mutability.

Basically, shared(immutable(T)) would only be useful to allocators, including arrays because they may need to allocate when growing. I don't think it would be useful beyond that; the sharedness of immutable data is probably not interesting to any other kind of code.

It would make immutable considerably harder to use than it is today. shared(immutable(T)) would be implicitly convertible to shared(const(T)), but not const(T), which precludes the vast majority of mutation-agnostic D code out there today (I have never seen shared(const(T)) used in the wild). We would no longer be able to do even the simplest things, like passing a path string to another thread and use std.file.read on it.

Here's something even weirder: If you append to y (const(int)[]) as a reference to x (immutable(int)[]), then part of the array is immutable and shareable, and the part that was appended is const and not shareable.

This should be the case for user-defined containers as well as long as the element type doesn't have mutable indirection. Appending on slices seems to handle this correctly as well, rejecting attempts to append a const(T)[] to an immutable(T)[] when T has mutable indirection.

This should work:

void main()
{
   auto s = new S;
   passToOtherThread(&s.b);
}

Which makes s.a unshared, and s.b shared. But the memory block needs to be shareable.

That is an interesting example. Plain shared has the same problem:

struct S {
    int a;
    shared int b;
}

void main() {
    import std.concurrency;

    auto tid = spawn(() {});
    auto s = new S;
    tid.send(&s.b);
}

Types that contain shared anywhere within the type would have to be allocated from a global allocator, and with the current state of immutable, the same would have to be done for types that contain immutable anywhere within it. Note that it's only the case for head-shared/head-immutable; types like immutable(T)[] or shared(T)* don't count.

Reply via email to