What is the case against a struct post-blit default constructor?

foobar foo at bar.com
Wed Oct 10 12:11:29 PDT 2012


Thank you for explaining.
See comments inline.

On Wednesday, 10 October 2012 at 18:12:13 UTC, Jonathan M Davis 
wrote:
> On Wednesday, October 10, 2012 13:40:06 foobar wrote:
>> Can you please elaborate on where the .init property is being
>> relied on? This is an aspect of D I don't really understand.
>> What's the difference between a no-arg ctor and one with args 
>> in
>> relation to this requirement?
>
> init is used anywhere and everywhere that an instance of a type 
> needs to be
> default-initialized. _One_ of those places is a local variable. 
> Not all places
> where an instance of an object needs to be created can be 
> initialized by the
> programmer. The prime example of this would be arrays. If you 
> declare
>
> auto i = new int[](5);
>
> or
>
> int[12] s;
>
> all of the elements in the array need to be initialized or 
> they'll be garbage,
> and there's no way for the programmer to indicate what values 
> they should be.
> The whole point of init is to avoid having variables ever be 
> garbage without
> the programmer explicitly asking for it. And having a default 
> constructor
> wouldn't help one whit with arrays, because the values of their 
> elements must
> be known at compile time (otherwise you couldn't directly 
> initialize member
> variables or static variables or anything else which requires a 
> value at
> compile time with an array). With init, the compiler can take 
> advantage of the
> fact that it knows the init value at compile time to 
> efficiently initialize the
> array.
>

I understand the idea of default initialization. I was more 
interested in the machinery and implementation details :) So 
let's dive in into those details:
Arrays - without changing existing syntax we can use these 
semantics:

auto a = new int[](5); // compiler calls T() for each instance
int[12] b; // ditto

This would be same as in C++. We could also expand the syntax and 
allow:
auto b = new int[](5, 9); // init all instances to 9
auto b = new int[](5, int (int index) { return index; });
initializes each member via a function call.
This can be generalized for multi dimensions.

> But even constructing objects sanely relies on init. All 
> user-defined objects
> are fully initialized to what their member variables are 
> directly initialized
> to before their constructors are even called. In the case of a 
> struct, that's
> the struct's init value. It's not for a class, because you 
> can't have a class
> separate from its reference (so it's the reference which gets 
> the init value),
> but the class still has a state equivalent to a struct's init 
> value, and
> that's the state that it has before any of its constructors are 
> called.

So for classes .init is null which complicates non-nullable 
classes. It seems the "solution" (more like a hack IMO) of 
@disable _breaks_ the .init guaranty in the language.

>
> If it weren't for that, you'd get the insanity that C++ or Java 
> have with
> regards to the state of objects prior to construction. C++ is 
> particularly bad
> in that each derived class is created in turn, meaning that 
> when a constructor
> is called, the object _is_ that class rather than the derived 
> class that
> you're ultimately constructing (which means that things can go 
> horribly wrong
> if you're stupid enough to call a virtual function from a 
> constructor in C++).
> I believe that Java handles that somewhat better, but it gets 
> bizarre ordering
> issues with regards to initializing member variables that cause 
> problems if
> you try and alter member variables from base classes inside of 
> a derived
> constructor. With D, the object is guaranteed to be in a sane 
> state prior to
> construction.
>

C++ is insanely bad here mainly due to [virtual?] MI which 
doesn't affect D
and Java _allows_ virtual methods in constructors, which I think 
is also "fixed" in the latest c++ standard. I don't know about 
the ordering problems you mention but AFAIK the complication 
arises with MI, not default initialization. It's just a matter of 
properly defining the inheritance semantics.



> And without init, even if every place that an object is 
> instantiated could be
> directly initialized by the programmer (which it can't), then 
> you would either
> end up with garbage every time that a variable isn't directly 
> initialized, or
> you'd have to directly initialize them all. In order for D's 
> construction
> model to work, this would include directly initializing _all_ 
> member variables
> even if the constructor then set them to something else (which 
> would actually
> cause problems with const and immutable). And that would get 
> _very_ annoying,
> even if it would be preferable for the local variable to 
> require explicit
> initialization.

You talk about:
class C {
immutable T val; // what to do here?
this() { ... }
}

This can be solved be either requiring a ctor call at # or if 
none specified call T(), or we can require the init to happen in 
the ctor a-la C++ semantics.




>
> Another case where init is required is out parameters. All out 
> parameters are
> set to their init value when the function is called in order to 
> avoid bugs
> caused by reading the value of an out parameter before it's set 
> within the
> function. That wouldn't work at all without init.

Personally, I'd just get remove this feature from the lanuage, 
tuples are a far better design for returning multiple values and 
even with this feature intact, we could always use the default 
no-arg constructor.
E.g
void foo(out T val);
becomes:
void foo(out T val = T());

>
> One of the more annoying AA bugs makes it so that if the foo 
> function in this
> code
>
> aa[5] = foo();
>
> throws, then aa[5] gets set with a init value of the element 
> type. While this
> clearly shouldn't happen, imagine how much worse it would be if 
> we didn't have
> init, and that element got set to garbage?
>

I don't get this example. If foo throws than the calling code 
will get control. How would you ever get to read that garbage in 
aa[5]? The surrounding try catch block should take care of this 
explicitly anyway.

E.g.
try {
  aa[5] = foo(); // foo throws
  // ## do something with aa[5], this won't happen
} catch {
// Please handle aa[5] here explicitly.
//
}
// @@ do something with aa[5], works due to the explicit fix in 
the catch.




> There are probably other cases that I can't think of right now 
> where init gets
> used - probably in the runtime if nowhere else. Every place 
> that could
> possibly result in a variable being garbage _doesn't_ result in 
> garbage,
> because we have init.
>
> And regardless of what the language does, there are definitely 
> places where the
> standard library takes advantage of init. It uses it a lot for 
> type
> inferrence, but it also uses it directly in places such as 
> std.algorithm.move.
> Without init, it would end up dealing with garbage values. It's 
> also a
> lifesaver in generic code, because without it, generic code 
> _can't_ initialize
> variables in many cases. Take something like
>
> T t;
>
> if(cond)
> {
>  ...
>  t = getValue();
>  ...
> }
> else
> {
>  ...
>  t = getOtherValue();
>  ...
> }
>
> How on earth could a generic function initialize t without 
> T.init? void?
> That's just begging for bugs when one the paths doesn't 
> actually set t like
> it's supposed to. It doesn't know anything about the type and 
> therefore
> doesn't know what a reasonable default value would be, so it 
> can't possibly
> initialize t properly.
>

Isn't @disable breaks those algorithms in phobos anyway? how 
would that work for non-nullable classes?
To answer the above question, I'd say there's nothing wrong with 
init to void. This is what happens anyway since the .init isn't 
used and the optimizer will optimize it away.

> I can understand prefering that local variables have to be 
> directly
> initialized by the programmer, but it just doesn't scale. 
> Having init is
> _far_more flexible and far more powerful. Any and every 
> situation that might
> need to initialize a variable can do it. Without init, that 
> just isn't
> possible.
>
> - Jonathan M Davis

Again, thanks for the explanation. I have to say that on a 
general level I have to agree with Don's post and I don't see how 
the .init idiom generally "works" or is useful. I can't see 
anything in the above examples that shows that .init is 
absolutely required and we can't live without it. The only thing 
that worries me here is the reliance of the runtime/phobos on 
.init.


More information about the Digitalmars-d mailing list