Bug 7021 – Structs with disabled default constructors can be constructed without calling a constructor.
Status
RESOLVED
Resolution
FIXED
Severity
major
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2011-11-27T13:23:00Z
Last change time
2012-10-28T04:40:45Z
Keywords
accepts-invalid, pull
Assigned to
nobody
Creator
simen.kjaras
Comments
Comment #0 by simen.kjaras — 2011-11-27T13:23:25Z
struct Foo {
@disable this();
}
void main() {
auto foo = Foo();
}
The above code compiles and runs just fine on dmd 2.056.
Comment #1 by issues.dlang — 2012-07-09T20:22:25Z
This works just fine too (with dmd 2.060HEAD)
struct Foo
{
@disable this();
}
void main()
{
auto foo = Foo.init;
}
It looks to me like @disable this() isn't working at all.
Comment #2 by issues.dlang — 2012-07-27T17:30:18Z
*** Issue 8457 has been marked as a duplicate of this issue. ***
Comment #3 by simen.kjaras — 2012-09-21T06:06:56Z
*** Issue 8703 has been marked as a duplicate of this issue. ***
Comment #4 by k.hara.pg — 2012-09-21T06:26:06Z
(In reply to comment #1)
> This works just fine too (with dmd 2.060HEAD)
>
> struct Foo
> {
> @disable this();
> }
>
> void main()
> {
> auto foo = Foo.init;
> }
>
> It looks to me like @disable this() isn't working at all.
I think that built-in init property should be valid even if default ctor is disabled.
T.init shows runtime object initial bit-wise representation. So, in this case, Foo.init would be 1 byte zero filled memory, and should be accessible even if Foo is not default constructible.
But T.init sometimes does not *valid* object. See following example.
struct Bar
{
int value;
@disable this();
this(int v) { assert(v > 0); value = v; }
}
Bar's constructor appeals that Bar.value is initialized with natural number.
But Bar.init.value == 0, because it doesn't call valid ctor. Then T.init is *invalid* object.
This is not a defect of D language, but also it is a point you should be careful.
(In reply to comment #4)
> I think that built-in init property should be valid even if default ctor is
> disabled.
> T.init shows runtime object initial bit-wise representation. So, in this case,
> Foo.init would be 1 byte zero filled memory, and should be accessible even if
> Foo is not default constructible.
I agree, seeing as T.init can be @disabled. If someone really, *really* needs a T without calling its constructor, there are still ways.
Comment #7 by issues.dlang — 2012-09-21T08:51:34Z
Wait.
@disable this();
_is_ the way to disable init. If
@diasble this();
was used, then there should be no init property. That's the entire point of
@disable this;
Comment #8 by maxim — 2012-09-21T09:18:39Z
(In reply to comment #7)
> Wait.
>
> @disable this();
>
> _is_ the way to disable init. If
>
> @diasble this();
>
> was used, then there should be no init property. That's the entire point of
>
> @disable this;
Why? .init is a property which currently (2.060) can be hijacked. Dmd seems not to generate an implicit constructor function, it just initialize raw memory with default values when it faces S(). This is why disabling any function (ctors too) doesn't prevent it from creating S object.
Comment #9 by k.hara.pg — 2012-09-21T09:38:44Z
(In reply to comment #7)
> Wait.
>
> @disable this();
>
> _is_ the way to disable init. If
>
> @diasble this();
>
> was used, then there should be no init property. That's the entire point of
>
> @disable this;
I think that using T.init does not call any constructors. Therefore any
constructor declarations cannot stop its using.
----
I think a struct that has @disable this(); is similar to nested struct that
used outside of the valid scope.
void main() {
struct S { // nested struct
int n; void foo(){}
}
static assert(is(typeof(S.init.tupleof[$-1]) == void*)); // hidden frame ptr
check!S();
}
void check!T() {
T t1; // today this is rejected by fixing issue 8339
T t2 = T.init; // this is still valid
assert(t2.tupleof[$-1] is null); // but hidden frame ptr is null
// then, t2 is *invalid* object.
}
On the other hand, even if @disable this(); is declared, init property exists.
struct S {
@disable this(); // disable default construction
this(int n) { // valid construction with a parameter
// in here, the memory of 'this' is filled with *S.init*.
...logical initialization of 'this' object with ctor parameters...
}
}
T.init property does not guarantee that the returned object is logically
correctly initialized, but it always provides the initial bit representation of
T. I think this rule is reasonable also for @disable this(); struct.
Of course, you can select a following design:
"If struct has @disable this(); *and* no other ctors, init property is also
disabled."
But, it seems to me that is a special case which reduces the orthogonality and
increase the complexity.
Comment #10 by issues.dlang — 2012-09-21T12:19:15Z
It was my understanding that
@disable this();
was specifically the syntax to disable the init property. Certainly, trying to disable init directly doesn't work as far as I can tell. And since structs don't _have_ default constructors, I don't know what else
@disable this();
would be disabling (other than perhaps S(), which uses the init property anyway, since there's no default consturctor).
The whole point is to be able disable init so that you can _never_ use it for _anything_. You must _always_ directly initialize such a type (unless you directly initialize it with void).
Yes, normally, T.init is the value that the type has before any constructor is called, but if it's disabled, then it effectively does not exist, and it should be impossible to use T.init _anywhere_. Construction should work the same with any constructors that do exist (so the object state prior to construction is the same as what T.init would have been had it existed), but it should be impossible to default initialize such a type or to use it in any context which would require default initialization, and it should be impossible to reference that type's init in code anywhere. That's the whole point of disabling init, and
@disable this();
is specifically for disabling init. It doesn't affect declared constructors work, but it affects every other aspect of init.
Comment #11 by k.hara.pg — 2012-09-21T19:40:51Z
(In reply to comment #10)
> It was my understanding that
>
> @disable this();
>
> was specifically the syntax to disable the init property. Certainly, trying to
> disable init directly doesn't work as far as I can tell. And since structs
> don't _have_ default constructors, I don't know what else
>
> @disable this();
>
> would be disabling (other than perhaps S(), which uses the init property
> anyway, since there's no default consturctor).
I think @disable this(); _does_ disables just a default constructor, and doesn't disable init property.
As long as I repeated, init property represents a initial state of the object.
(In the language reference, it is called 'default initializer'; http://dlang.org/property.html#init ).
My first point is: init property does not construct anything.
Next, if any constructor is declared in struct S, S(...) is always calls the constructors, and _should not_ become struct literal expression.
This is my second point. S() would call this(), but it is disabled, then the compiler show an error so @disable function is used.
The third point is: the first and second are orthogonal. So I can agree that @disable this(); would forbid a usage of S(), but I don't think that S.init would also be forbidden.
> The whole point is to be able disable init so that you can _never_ use it for
> _anything_. You must _always_ directly initialize such a type (unless you
> directly initialize it with void).
>
> Yes, normally, T.init is the value that the type has before any constructor is
> called, but if it's disabled, then it effectively does not exist, and it should
> be impossible to use T.init _anywhere_. Construction should work the same with
> any constructors that do exist (so the object state prior to construction is
> the same as what T.init would have been had it existed), but it should be
> impossible to default initialize such a type or to use it in any context which
> would require default initialization, and it should be impossible to reference
> that type's init in code anywhere. That's the whole point of disabling init,
> and
>
> @disable this();
>
> is specifically for disabling init. It doesn't affect declared constructors
> work, but it affects every other aspect of init.
There is a way to disable explicit init property usage.
struct S {
@disable this(); // disable default construction
@disable enum int init; // dummy disabled init property, this is allowed
...
}
I think that the usage of S.init is at your own risk. S.init may generate invalid object, but it is sometimes useful. I hesitate that stopping such usage by the compiler.
At least, S() would be disabled by @disable this(); declaration. It is the point of this issue, and my pull request will fix it.
Comment #12 by issues.dlang — 2012-09-21T19:48:34Z
For structs, as long as there is no static opCall declared, S() and S.init should be identical, and I'd strongly argue that if one is disabled, the other should be disabled. They do exactly the same thing. It makes _no_ sense IMHO to allow S.init but allow S() or vice versa (as long as no static opCall is declared).
Comment #13 by k.hara.pg — 2012-09-21T20:19:53Z
(In reply to comment #12)
> For structs, as long as there is no static opCall declared, S() and S.init
> should be identical,
This is not already true for nested structs.
void main() {
int g = 10;
struct S {
int n;
auto foo(){ return g; }
}
auto s1 = S(); // StructLiteralExp
assert(s1.tupleof[$-1] !is null); // hidden ptr is filled
assert(s1.foo() == 10); // OK
auto s2 = S.init;
assert(s2.tupleof[$-1] is null); // hidden ptr isn't filled
assert(s2.foo() == 10); // Access Violation!
}
Comment #14 by issues.dlang — 2012-09-21T21:10:52Z
> This is not already true for nested structs.
Well, that's disgusting. There are way too many special cases here. I'd vote to just make S() identical to S.init even with nested structs. It's more consistent that way, especially since you can't have a default constructor making it so that S() can't possible refer to a default constructor.
Comment #15 by clugdbug — 2012-09-24T06:31:57Z
(In reply to comment #13)
> (In reply to comment #12)
> > For structs, as long as there is no static opCall declared, S() and S.init
> > should be identical,
>
> This is not already true for nested structs.
>
> void main() {
> int g = 10;
> struct S {
> int n;
> auto foo(){ return g; }
> }
> auto s1 = S(); // StructLiteralExp
> assert(s1.tupleof[$-1] !is null); // hidden ptr is filled
> assert(s1.foo() == 10); // OK
> auto s2 = S.init;
> assert(s2.tupleof[$-1] is null); // hidden ptr isn't filled
> assert(s2.foo() == 10); // Access Violation!
> }
That's a bug: .init for nested structs is garbage. It's the cause of bug 6419, for example. defaultInitLiteral() is wrong.
Comment #16 by issues.dlang — 2012-09-24T08:32:45Z
> That's a bug: .init for nested structs is garbage. It's the cause of bug 6419,
for example. defaultInitLiteral() is wrong.
Then I _definitely_ think that S() and S.init should always be the same for structs as long as static opCall has not been declared, and
@disable this();
should disable S.init such that it's impossible to ever default-initialize S or to use S.init or to use S() (though S() should still work if static opCall is declared).
Comment #17 by k.hara.pg — 2012-09-24T08:47:32Z
(In reply to comment #15)
> That's a bug: .init for nested structs is garbage. It's the cause of bug 6419,
> for example. defaultInitLiteral() is wrong.
Hmmm? Now all cases in bug 6419 run correctly. In current, by fixing bug 7965, TypeStruct::defaultInitLiteral always returns StructLiteralExp.
Comment #18 by k.hara.pg — 2012-09-24T09:10:23Z
(In reply to comment #16)
> Then I _definitely_ think that S() and S.init should always be the same for
> structs as long as static opCall has not been declared
I cannot agree with your this opinion.
If S() is identical with S.init, default construction of nested struct will cause Access Violation in anywhere. It means that s1 would be broken at the example in comment#13. It is terrible language semantics.
Comment #19 by monarchdodra — 2012-09-24T23:36:12Z
Erm, it was my understanding that @disable this() meant that it becomes illegal to initialize a struct to *just* .init (or call the .init property directly), and that a constructor must be called. The .init property still exists though, because construction can't actually happen without .init anyways...
------
import std.stdio;
struct S
{
int i;
S(int v)
{
i+=v;
}
}
void main()
{
// S s1; //Error: variable main.main.s1 initializer required for type S
S s3 = S(3);
writeln(s3.i);
}
------
3
------
The fact that this program outputs three is proof that s3 is first .init initialized, before the constructor is called proper. That's the only logical behavior (IMO) when you think of how D's initialization scheme works. If .init was truly disabled, the ONLY legal initialization would become:
----
S s = void;
----
THIS works though :/ ???
--------
void main()
{
S s2 = S(); //WHAT...?
S s3 = S(3);
writeln(s3.i);
}
--------
I side with Jonathan that S() and .init should be the same thing. There are no "default constructors" (or "no-arg", to my great dismay), in D, so having "S s = S();" do some things behind the scenes that "S s;" doesn't is just not conceivable from a language standpoint.
The fact that the stack pointer is not known at compile time in .init (I think, anyways) should not prevent "S s;" from properly initialized, be it by a stwo-step scheme if needed. That is a compiler implementation details, and users should (NEED) to remain blissfully unaware of it.
Either that, or we allow a true constructor that takes no argument:
S s; // .init
S s = S(); // this(){...}
But as of right now, it would seem there is a bastard of an hybrid that is half of both.
Comment #20 by github-bugzilla — 2012-10-11T09:17:23Z