Jump to page: 1 2 3
Thread overview
how is this array subtyping inside struct (bug?) possible?
Aug 09
mw
Aug 09
mw
Aug 10
RazvanN
Aug 10
mw
Aug 10
Avrina
Aug 10
Kagamin
Aug 10
mw
Aug 10
mw
Aug 10
mw
Aug 10
mw
Aug 10
mw
Aug 10
kinke
Aug 10
mw
Aug 11
Avrina
Aug 10
mw
Aug 15
Kagamin
Aug 11
RazvanN
Aug 10
mw
Aug 10
mw
August 09
Hi,

I want to share an array among a number of structs, with subtyping, I tried this, and find some strange behavior:

```
class SharedArray(T) {
  public T[] array;
  alias array this;  // subtyping
}

alias Filenames = SharedArray!(string);

struct S {
  Filenames fns;
  void alloc() {
    fns = new Filenames();
  }
}

void main(string[] args) {
  S s0;
  s0.alloc();
  s0.fns ~= "abc";

  foreach (i; 0..3) {
    S* s1 = new S();
    *s1 = s0;   //  copy the value from s0 to *s1
    writeln(s0.fns);
  }
}
```

program output:
```
["abc"]
[]
[]
```

why s0.fns changed after copy the value from s0 to *s1?

Is this a bug?
August 09
On Sunday, 9 August 2020 at 18:45:07 UTC, mw wrote:
> Hi,
>
> I want to share an array among a number of structs, with subtyping, I tried this, and find some strange behavior:
>
> ```
> class SharedArray(T) {
>   public T[] array;
>   alias array this;  // subtyping
> }
>
> alias Filenames = SharedArray!(string);
>
> struct S {
>   Filenames fns;
>   void alloc() {
>     fns = new Filenames();
>   }
> }
>
> void main(string[] args) {
>   S s0;
>   s0.alloc();
>   s0.fns ~= "abc";
>
>   foreach (i; 0..3) {
>     S* s1 = new S();
>     *s1 = s0;   //  copy the value from s0 to *s1
>     writeln(s0.fns);
>   }
> }
> ```
>
> program output:
> ```
> ["abc"]
> []
> []
> ```
>
> why s0.fns changed after copy the value from s0 to *s1?
>
> Is this a bug?

No, it's not a bug, it's just a weird quirk of how `alias this` interacts with reference types like classes.

When you pass a range to `writeln`, it will iterate the range using `front` and `popFront` in order to print each of the elements. Doing this consumes the range.

Normally, that's not a problem, because writeln takes its arguments by value, so any range you pass to it will be copied, and only the copy will be consumed. However, because you've made your `Filenames` class into an input range using `alias this`, and classes are reference types, consuming a copy of the range also consumes the original.

The solution is to use the `save` function from `std.range` to create an independent copy of the range for `writeln` to iterate:

    import std.range;
    writeln(s0.fns.save);
August 09
On Sunday, 9 August 2020 at 20:30:35 UTC, Paul Backus wrote:

> Normally, that's not a problem, because writeln takes its arguments by value, so any range you pass to it will be copied, and only the copy will be consumed. However, because you've made your `Filenames` class into an input range using `alias this`, and classes are reference types, consuming a copy of the range also consumes the original.

I also tried this:

```
  foreach (i; 0..3) {
    S* s1 = new S();
    *s1 = s0;
    writeln(s0.fns);
    writeln(s0.fns.array.length);  // *directly* access array
  }
```

The output is:

```
["abc"]
0
[]
0
[]
0
```

I'm *directly* access the underlying array, so why its length changed to 0 after writeln?

So a plain array *is* also a range? I've thought (an array's) range is a *separate* struct (which wrap the original array). The wrapper range can be consumed, but the underlying array should stay the same.

I never seen it's mentioned in the doc:

https://dlang.org/spec/arrays.html

August 10
On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:

> I'm *directly* access the underlying array, so why its length changed to 0 after writeln?
>
You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.

> So a plain array *is* also a range? I've thought (an array's) range is a *separate* struct (which wrap the original array). The wrapper range can be consumed, but the underlying array should stay the same.
>
> I never seen it's mentioned in the doc:
>
> https://dlang.org/spec/arrays.html

You had a type in your code.
August 10
On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:
> On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:
>
>> I'm *directly* access the underlying array, so why its length changed to 0 after writeln?
>>
> You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.

I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:

```
  foreach (i; 0..3) {
    S* s1 = new S();
    *s1 = s0;  //  copy the value from s0 to *s1
    writeln(s0.fns.array);
    writeln(s0.fns.array.length);  //*directly* access the underlying array
  }
```

output:
```
["abc"]
1
["abc"]
1
["abc"]
1
```

via subtyping:

```
  foreach (i; 0..3) {
    S* s1 = new S();
    *s1 = s0;  //  copy the value from s0 to *s1
    writeln(s0.fns);
    writeln(s0.fns.length);
  }
```

output:
```
["abc"]
0
[]
0
[]
0
```

And the direct access is the expected behavior that the subtyping mechanism want to provide a convenience for.

Since it introduce a surprise to the user, I'd call that a bug.


>> So a plain array *is* also a range? I've thought (an array's) range is a *separate* struct (which wrap the original array). The wrapper range can be consumed, but the underlying array should stay the same.
>>
>> I never seen it's mentioned in the doc:
>>
>> https://dlang.org/spec/arrays.html
>
> You had a type in your code.

What do you mean? can you elaborate?


August 10
On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:
> On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:
>> On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:
>>
>>> I'm *directly* access the underlying array, so why its length changed to 0 after writeln?
>>>
>> You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
>
> I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:

You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference.

In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.
August 10
On Monday, 10 August 2020 at 12:03:06 UTC, Paul Backus wrote:
> On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:
>> On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:
>>> On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:
>>>
>>>> I'm *directly* access the underlying array, so why its length changed to 0 after writeln?
>>>>
>>> You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
>>
>> I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:
>
> You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference.
>
> In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.

This really doesn't excuse why this bug is happening. Why is writeln() using front() and popFront() that modifies the range rather than simply using foreach()? Why is it modifying something it knows to be a class? This is definitely a bug that should be fixed. Not sure why people are trying to explain it away, looking that the writeln implementation it is completely convoluted. It's no wonder bugs like this are going to happen.
August 10
On Monday, 10 August 2020 at 13:44:17 UTC, Avrina wrote:
> This really doesn't excuse why this bug is happening. Why is writeln() using front() and popFront() that modifies the range rather than simply using foreach()? Why is it modifying something it knows to be a class? This is definitely a bug that should be fixed. Not sure why people are trying to explain it away, looking that the writeln implementation it is completely convoluted. It's no wonder bugs like this are going to happen.

When writeln receives a reference type range, are you sure it shouldn't be consumed? How do you decide that?
August 10
On Monday, 10 August 2020 at 13:44:17 UTC, Avrina wrote:
>
> This really doesn't excuse why this bug is happening. Why is writeln() using front() and popFront() that modifies the range rather than simply using foreach()?

foreach over a range also uses front() and popFront().

> Why is it modifying something it knows to be a class? This is definitely a bug that should be fixed.

The behavior of writeln in this case is documented under `std.format.formatValue` [1], which is used by `writeln` internally:

> For the class objects which have input range interface,
>
>    * If the instance toString has overridden Object.toString, it is used.
>    * Otherwise, the objects are formatted as input range.

So everything is working as intended. However, there is certainly a documentation issue here, since it is not at all obvious from the `writeln` docs that it uses `formatValue`.

[1] http://dpldocs.info/experimental-docs/std.format.formatValue.html
August 10
On Monday, 10 August 2020 at 12:03:06 UTC, Paul Backus wrote:
> On Monday, 10 August 2020 at 02:50:20 UTC, mw wrote:
>> On Monday, 10 August 2020 at 02:38:55 UTC, RazvanN wrote:
>>> On Sunday, 9 August 2020 at 21:12:58 UTC, mw wrote:
>>>
>>>> I'm *directly* access the underlying array, so why its length changed to 0 after writeln?
>>>>
>>> You are accessing the underlying array after it was consumed. The line writeln(s0.fns) passes class, if you want to pass the underlying array you should type `writeln(so.fns.array)` and then it will not consume the array.
>>
>> I know that; and that's exactly I call it a subtyping bug: i.e. directly access & access via subtyping behave differently:
>
> You are subtyping a value type (array) with a reference type (class). So when you directly access it, it's passed by value, but when you pass the subtype, it's passed by reference.
>
> In other words, using `alias this` does not create a *true* subtype (according to the Liskov substitution principle), because it allows you to change the way the type is copied from by-value to by-reference.

https://dlang.org/spec/arrays.html#dynamic-arrays

"Dynamic arrays consist of a length and a pointer to the array data."

I've thought, D's dynamic array is implemented as:

```  /* this is C code */
struct {
  T* ptr;
  size_t length;
}
```

And now, it shows the length is changeable via its range interface: so where on this range doc page this behavior on length is mentioned?

https://dlang.org/phobos/std_range_primitives.html

"It defines the bidirectional and forward range primitives for arrays: empty, front, back, popFront, popBack and save."

and where in this doc on range:

https://tour.dlang.org/tour/en/basics/ranges

it is mentioned that: the length property of the range will be changed after the range is "consumed" by foreach loop?

```
foreach (element; range)
{
    // Loop body...
}
it's internally rewritten similar to the following:

for (auto __rangeCopy = range;
     !__rangeCopy.empty;
     __rangeCopy.popFront())
 {
    auto element = __rangeCopy.front;
    // Loop body...
}
```


« First   ‹ Prev
1 2 3