www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How do I copy struct having immutable pointer member when enabled

reply outlandkarasu <outland.karasu gmail.com> writes:
I wrote a struct that having a std.datetime.SysTime member.
However this struct cannot copy from scope variable.

I think because SysTime has a immutable(TimeZone) member and it 
cannot copy to out of scope.

--------
struct A { SysTime timestamp; }

A createA()  safe
{
     scope a = A(Clock.currTime());

     // Error: scope variable a may not be returned
     return a;

     // OK
     //return A(SysTime(a.timestamp.stdTime, 
a.timestamp.timezone));
}
--------
https://run.dlang.io/is/e3AID7

There is a similar situation when struct has string enum member.

--------
enum Tag { tag = "tag" }

struct A { Tag tag; }

A createA()  safe
{
     scope a = A(Tag.tag);

     // Error: scope variable a may not be returned
     return a;

     // NG
     // return A(a);
     // return A(a.tag);
}
--------
https://run.dlang.io/is/Y8v5RX

I have an unsightly workaround.

--------
import std;

enum Tag
{
     tag = "tag"
}

/// scope tag to unscope.
Tag unscope(scope Tag tag)  nogc nothrow pure  safe
{
     static foreach (e; EnumMembers!Tag)
     {
         if (e == tag)
         {
             return e;
         }
     }

     assert(false);
}

struct A
{
     Tag tag;

     /// add this for construct from value.
     this (Tag tag)  nogc nothrow pure  safe scope
     {
         this.tag = tag;
     }

     /// add this for copy to out of scope.
     this (ref scope const A a)  nogc nothrow pure  safe scope
     {
         this.tag = a.tag.unscope;
     }

     // cannot use formal copy constructor.
     /+
     this (ref return scope const A a)  nogc nothrow pure  safe 
scope
     {
         this.tag = a.tag.unscope;
     }
     +/
}

A createA()  safe
{
     scope a = A(Tag.tag);

     // OK
     return A(a);
}
--------

I understand those errors are DIP1000 language design.
However I suppose that DIP1000 check can permit immutable pointer 
in some cases.

Is there a better workaround, practices or patterns?
Aug 30 2020
parent reply ag0aep6g <anonymous example.com> writes:
On 30.08.20 17:24, outlandkarasu wrote:
 --------
 enum Tag { tag = "tag" }
 
 struct A { Tag tag; }
 
 A createA()  safe
 {
      scope a = A(Tag.tag);
 
      // Error: scope variable a may not be returned
      return a;
 
      // NG
      // return A(a);
      // return A(a.tag);
 }
 --------
[...]
 I understand those errors are DIP1000 language design.
 However I suppose that DIP1000 check can permit immutable pointer in 
 some cases.
If I understand correctly, your point is that an enum pointer is guaranteed to refer to static data, so it could be exempt from `scope` checks. At a glance, that makes sense to me. But I guess one question is whether it's possible to create an enum value that points to the stack. A cast does the trick: immutable char[1] c = 'e'; E e = cast(E) c[]; DMD accepts it as safe, implying that the cast is valid and that `e` is a safe value. If that is correct, then enum pointers are actually not guaranteed to refer to static data. They can just as well point to the stack. Consequently, an enum pointer must be treated like a plain pointer. I.e., `scope` must treat a `Tag` just like a plain `string`.
 Is there a better workaround, practices or patterns?
In your example, you can just remove the `scope` annotation. Why mark a local that you want to return with `scope`? Doesn't make sense But I guess your actual use case isn't as simple. Maybe you can show a less reduced version of the code where simply removing `scope` is not an option?
Aug 30 2020
parent reply outlandkarasu <outland.karasu gmail.com> writes:
On Sunday, 30 August 2020 at 16:33:58 UTC, ag0aep6g wrote:
 On 30.08.20 17:24, outlandkarasu wrote:
 --------
 enum Tag { tag = "tag" }
 
 struct A { Tag tag; }
 
 A createA()  safe
 {
      scope a = A(Tag.tag);
 
      // Error: scope variable a may not be returned
      return a;
 
      // NG
      // return A(a);
      // return A(a.tag);
 }
 --------
[...]
 I understand those errors are DIP1000 language design.
 However I suppose that DIP1000 check can permit immutable 
 pointer in some cases.
If I understand correctly, your point is that an enum pointer is guaranteed to refer to static data, so it could be exempt from `scope` checks. At a glance, that makes sense to me. But I guess one question is whether it's possible to create an enum value that points to the stack. A cast does the trick: immutable char[1] c = 'e'; E e = cast(E) c[]; DMD accepts it as safe, implying that the cast is valid and that `e` is a safe value. If that is correct, then enum pointers are actually not guaranteed to refer to static data. They can just as well point to the stack. Consequently, an enum pointer must be treated like a plain pointer. I.e., `scope` must treat a `Tag` just like a plain `string`.
 Is there a better workaround, practices or patterns?
In your example, you can just remove the `scope` annotation. Why mark a local that you want to return with `scope`? Doesn't make sense But I guess your actual use case isn't as simple. Maybe you can show a less reduced version of the code where simply removing `scope` is not an option?
Thanks for your reply. I thought that I cannot make non-scope `ref` parameters from `scope` array references. But I found It allowed currently. My reduced version code is below; -------- enum Currency : string { USD = "USD", EUR = "EUR", GBP = "GBP", JPY = "JPY", } struct Instrument { Currency bid; Currency ask; } struct Price { Instrument instrument; ulong value; } class MinRecorder { nogc nothrow pure safe: // prices from local scoped array buffers. // I want to restrict prices reference to scope. void update(scope const(Price)[] prices) scope { foreach (price; prices) { update(price); } } // I thought price parameter need `scope` when called by scoped array elements. // But it can remove `scope` attribute. void update( /* scope */ ref const(Price) price) scope { if (minPrice.isNull || price.value < minPrice.get.value) { minPrice = price; } } Nullable!Price minPrice; } -------- full example: https://run.dlang.io/is/wVbrwf I also found a worried point that I can take non-scope pointer from non-scope `ref` parameter in DMV v2.093.1. -------- class MinPointerRecorder { nogc nothrow pure safe: void update(scope const(Price)[] prices) scope { foreach (price; prices) { update(price); } } void update( /* scope */ ref const(Price) price) scope { if (!minPrice || price.value < minPrice.value) { // Is this DIP1000 BUG? // When without DIP1000, reported compile error. // Error: cannot take address of parameter price minPrice = &price; } } const(Price)* minPrice; } -------- I also think about Flyweight pattern in D. I expected simple struct that contains cached `immutable` reference behaves a simple value type like primitive types. But reference contained struct is not simple. If I want a simple value type struct, the struct shouldn't be contain any references also include static string or immutable reference.
Aug 30 2020
parent reply ag0aep6g <anonymous example.com> writes:
On 31.08.20 06:24, outlandkarasu wrote:
 I thought that I cannot make non-scope `ref` parameters from `scope` 
 array references.
 But I found It allowed currently.
[...]
 enum Currency : string {
      USD = "USD", EUR = "EUR", GBP = "GBP", JPY = "JPY",
 }
 
 struct Instrument {
      Currency bid;
      Currency ask;
 }
 
 struct Price {
      Instrument instrument;
      ulong value;
 }
[...]
      void update(scope const(Price)[] prices) scope
      {
          foreach (price; prices)
          {
              update(price);
          }
      }
 
      // I thought price parameter need `scope` when called by scoped 
 array elements.
      // But it can remove `scope` attribute.
      void update( /* scope */ ref const(Price) price) scope
      {
          if (minPrice.isNull || price.value < minPrice.get.value)
          {
              minPrice = price;
          }
      }
`ref` kind of implies `scope` [1]. You don't need to type it out. When you do type out `scope ref const(Price)`, the `scope` actually doesn't apply to the `ref` but to the pointers in `Price` (whereas the `scope` in `scope const(Price)[]` applies to the pointer of the array). So you don't need `scope` on the `price` parameter because you're taking it as a `ref`. You would need `scope` if you were taking it as a pointer (`scope const(Price)* price`). By the way, semantically there isn't any reason to take `price` as either `ref` or pointer. You can just as well take it by value, since you're making a copy of it anyway with `minPrice = price` (and you also make a copy earlier with `foreach (price; prices)`). [...]
 I also found a worried point that I can take non-scope pointer from 
 non-scope `ref` parameter in DMV v2.093.1.
 
 --------
 class MinPointerRecorder
 {
  nogc nothrow pure  safe:
 
      void update(scope const(Price)[] prices) scope
      {
          foreach (price; prices)
          {
              update(price);
          }
      }
 
      void update( /* scope */ ref const(Price) price) scope
      {
          if (!minPrice || price.value < minPrice.value)
          {
              // Is this DIP1000 BUG?
              // When without DIP1000, reported compile error.
              // Error: cannot take address of parameter price
              minPrice = &price;
          }
      }
 
      const(Price)* minPrice;
 }
 --------
Definitely a bug, yes. Reduced test case: ---- class MinPointerRecorder { int* minPrice; void update(ref int price) safe { minPrice = &price; /* Should not compile. */ } } void main() safe { auto r = new MinPointerRecorder; () { int mp = 42; r.update(mp); } (); () { ulong[1000] stomp = 13; } (); import std.stdio: writeln; writeln(*r.minPrice); /* Prints "13". */ } ---- I don't think this is in Bugzilla yet. Please file an issue. Or let me know if you want me to do it. https://issues.dlang.org [1] I'm not exactly sure how it works. As far as I know, it's not documented anywhere.
Aug 30 2020
parent outlandkarasu <outland.karasu gmail.com> writes:
On Monday, 31 August 2020 at 05:46:45 UTC, ag0aep6g wrote:
 `ref` kind of implies `scope` [1]. You don't need to type it 
 out. When you do type out `scope ref const(Price)`, the `scope` 
 actually doesn't apply to the `ref` but to the pointers in 
 `Price` (whereas the `scope` in `scope const(Price)[]` applies 
 to the pointer of the array).

 So you don't need `scope` on the `price` parameter because 
 you're taking it as a `ref`. You would need `scope` if you were 
 taking it as a pointer (`scope const(Price)* price`).

 By the way, semantically there isn't any reason to take `price` 
 as either `ref` or pointer. You can just as well take it by 
 value, since you're making a copy of it anyway with `minPrice = 
 price` (and you also make a copy earlier with `foreach (price; 
 prices)`).
Thank you for your advice. I understand that I had abused `scope` parameter.
 I don't think this is in Bugzilla yet. Please file an issue. Or 
 let me know if you want me to do it.
I have filed issue about `ref` parameter address check. https://issues.dlang.org/show_bug.cgi?id=21212
Aug 31 2020