shared - i need it to be useful

Stanislav Blinov stanislav.blinov at gmail.com
Wed Oct 17 21:12:19 UTC 2018


On Wednesday, 17 October 2018 at 19:25:33 UTC, Manu wrote:
> On Wed, Oct 17, 2018 at 12:05 PM Stanislav Blinov via 
> Digitalmars-d <digitalmars-d at puremagic.com> wrote:
>>
>> On Wednesday, 17 October 2018 at 18:46:18 UTC, Manu wrote:
>>
>> > I've said this a bunch of times, there are 2 rules:
>> > 1. shared inhibits read and write access to members
>> > 2. `shared` methods must be threadsafe
>> >
>> >>From there, shared becomes interesting and useful.
>>
>> Oh God...
>>
>> void atomicInc(shared int* i) { /* ... */ }
>>
>> Now what? There are no "methods" for ints, only UFCS. Those 
>> functions can be as safe as you like, but if you allow 
>> implicit promotion of int* to shared int*, you *allow implicit 
>> races*.
>
> This function is effectively an intrinsic. It's unsafe by 
> definition.

Only if implicit conversion is allowed. If it isn't, that's 
likely @trusted, and this:

void atomicInc(ref shared int);

can even be @safe.

> It's a tool for implementing threadsafe machinery.
> No user can just start doing atomic operations on random ints 
> and say
> "it's threadsafe", you must encapsulate the threadsafe 
> functionality
> into some sort of object that aggregates all concerns and 
> presents an
> intellectually sound api.

Threadsafety starts and ends with the programmer. By your logic 
*all* functions operating on `shared` are unsafe then. As far as 
compiler is concerned, there would be no difference between these 
two:

struct S {}
void atomicInc(ref shared S);

and

struct S { void atomicInc() shared { /* ... */ } }

The signatures of those two functions are exactly the same. How 
is that different from a function taking a shared int pointer or 
reference?

>
> Let me try one:
>
> void free(void*) { ... }
>
> Now what? I might have dangling pointers... it's a catastrophe!

One could argue that it should be void free(ref void* p) { /* ... 
*/ p = null; }
As a matter of fact, in my own allocators memory blocks allocated 
by them are passed by value and are non-copyable, they're not 
just void[] as in std.experimental.allocator. One must 'move' 
them to pass ownership, and that includes deallocation. But 
that's another story altogether.

> It's essentially the same argument.
> This isn't a function that professes to do something that 
> people might
> misunderstand and try to use in an unsafe way, it's a low-level
> implementation device, which is used to build larger *useful*
> constructs.

You're missing the point, again. You have an int. You pass a 
pointer to it to some API that takes an int*. You continue to use 
your int as just an int. The API changes, and now the function 
you called previously takes a shared int*. Implicit conversion 
works, everything compiles, you have a race. Now, that's of 
course an extremely stupid scenario. The point is: the caller of 
some API *must* assert that they indeed pass shared data. It's 
insufficient for the API alone to "promise" taking shared data. 
That's the difference with promotion to `const`.


More information about the Digitalmars-d mailing list