digitalmars.D - what exactly does cast(shared) and cast away shared do?
- mw (70/70) Jun 15 2020 I'm trying this program:
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (27/97) Jun 15 2020 The only thing cast(shared) does is tell the type system 'this
- mw (10/17) Jun 16 2020 Thanks for the explanation. I've thought &foo will return the
- Petar Kirov [ZombineDev] (5/15) Jun 16 2020 To get the actual address, you need to cast the class reference
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (10/29) Jun 16 2020 Correct. So it's perfectly possible to have a shared and a
- Petar Kirov [ZombineDev] (6/12) Jun 16 2020 It has been implemented (modulo bugs [1][2]) as of:
- Petar Kirov [ZombineDev] (3/8) Jun 16 2020 Probably *mostly* implemented is a more accurate way to put it.
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (6/22) Jun 16 2020 By Golly, you're right! I'd forgotten this had actually happened.
- Petar Kirov [ZombineDev] (13/15) Jun 16 2020 Hehe, you're welcome :)
- Kagamin (4/11) Jun 17 2020 Uh, people already believe DIP1024 is an all purpose safety
- Kagamin (5/9) Jun 17 2020 The way to tell it is if the object is typed as shared, then it's
- Johannes Loher (41/134) Jun 16 2020 This is because you are using different class variables and then take
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.
Jun 15 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
Jun 15 2020
On Tuesday, 16 June 2020 at 06:53:22 UTC, Simen Kjærås wrote: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.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.
Jun 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=dmdSo 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.
Jun 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: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 -- SimenThanks 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.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.
Jun 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 -- SimenIt 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
Jun 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=20908Probably *mostly* implemented is a more accurate way to put it.
Jun 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:By Golly, you're right! I'd forgotten this had actually happened. Thanks! :) -- Simen[..] 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 -- SimenIt 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
Jun 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 :)
Jun 16 2020
On Tuesday, 16 June 2020 at 08:07:41 UTC, Simen Kjærås wrote: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.mdUh, people already believe DIP1024 is an all purpose safety feature. Hello, it isn't. Especially it does nothing in case you have differently typed references to the same data.
Jun 17 2020
On Tuesday, 16 June 2020 at 07:22:59 UTC, mw wrote:`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.The way to tell it is if the object is typed as shared, then it's shared, if it's not typed as shared, then it's not shared. The only way to circumvent this is to use a cast, that circumvents entire type system.
Jun 17 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.
Jun 16 2020