The generated opAssign function has the following signature:
*---
*ref S opAssign(S s) // S is the name of the struct
*---
The opAssign function will be built for a struct S if the
following constraints are met:
1. S does not have an identity opAssign defined.
2. S has at least one of the following members: a postblit (user-defined or
generated for fields that have a defined postblit), a destructor
(user-defined or generated for fields that have a defined destructor)
or at least one field that has a defined opAssign.
3. S does not have any non-mutable fields.
If S has a disabled destructor or at least one field that has a disabled
opAssign, S.opAssign is going to be generated, but marked with @disable
If S defines a destructor, the generated code for opAssign is:
*---
*S __swap = void;
*__swap = this; // bit copy
*this = s; // bit copy
*__swap.dtor();
*---
Otherwise, if S defines a postblit, the generated code for opAssign is:
*---
*this = s;
*---
Note that the parameter to the generated opAssign is passed by value, which means
that the postblit is going to be called (if it is defined) in both of the above
situations before entering the body of opAssign. The assignments in the above generated
function bodies are blit expressions, so they can be regarded as memcpys
(opAssign is not called as this will result in an infinite recursion; the postblit
is not called because it has already been called when the parameter was passed by value).
If S does not have a postblit or a destructor, but contains at least one field that defines
an opAssign function (which is not disabled), then the body will make member-wise
assignments:
Build opAssign for a struct.
The generated opAssign function has the following signature: *--- *ref S opAssign(S s) // S is the name of the struct *---
The opAssign function will be built for a struct S if the following constraints are met:
1. S does not have an identity opAssign defined.
2. S has at least one of the following members: a postblit (user-defined or generated for fields that have a defined postblit), a destructor (user-defined or generated for fields that have a defined destructor) or at least one field that has a defined opAssign.
3. S does not have any non-mutable fields.
If S has a disabled destructor or at least one field that has a disabled opAssign, S.opAssign is going to be generated, but marked with @disable
If S defines a destructor, the generated code for opAssign is:
*--- *S __swap = void; *__swap = this; // bit copy *this = s; // bit copy *__swap.dtor(); *---
Otherwise, if S defines a postblit, the generated code for opAssign is:
*--- *this = s; *---
Note that the parameter to the generated opAssign is passed by value, which means that the postblit is going to be called (if it is defined) in both of the above situations before entering the body of opAssign. The assignments in the above generated function bodies are blit expressions, so they can be regarded as memcpys (opAssign is not called as this will result in an infinite recursion; the postblit is not called because it has already been called when the parameter was passed by value).
If S does not have a postblit or a destructor, but contains at least one field that defines an opAssign function (which is not disabled), then the body will make member-wise assignments:
*--- *this.field1 = s.field1; *this.field2 = s.field2; *...; *---
In this situation, the assignemnts are actual assign expressions (opAssign is used if defined).
References: https://dlang.org/spec/struct.html#assign-overload