how to assign to shared obj.systime?

Arafel er.krali at
Mon Jul 13 07:26:06 UTC 2020

On 13/7/20 3:46, Steven Schveighoffer wrote:
> On 7/11/20 6:15 AM, Arafel wrote:
>> What I really miss is some way of telling the compiler "OK, I know 
>> what I'm doing, I'm already in a critical section, and that all the 
>> synchronization issues have been already managed by me".
> You do. It's a cast.

Yes, and that's what I'm doing (although with some helper function to 
make it look slightly less ugly), but for non-reference types I have to 
do it every single time you use the variables, and it's annoying for 
anything beyond trivial.

There's no way to avoid it, because at best you can get a pointer that 
will be enough for most things, but it will show for instance if you 
want to use it as a parameter to another function.

Also, with more complex data types like structs and AAs where not only 
the AA itself, but also the members, keys and values become shared, it's 
*really* annoying, because there's no easy way you can get a "fully" 
non-shared reference, because `cast()` will *often* only remove the 
external shared layer (I'm not sure it's always the case, it has happen 
semi-randomly to me, and what it's worse, I don't know the rules for that).

Also, it becomes a real pain when you have to send those types to 
generic code that is not "share-aware".

And for basic types, you'll be forced to use atomicOp all the time, or 
again resort to pointers.

So yes, it's not impossible, but it's really, really inconvenient, to 
the point of making `shared` almost unusable beyond the most simple 
cases. In fact, I would be happy if it had to take a list of variables, 
and ignore `shared` just for them (and their members):

>> Within this block, shared would implicitly convert to non-shared, and 
>> the other way round, like this (in a more complex setup with a RWlock):
>> ```
>> setTime(ref SysTime t) shared {
>>      synchronized(myRWMutex.writer) critical_section {  // From this 
>> point I can forget about shared
>>          time = t;
>>      }
>> }
>> ```
> This isn't checkable by the compiler.

That's exactly why what I propose is a way to *explicitly* tell the 
compiler about it, like @system does for safety. I used 
`critical_section`, but perhaps `@critical_section` would have been 
clearer. Here is be a more explicit version specifying the variables to 
which it applies (note that you'd be able to use "this", or leave it 
empty and have it apply to everything):

void setTime(ref SysTime t) shared {
     synchronized(myRWMutex.writer) {
         @critical_section(time) {  // From this point I can forget 
about shared
             time = t;

Here it doesn't make a difference because the critical section is a 
single line (so it's even longer), but if you had to use multiple 
variables like that in a large expression, it'd become pretty much 
impossible to understand without it:

import std;

synchronized shared class TimeCount { // It's a synchronized class, so 
automatically locking
	void startClock() {
		cast() startTime = Clock.currTime; // Here I have to cast the lvalue
         // startTime = cast(shared) Clock.currTime; // Fails because 
opAssign is not defined for shared
	void endClock() {
		cast() endTime = Clock.currTime; // Again unintuitively casting the lvalue
	void calculateDuration() {
         timeEllapsed = cast (shared) (cast() endTime - cast() 
startTime); // Here I can also cast the rvalue, which looks more natural

	SysTime startTime;
	SysTime endTime;
	Duration timeEllapsed;

Non-obvious lvalue-casts all over the place, and even `timeEllapsed = 
cast (shared) (cast() end - cast() start);`.

And that one is not even too complex... I know in this case you can 
reorganize things, but it was just an example of what happens when you 
have to use multiple shared variables in an expression.

> You could accidentally end up referencing shared things as unshared when 
> the lock is unlocked. If you remove shared, you need to know and 
> understand the consequences, and the compiler can't help there, because 
> the type qualifier has been removed, so it's not aware of which things 
> are going to become shared after the lock is gone.
> -Steve

Well, it's meant as a low level tool, similar to what @system does for 
memory safety. You can't blame the compiler if you end up doing 
something wrong with your pointer arithmetic or with your casts from and 
to void* in your @system code, can you?

More information about the Digitalmars-d-learn mailing list