Thread overview
Potential strategy for avoiding problems with copy of a struct (maybe??)
Aug 22, 2021
james.p.leblanc
Aug 22, 2021
Mathias LANG
Aug 22, 2021
jfondren
Aug 22, 2021
james.p.leblanc
Aug 22, 2021
jfondren
Aug 22, 2021
james.p.leblanc
Aug 22, 2021
jfondren
Aug 22, 2021
Ali Çehreli
Aug 22, 2021
james.p.leblanc
August 22, 2021

Hello,

Question about a possible strategy to avoid problems with
undesired/unintended copying of a structure:

  1. We have a struct, call this Foo.

  2. We instantiate it with, x = Foo(a,b,c);
    a. our constructor will initialize a field: this.myadd = &this
    b. this capture the address of our original "x" in x.myadd.

  3. We wish to allow, any function calls using x, so we cannot
    disable Foo's this(this).

  4. Our goal is to avoid problems with any accidental copying
    of x ... say by doing: auto y=x;

  5. the copy of step 4 does not use the constructor, thus y.myadd
    would contain the address of x (and not y)

  6. We can exploit that y.myadd does NOT contain its own
    address (but instead contains the address of x).

    This requires adding logic checks in any Foo opAssign,
    and other overloads. For example, we can disallow any
    such overloads by checking:

           if( &this != this.myadd ){ ... }
    
  7. Needing to operate on x with other functions implies that
    private or const is not a solution. (I believe.)

Some initial experiments lead me to believe this may acheive
part of what I would like. But, it feels very "hackish" and
ugly.

Is there a better way?

Best Regards,
James

August 22, 2021

On Sunday, 22 August 2021 at 07:58:12 UTC, james.p.leblanc wrote:

>

Is there a better way?

Best Regards,
James

public mixin template NonMovableOrCopyable ()
{
    @disable this ();
    @disable this (this);
    @disable ref typeof (this) opAssign () (auto ref typeof(this) rhs);
}

This will catch most mistakes at CT. However the language is technically free to copy / move structs at will (and interior pointers are forbidden).
A recent enough version of LDC (>= v1.20.0 IIRC, might be v1.22.0) will do a very good job at not needlessly moving things around.
See for example the discussion here: https://forum.dlang.org/thread/miuevyfxbujwrhghmiuw@forum.dlang.org

DMD on the other hand is much more likely to not perform NRVO / move things, so be wary of compiler differences.

August 22, 2021

On Sunday, 22 August 2021 at 07:58:12 UTC, james.p.leblanc wrote:

>

Hello,

Question about a possible strategy to avoid problems with
undesired/unintended copying of a structure:

  1. We have a struct, call this Foo.

  2. We instantiate it with, x = Foo(a,b,c);
    a. our constructor will initialize a field: this.myadd = &this
    b. this capture the address of our original "x" in x.myadd.

Sorry, I don't follow this at all.

Consider:

struct Foo {
    int a, b, c;
    Foo* myadd;

    this(int a, int b, int c) {
        myadd = &this;
        this.a = a;
        this.b = b;
        this.c = c;
    }
}

void main() {
    import std.stdio : writeln;

    Foo x; // initialized with (0, 0, 0, null)
    writeln(&x); // a memory location
    x = Foo(1, 2, 3); // (1, 2, 3, &x)

    writeln(&x); // the exact same memory location
    writeln(x.myadd.a); // 1, rather than 0, because &x==x.myadd
}

There's no saving the original "x" here, it's overwritten by the assignment. Are you sure you don't want classes instead, to get reference semantics?

>
  1. Our goal is to avoid problems with any accidental copying
    of x ... say by doing: auto y=x;

  2. the copy of step 4 does not use the constructor, thus y.myadd
    would contain the address of x (and not y)

auto y=x; actually does call object lifetime functions.

struct CountCopies {
    int copies;

    this(this) { // postblit
        copies++;
    }
}

unittest {
    CountCopies x;
    auto y = x;
    auto z = y;
    assert(x.copies == 0);
    assert(y.copies == 1);
    assert(z.copies == 2);
}

or with a copy constructor,

struct CountCopies {
    int copies;

    this(ref return scope const CountCopies rhs) { // copy constructor
        copies = rhs.copies + 1;
    }
    @disable this(this); // helps pre-copy-constructor compilers reject this code
}

unittest {
    CountCopies x;
    auto y = x;
    auto z = y;
    assert(x.copies == 0);
    assert(y.copies == 1);
    assert(z.copies == 2);
}
>

Some initial experiments lead me to believe this may acheive
part of what I would like. But, it feels very "hackish" and
ugly.

Is there a better way?

If you don't get an answer that you like, I suggesting posting functional code and then stating your dissastisfactions with it.

August 22, 2021

On Sunday, 22 August 2021 at 11:10:33 UTC, jfondren wrote:

>

On Sunday, 22 August 2021 at 07:58:12 UTC, james.p.leblanc wrote:

>

Hello,

If you don't get an answer that you like, I suggesting posting functional code and then stating your dissastisfactions with it.

Mattias, jfondren,

Thanks both for your replies, I always learn something from them.

I've trimmed my code to a minimal example to give a better idea
of my thinking on this. You will notice that to ensure that I
"seed" x with its real address at initialization, I must add @disable
this() to my struct.

import std.stdio;

struct Foo {
    int a, b, c;
    Foo* myadd;

    this(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.myadd = &this;
    }

    @ disable this();
}

void main() {

    // x is original, we wish to protect
    auto x = Foo(1, 2, 3);

    // y is the "bad copy", do not want operation on y to pollute original x
    auto y=x;

    writeln("&x: ",&x,", x.myadd: ",x.myadd,", the 2 agree, we have original!");
    writeln("&y: ",&y,", y.myadd: ",y.myadd,", these 2 disagree (must be a bad copy!");

}

Produces output:

&x: 7FFC65C02CC8, x.myadd: 7FFC65C02CC8, the 2 agree, we have original!
&y: 7FFC65C02CE8, y.myadd: 7FFC65C02CC8, these 2 disagree (must be a bad copy!

So, as stated, in my struct overloading I can check if my two values agree
or not (exposing whether or not I have the original, or a copy), and react
appropriately.

I understand that the language allows circumvention of this method... but
I just want to catch minor mistakes in programming.

Again, all comments and thoughts are welcome.

Best Regards,
James

August 22, 2021

On Sunday, 22 August 2021 at 13:03:20 UTC, james.p.leblanc wrote:

>

On Sunday, 22 August 2021 at 11:10:33 UTC, jfondren wrote:

>

On Sunday, 22 August 2021 at 07:58:12 UTC, james.p.leblanc wrote:

>

Hello,

If you don't get an answer that you like, I suggesting posting functional code and then stating your dissastisfactions with it.

Mattias, jfondren,

Thanks both for your replies, I always learn something from them.

I've trimmed my code to a minimal example to give a better idea
of my thinking on this.

I still really don't get what you're trying to do, to the point of wanting to know: are you making 'myadd' a field for this struct only so that you can check it against the address of the variable? Or is it a real example of data you want to protect from accidental copies that just, coincidentally, can also be used to check if the struct has been copied? And, still, are you sure that you want a struct and not a class?

The idea of not polluting the original of a struct requires some reference types as members of the struct, or a pointer, as otherwise everything is copied and the original is perfectly 'protected' anyway.

Here's a struct that

  1. has an int* as an example of data that shouldn't be copied, for example because it would result in a double free on destruction
  2. protects against bad copies, not by forbidding copies, but by nulling the int* with a postblit
  3. can then report that it's an original vs. a copy by seeing if the int* is null
import std.stdio;
import core.memory : pureMalloc, pureFree;

struct Foo {
    int a, b, c;
    int* unique;

    this(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
        unique = cast(int*) pureMalloc(int.sizeof);
        *unique = a * b * c;
    }

    void report() const {
        if (unique)
            writeln("I'm the original: ", *unique);
        else
            writeln("I am an incomplete copy :(");
    }

    this(this) {
        unique = null;
    }

    ~this() {
        pureFree(unique);
    }
}

void alwaysGetsACopy(Foo f) {
    assert(f.unique is null);
}

void reportIfOriginal(ref Foo f) {
    if (f.unique !is null)
        f.report;
}

void main() {
    auto x = Foo(1, 2, 3);
    auto y = x;

    x.report; // I'm an original
    y.report; // I'm a copy
    x.alwaysGetsACopy; // assertion succeeds
    x.reportIfOriginal; // I'm an original
    y.reportIfOriginal;
}
August 22, 2021
On 8/22/21 6:03 AM, james.p.leblanc wrote:

>      struct Foo {
>          int a, b, c;
>          Foo* myadd;
>
>          this(int a, int b, int c) {
>              this.a = a;
>              this.b = b;
>              this.c = c;
>              this.myadd = &this;

As Matthias Lang mentioned, keeping a reference to itself makes a struct object illegal in D. D sees structs as value types: It is supposed that any copy can be used in place of another copy.

Ali

August 22, 2021

On Sunday, 22 August 2021 at 13:37:50 UTC, jfondren wrote:

>
this(this) {
    unique = null;
}

~this() {
    pureFree(unique);
}

}

Dear jfondren,

I truly appreciate you taking the time to help me with my question!

This bit of magic with the postblit may hold the key to my issue.

(I have done some experiments based on your example code, am learning much,
and it looks VERY encouraging!

It is a bit scary how you guessed very closely what I am trying to
do. I have a AVX aligned pointers (obtained from fftw_malloc), that I
want to protect.

To be a bit more specific. The code that reads/writes from to/from fftw
routines is all pointer based. But, to allow use of standard dlang things
such as "foreach" and friends, I've been cobbling together a (naive) wrapper.

This allows me to use standard dlang syntax and operator overloads. I call my
struct "fakeArray" ... since it is meant to behave either as a static array,
or a dynamic array as needed.

It seems to work pretty well ... but protecting the arrays from accidental
programming mistakes (such as the "auto y=x") has been eluding me.

In fact, I can do a legitimate data copy via "y = x", as long as
y is already instantiated as a "fakeArray".

I must steady more the example you kindly provided (learn more about the
postblit, and pureFree, the const report function, etc...)

Best Regards,
James

August 22, 2021
On Sunday, 22 August 2021 at 14:35:48 UTC, Ali Çehreli wrote:
> On 8/22/21 6:03 AM, james.p.leblanc wrote:
>
> >      struct Foo {
> >          int a, b, c;
> >          Foo* myadd;
> >
> >          this(int a, int b, int c) {
> >              this.a = a;
> >              this.b = b;
> >              this.c = c;
> >              this.myadd = &this;
>
> As Matthias Lang mentioned, keeping a reference to itself makes a struct object illegal in D. D sees structs as value types: It is supposed that any copy can be used in place of another copy.
>
> Ali

Ali,

You highlight am important point that I have not been completely aware of.

Indeed, I did have a few "warning bells" going off in my brain when I began
typing "this.myadd = &this;" into my program ... but, it compiled, so I
continued (into the potential quicksand, admittedly!)

So, thanks for illuminating this issue for me.

Also, the "postblit" hint recently posted in this thread, may allow me
to procede along a safer path.

Kind Regards,
James

August 22, 2021

On Sunday, 22 August 2021 at 14:40:29 UTC, james.p.leblanc wrote:

>

It is a bit scary how you guessed very closely what I am trying to
do. I have a AVX aligned pointers (obtained from fftw_malloc), that I
want to protect.

To be a bit more specific. The code that reads/writes from to/from fftw
routines is all pointer based. But, to allow use of standard dlang things
such as "foreach" and friends, I've been cobbling together a (naive) wrapper.

This allows me to use standard dlang syntax and operator overloads. I call my
struct "fakeArray" ... since it is meant to behave either as a static array,
or a dynamic array as needed.

It seems to work pretty well ... but protecting the arrays from accidental
programming mistakes (such as the "auto y=x") has been eluding me.

OK, rather than roll your own solution, Unique!Foo might do what you want, from https://dlang.org/phobos/std_typecons.html#Unique