Jump to page: 1 2
Thread overview
what exactly does cast(shared) and cast away shared do?
Jun 16, 2020
mw
Jun 16, 2020
Simen Kjærås
Jun 16, 2020
mw
Jun 16, 2020
Simen Kjærås
Jun 16, 2020
Simen Kjærås
Jun 17, 2020
Kagamin
Jun 17, 2020
Kagamin
Jun 16, 2020
Johannes Loher
June 16, 2020
I'm trying this program:

-----------------------------------------------------------------------------
class Foo {
  int attr;

  this() {
    writeln("Foo.ctor:", &attr);
  }
}

void threadInc(shared Foo foo) {
  writeln("threadInc:", &foo);
  writeln("threadInc:", &(foo.attr));
  Foo f = cast(Foo)foo;
  writeln("threadInc:", &f);
  foo.attr = 123;
}

void main() {
  Foo foo = new Foo();
  writeln("main:\t", &foo);
  writeln("main:\t", &(foo.attr));

  spawn(&threadInc, cast(shared)foo);  // cannot pass foo: Error: static assert:  "Aliases to mutable thread-local data not allowed." Have to cast to shared
  thread_joinAll();

  writeln("main:", foo.attr);
  writeln("main:\t", &foo);
  writeln("main:\t", &(foo.attr));

  shared sf1 = cast(shared)foo;
  shared sf2 = cast(shared)foo;
  writeln("sf1:\t", &sf1, "\t", &(sf1.attr));
  writeln("sf2:\t", &sf2, "\t", &(sf2.attr));

  foo.attr = 456;
  writeln("main:", foo.attr);
  writeln("main:", sf1.attr);
  writeln("main:", sf2.attr);
}
-----------------------------------------------------------------------------

here is the result:

-----------------------------------------------------------------------------
Foo.ctor:7FB83F4B0010              <- &attr is always the same
main:   7FFFD6D081F0               <- &foo
main:   7FB83F4B0010
threadInc:7FB83E1EFC08             <- cast(shared)foo
threadInc:7FB83F4B0010
threadInc:7FB83E1EFBF8             <- cast(Foo)foo, but != the original &foo
main:123
main:   7FFFD6D081F0               <- foo
main:   7FB83F4B0010
sf1:    7FFFD6D08200    7FB83F4B0010   <- each time cast(shared)foo will have a new addr
sf2:    7FFFD6D08208    7FB83F4B0010   <- but &(x.attr) is always the same
main:456
main:456
main:456
-----------------------------------------------------------------------------

In all the 3 cases, &(foo.attr) are the same; this is what I wanted, since the object is allocated on the heap.

But the memory address of the original foo, cast(shared) foo, and cast(Foo) foo are all different.

And for sf1, sf2, each time cast(shared)foo will have a new addr.

So every time cast(shared) / cast away shared will create a new wrapper to the original object, but all these wrappers' actual fields (the physical addr) stay the same?

What's the point of doing these wrappers every time?

Esp. for sf1, and sf2?  `shared` means it's globally *shared*, everywhere it's the same object, then why we have &(sf1) != &(sf2)?


What's the exact semantics of cast(shared) and cast away shared?


Thanks.

June 16, 2020
On Tuesday, 16 June 2020 at 06:15:11 UTC, mw wrote:
> I'm trying this program:
>
> -----------------------------------------------------------------------------
> class Foo {
>   int attr;
>
>   this() {
>     writeln("Foo.ctor:", &attr);
>   }
> }
>
> void threadInc(shared Foo foo) {
>   writeln("threadInc:", &foo);
>   writeln("threadInc:", &(foo.attr));
>   Foo f = cast(Foo)foo;
>   writeln("threadInc:", &f);
>   foo.attr = 123;
> }
>
> void main() {
>   Foo foo = new Foo();
>   writeln("main:\t", &foo);
>   writeln("main:\t", &(foo.attr));
>
>   spawn(&threadInc, cast(shared)foo);  // cannot pass foo: Error: static assert:  "Aliases to mutable thread-local data not allowed." Have to cast to shared
>   thread_joinAll();
>
>   writeln("main:", foo.attr);
>   writeln("main:\t", &foo);
>   writeln("main:\t", &(foo.attr));
>
>   shared sf1 = cast(shared)foo;
>   shared sf2 = cast(shared)foo;
>   writeln("sf1:\t", &sf1, "\t", &(sf1.attr));
>   writeln("sf2:\t", &sf2, "\t", &(sf2.attr));
>
>   foo.attr = 456;
>   writeln("main:", foo.attr);
>   writeln("main:", sf1.attr);
>   writeln("main:", sf2.attr);
> }
> -----------------------------------------------------------------------------
>
> here is the result:
>
> -----------------------------------------------------------------------------
> Foo.ctor:7FB83F4B0010              <- &attr is always the same
> main:   7FFFD6D081F0               <- &foo
> main:   7FB83F4B0010
> threadInc:7FB83E1EFC08             <- cast(shared)foo
> threadInc:7FB83F4B0010
> threadInc:7FB83E1EFBF8             <- cast(Foo)foo, but != the original &foo
> main:123
> main:   7FFFD6D081F0               <- foo
> main:   7FB83F4B0010
> sf1:    7FFFD6D08200    7FB83F4B0010   <- each time cast(shared)foo will have a new addr
> sf2:    7FFFD6D08208    7FB83F4B0010   <- but &(x.attr) is always the same
> main:456
> main:456
> main:456
> -----------------------------------------------------------------------------
>
> In all the 3 cases, &(foo.attr) are the same; this is what I wanted, since the object is allocated on the heap.
>
> But the memory address of the original foo, cast(shared) foo, and cast(Foo) foo are all different.
>
> And for sf1, sf2, each time cast(shared)foo will have a new addr.
>
> So every time cast(shared) / cast away shared will create a new wrapper to the original object, but all these wrappers' actual fields (the physical addr) stay the same?
>
> What's the point of doing these wrappers every time?
>
> Esp. for sf1, and sf2?  `shared` means it's globally *shared*, everywhere it's the same object, then why we have &(sf1) != &(sf2)?
>
>
> What's the exact semantics of cast(shared) and cast away shared?

The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same.

So in D, unlike C++, you can't really refer to a class instance directly - you always have a reference to the instance instead. When you have 'Foo instance = new Foo();', 'instance' is, behind the scenes, a pointer, for instance 7FB83F4B0000. When you further add 'Foo instance2 = instance;', they both refer to the same object. However, 'instance' and 'instance2' are themselves separate variables, placed on the stack, and they will have their own addresses, which is what you get with &instance and &instance2. If you look at the addresses you get for &foo vs &(foo.attr), you will notice they are significantly different, which you would not expect for a pointer to the instance vs a pointer to a field inside that instance, unless the instance was humongous.

Since I've told you class references are pointers behind the scenes, you can print their values by casting them to to some kind of pointer. Try replacing &foo in the code with cast(int*)foo, and you'll see they all point to the same memory (8 or 16 bytes before the address of attr, since class instances have some hidden fields - vtable and monitor).

--
  Simen
June 16, 2020
Am 16.06.20 um 08:15 schrieb mw:
> I'm trying this program:
> 
> -----------------------------------------------------------------------------
> 
> class Foo {
>   int attr;
> 
>   this() {
>     writeln("Foo.ctor:", &attr);
>   }
> }
> 
> void threadInc(shared Foo foo) {
>   writeln("threadInc:", &foo);
>   writeln("threadInc:", &(foo.attr));
>   Foo f = cast(Foo)foo;
>   writeln("threadInc:", &f);
>   foo.attr = 123;
> }
> 
> void main() {
>   Foo foo = new Foo();
>   writeln("main:\t", &foo);
>   writeln("main:\t", &(foo.attr));
> 
>   spawn(&threadInc, cast(shared)foo);  // cannot pass foo: Error: static
> assert:  "Aliases to mutable thread-local data not allowed." Have to
> cast to shared
>   thread_joinAll();
> 
>   writeln("main:", foo.attr);
>   writeln("main:\t", &foo);
>   writeln("main:\t", &(foo.attr));
> 
>   shared sf1 = cast(shared)foo;
>   shared sf2 = cast(shared)foo;
>   writeln("sf1:\t", &sf1, "\t", &(sf1.attr));
>   writeln("sf2:\t", &sf2, "\t", &(sf2.attr));
> 
>   foo.attr = 456;
>   writeln("main:", foo.attr);
>   writeln("main:", sf1.attr);
>   writeln("main:", sf2.attr);
> }
> -----------------------------------------------------------------------------
> 
> 
> here is the result:
> 
> -----------------------------------------------------------------------------
> 
> Foo.ctor:7FB83F4B0010              <- &attr is always the same
> main:   7FFFD6D081F0               <- &foo
> main:   7FB83F4B0010
> threadInc:7FB83E1EFC08             <- cast(shared)foo
> threadInc:7FB83F4B0010
> threadInc:7FB83E1EFBF8             <- cast(Foo)foo, but != the original
> &foo
> main:123
> main:   7FFFD6D081F0               <- foo
> main:   7FB83F4B0010
> sf1:    7FFFD6D08200    7FB83F4B0010   <- each time cast(shared)foo will
> have a new addr
> sf2:    7FFFD6D08208    7FB83F4B0010   <- but &(x.attr) is always the same
> main:456
> main:456
> main:456
> -----------------------------------------------------------------------------
> 
> 
> In all the 3 cases, &(foo.attr) are the same; this is what I wanted, since the object is allocated on the heap.
> 
> But the memory address of the original foo, cast(shared) foo, and
> cast(Foo) foo are all different.
> 
> And for sf1, sf2, each time cast(shared)foo will have a new addr.
> 
> So every time cast(shared) / cast away shared will create a new wrapper
> to the original object, but all these wrappers' actual fields (the
> physical addr) stay the same?
> 
> What's the point of doing these wrappers every time?
> 
> Esp. for sf1, and sf2?  `shared` means it's globally *shared*,
> everywhere it's the same object, then why we have &(sf1) != &(sf2)?
> 
> 
> What's the exact semantics of cast(shared) and cast away shared?
> 
> 
> Thanks.
> 
This is because you are using different class variables and then take their address. Of course the variables themselves have different addresses on the stack, which is why you see different addresses. But as you already noticed, they point to the same object. You get the exact same behavior when using several regular variables (i.e. no shared involved) that point to the same class object.

```
import std;

class A {}

void main()
{
    auto a = new A();
    auto b = a;
    writeln(&a); // 7FFF0D776E20
    writeln(&b); // 7FFF0D776E28
}
```

Classes are reference types, i.e. internally, they actually are pointers. you can cast them to void* to see what they actually point to:

```
import std;

class A {}

void main()
{
    A a = new A();
    A b = a;
    shared(A) c = cast(shared A) a;
    A d = cast(A) c;
    writeln(&a); // 7FFF0D776E20
    writeln(&b); // 7FFF0D776E28
    writeln(&c); // 7FFE15993F80
    writeln(&d); // 7FFE15993F88
    writeln(cast(void*)a); // 7FD1AB5AA000
    writeln(cast(void*)b); // 7FD1AB5AA000
    writeln(cast(void*)c); // 7FD1AB5AA000
    writeln(cast(void*)d); // 7FD1AB5AA000
}
```

As you see, casting to shared and back inbetween doesn't have any effect on the object the variables actually points to.
June 16, 2020
On Tuesday, 16 June 2020 at 06:53:22 UTC, Simen Kjærås wrote:
>> What's the exact semantics of cast(shared) and cast away shared?
>
> The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same.

Thanks for the explanation. I've thought &foo will return the address of that actual object, just as &(foo.attr) does for .attr.

So cast(shared) and cast away shared only affect the type system, the actual object will always stay the same, and there is not any magic operation or wrapper behind the scene.

`shared` or not is purely a compile-time attribute, not some runtime dynamic attribute on the object, i.e. at runtime when you get hold of an object on the heap, there's no way you can tell from the object itself whether it's shared or not.

June 16, 2020
On Tuesday, 16 June 2020 at 07:22:59 UTC, mw wrote:
>
> Thanks for the explanation. I've thought &foo will return the address of that actual object, just as &(foo.attr) does for .attr.

To get the actual address, you need to cast the class reference to a pointer:

https://run.dlang.io/gist/run-dlang/ea2b163f0fd90e16adc72ab78ed48553?compiler=dmd

> So cast(shared) and cast away shared only affect the type system, the actual object will always stay the same, and there is not any magic operation or wrapper behind the scene.
>
> `shared` or not is purely a compile-time attribute, not some runtime dynamic attribute on the object, i.e. at runtime when you get hold of an object on the heap, there's no way you can tell from the object itself whether it's shared or not.

Yes, exactly.


June 16, 2020
On Tuesday, 16 June 2020 at 07:22:59 UTC, mw wrote:
> On Tuesday, 16 June 2020 at 06:53:22 UTC, Simen Kjærås wrote:
>>> What's the exact semantics of cast(shared) and cast away shared?
>>
>> The only thing cast(shared) does is tell the type system 'this object is shared'. The reason you're getting different addresses is you're taking the address of the reference on the stack. As you've noticed, &foo.attr, &(sf1.attr), &(sf2.attr) is exactly the same, so the actual instances are the same.
>
> Thanks for the explanation. I've thought &foo will return the address of that actual object, just as &(foo.attr) does for .attr.
>
> So cast(shared) and cast away shared only affect the type system, the actual object will always stay the same, and there is not any magic operation or wrapper behind the scene.
>
> `shared` or not is purely a compile-time attribute, not some runtime dynamic attribute on the object, i.e. at runtime when you get hold of an object on the heap, there's no way you can tell from the object itself whether it's shared or not.

Correct. So it's perfectly possible to have a shared and a non-shared reference to the same object, which is potentially dangerous. A DIP* has been accepted that will limit your ability to do dangerous things in this case, but it is not yet implemented.


* https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md

--
  Simen
June 16, 2020
On Tuesday, 16 June 2020 at 08:07:41 UTC, Simen Kjærås wrote:
> [..] A DIP* has been accepted that will limit your ability to do dangerous things in this case, but it is not yet implemented.
>
>
> * https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md
>
> --
>   Simen

It has been implemented (modulo bugs [1][2]) as of:

https://github.com/dlang/dmd/pull/10209
https://github.com/dlang/dmd/pull/11239

[1]: https://issues.dlang.org/show_bug.cgi?id=20195
[2]: https://issues.dlang.org/show_bug.cgi?id=20908
June 16, 2020
On Tuesday, 16 June 2020 at 08:32:47 UTC, Petar Kirov [ZombineDev] wrote:
>
> It has been implemented (modulo bugs [1][2]) as of:
>
> https://github.com/dlang/dmd/pull/10209
> https://github.com/dlang/dmd/pull/11239
>
> [1]: https://issues.dlang.org/show_bug.cgi?id=20195
> [2]: https://issues.dlang.org/show_bug.cgi?id=20908

Probably *mostly* implemented is a more accurate way to put it.


June 16, 2020
On Tuesday, 16 June 2020 at 08:32:47 UTC, Petar Kirov [ZombineDev] wrote:
> On Tuesday, 16 June 2020 at 08:07:41 UTC, Simen Kjærås wrote:
>> [..] A DIP* has been accepted that will limit your ability to do dangerous things in this case, but it is not yet implemented.
>>
>>
>> * https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1024.md
>>
>> --
>>   Simen
>
> It has been implemented (modulo bugs [1][2]) as of:
>
> https://github.com/dlang/dmd/pull/10209
> https://github.com/dlang/dmd/pull/11239
>
> [1]: https://issues.dlang.org/show_bug.cgi?id=20195
> [2]: https://issues.dlang.org/show_bug.cgi?id=20908

By Golly, you're right! I'd forgotten this had actually happened. Thanks! :)

--
  Simen
June 16, 2020
On Tuesday, 16 June 2020 at 08:45:24 UTC, Simen Kjærås wrote:
>
> By Golly, you're right! I'd forgotten this had actually happened. Thanks! :)
>

Hehe, you're welcome :)

If only all the people on newsgroup were aware of all the amazing developments happening everywhere in D, I think the atmosphere would be much more positive in the General group :)

I have been thinking that this would be a cool idea for a YouTube channel.

Dart/Flutter have an excellent channel: https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw

Though it 's geared mainly towards to the users of the language and the framework. I have been watching some of their GitHub repos of the past 1 year and I can say that the D development activity is definitely much more interesting :)
« First   ‹ Prev
1 2