www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Structs are Not Plain: A call for empty struct constructors

reply FeepingCreature <feepingcreature gmail.com> writes:
Let me lay out the problem first.

We're doing unittesting with fixtures. A Fixture is a struct that 
contains default values and mocks of the class structure that is 
being tested. Unittests will have the form

```
unittest {
   with (Fixture()) {
   }
}
```

so that within the unittest the fields of the fixture can be 
easily accessed.

Now, from this follow a few requirements. First, Fixture *has* to 
be a struct. Why "has"? Because one thing the mocker does is 
check if its expectations were fulfilled, and it does that at 
scope exit with `~this()`. So the mocker has to be a struct - so 
Fixture has to be a struct, because only structs have a halfway 
cleanly defined end of lifecycle.

(`scope(exit) validate;` does *not* work - that's the sort of 
line you just know will get forgotten. We want validation to be 
automatic.)

So how to construct a Fixture? It can't be the `init` state, 
because there's classes in it. It can't be a constructor, because 
D structs cannot have empty constructors (because "why would you 
want that if they're just plain old data"). So we have to use 
`static Fixture opCall()`, aka "ssh, it's a constructor but we'll 
pretend it isn't." But that means we can't use a regular 
autogenerated constructor, because if we give the struct *any* 
constructor it disables `static opCall`.

(Why? I can only conclude that we have sinned by giving the 
struct a fake empty constructor, and the language has decided to 
punish us.)

What else follows from this? We can't use immutable - because 
immutable fields can only be set in a proper constructor. We 
could do horrible workarounds like `Fixture(null)`, but that's 
just making a bad situation worse.

So that's all terrible.

I think the deeper problem stems right from the original 
assumption that structs are "plain old data" and thus shouldn't 
be interested in construction. With their use in reference 
counting, with(), destructors, assignments... does anyone still 
really believe that? Structs have taken on a dual role of "plain 
old data, and also anything that needs customized behavior bound 
to lifecycle."

The decision to not allow `this()` stems from a time where that 
wasn't really on the table, and I think it should be overturned. 
Let `Struct s;` be Struct.init, by all means, but `Struct()` 
should call `this()`, if there is one.
Sep 19 2019
next sibling parent Dennis <dkorpel gmail.com> writes:
On Thursday, 19 September 2019 at 09:02:39 UTC, FeepingCreature 
wrote:
 We could do horrible workarounds like `Fixture(null)`, but 
 that's just making a bad situation worse.
The usual workaround is to have a function `fixture` that returns your default constructed `Fixture` struct. I don't know why default constructors are not allowed on structs though.
Sep 19 2019
prev sibling next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Thursday, 19 September 2019 at 09:02:39 UTC, FeepingCreature 
wrote:
 Let me lay out the problem first.

 We're doing unittesting with fixtures. A Fixture is a struct 
 that contains default values and mocks of the class structure 
 that is being tested. Unittests will have the form

 ```
 unittest {
   with (Fixture()) {
   }
 }
 ```
A workaround, wrap it with alias this in another struct: ``` import std.stdio; struct Foo { int a; this(int) { a = -1; } ~this() { writeln("~this(), a = ",a); } } struct Bar { Foo f = Foo(1); alias f this; } void main() { with(Bar()) { writeln(a); a = 42; } } ```
Sep 19 2019
next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Thursday, 19 September 2019 at 09:30:00 UTC, Nicholas Wilson 
wrote:
 A workaround, wrap it with alias this in another struct:

 ```
 import std.stdio;
 struct Foo
 {
     int a;
     this(int) { a = -1; }
     ~this() { writeln("~this(), a = ",a); }
 }

 struct Bar
 {
     Foo f = Foo(1);
     alias f this;
 }
 void main()
 {
     with(Bar())
     {
         writeln(a);
         a = 42;
     }
 }
 ```
Okay first of all that's brilliant, but please note that any solution has to pass our internal review and I would never even try to put this code in a pull request. :) The point is to end up with a *cleaner* solution.
Sep 19 2019
prev sibling parent Max Samukha <maxsamukha gmail.com> writes:
On Thursday, 19 September 2019 at 09:30:00 UTC, Nicholas Wilson 
wrote:
 A workaround, wrap it with alias this in another struct:

 ```
 import std.stdio;
 struct Foo
 {
     int a;
     this(int) { a = -1; }
     ~this() { writeln("~this(), a = ",a); }
 }

 struct Bar
 {
     Foo f = Foo(1);
     alias f this;
 }
 void main()
 {
     with(Bar())
     {
         writeln(a);
         a = 42;
     }
 }
 ```
Won't work - Foo(1) is evaluated at compile time.
Sep 19 2019
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
On Thursday, 19 September 2019 at 09:02:39 UTC, FeepingCreature 
wrote:
 (`scope(exit) validate;` does *not* work - that's the sort of 
 line you just know will get forgotten. We want validation to be 
 automatic.)
The test code is usually tested for alternative scenario to verify that it actually tests what it's supposed to test. Forgotten validation won't pass that.
Sep 19 2019
parent FeepingCreature <feepingcreature gmail.com> writes:
On Thursday, 19 September 2019 at 10:06:12 UTC, Kagamin wrote:
 On Thursday, 19 September 2019 at 09:02:39 UTC, FeepingCreature 
 wrote:
 (`scope(exit) validate;` does *not* work - that's the sort of 
 line you just know will get forgotten. We want validation to 
 be automatic.)
The test code is usually tested for alternative scenario to verify that it actually tests what it's supposed to test. Forgotten validation won't pass that.
`validate` is not actually part of the test in this case, it's part of dmocks and nothing bad happens if you forget to call it.
Sep 19 2019
prev sibling next sibling parent nkm1 <t4nk074 openmailbox.org> writes:
On Thursday, 19 September 2019 at 09:02:39 UTC, FeepingCreature 
wrote:
 What else follows from this? We can't use immutable - because 
 immutable fields can only be set in a proper constructor. We 
 could do horrible workarounds like `Fixture(null)`, but that's 
 just making a bad situation worse.
I have no idea why default constructors are disallowed, but your factory doesn't need to be opCall(). E.g., this seems to me a reasonable solution: struct Fixture { int a, b; static Fixture Default() { return Fixture(1, 2); } } void main() { import std.stdio; with (Fixture.Default) { writeln(a); writeln(b); } } (I actually like it more than default ctors, which is, of course, not a reason to disallow default ctors).
Sep 19 2019
prev sibling next sibling parent Ethan <gooberman gmail.com> writes:
On Thursday, 19 September 2019 at 09:02:39 UTC, FeepingCreature 
wrote:
 I think the deeper problem stems right from the original 
 assumption that structs are "plain old data" and thus shouldn't 
 be interested in construction.
I've made this point many times over the years with regards to C++ binding. One of my DConf talks explicitly points out that making a Mutex/CriticalSection object cannot be initialised with a postblit. I think I even pointed out the static opCall hack as well in that talk... but I disagree with it because the meaning is different between C++ and D (C++ -> zero initialisation; D -> call a function that isn't a constructor of any kind). Whenever someone says "RAII doesn't work in D" it's because struct default constructors are disallowed.
Sep 19 2019
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, September 19, 2019 3:02:39 AM MDT FeepingCreature via 
Digitalmars-d wrote:
 I think the deeper problem stems right from the original
 assumption that structs are "plain old data" and thus shouldn't
 be interested in construction. With their use in reference
 counting, with(), destructors, assignments... does anyone still
 really believe that? Structs have taken on a dual role of "plain
 old data, and also anything that needs customized behavior bound
 to lifecycle."

 The decision to not allow `this()` stems from a time where that
 wasn't really on the table, and I think it should be overturned.
 Let `Struct s;` be Struct.init, by all means, but `Struct()`
 should call `this()`, if there is one.
This has nothing to do with structs being POD. It has to do with how D was designed with the idea that every type has an init value which is known at compile time, and various operations rely on being able to simply blit that init value. Having default constructors would mean that none of that code could simply blit the init value but would instead have to construct the structs using the default constructor. It also could no longer rely on having a default value which works at compile time. If you want to see how much in D relies on the init value working, then disable default initialization on a struct and see how much it doesn't work with. Having disable this(); was hacked into the language later, and there's plenty of stuff that doesn't work when it's used (e.g. dynamic arrays won't work at all with types that can't be default-initialized, because they rely on being able to blit the init value). Classes avoid all of this, because they have an extra layer of indirection, and T.init is the init value for T's reference, meaning that it can just be null, whereas because structs can live directly on the stack, default initialization requires initializing the struct itself. Default construction of structs was a casualty of D's goal of avoiding objects being initalized as garbage like you get in C/C++. It certainly can be annoying at times, but in most cases, you can just use a factory function instead, and if you want to avoid such a struct ever accidentally being used via its init value instead of the factory function, then disable default initialization for that type. And for a use case like you're talking about here where you're using RAII without needing to actually put the object inside of a container (or anything else that would require the init value), it should work just fine. You'll just get something like with(Fixture.make()) { } or with(Fixture.cons()) { } or whatever instead of with(Fixture()) { } Adding default construction to the language would be a pretty big change, and I expect that you would need a DIP going over all of the pros and cons, taking into account exactly how the language relies on init in order to get it changed. Given how baked into the language init is, my guess is that such a DIP doesn't stand much of a chance, but I don't know. - Jonathan M Davis
Sep 19 2019
next sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis 
wrote:
It has to do
 with how D was designed with the idea that every type has an 
 init value which is known at compile time, and various 
 operations rely on being able to simply blit that init value.
We understand the rationale behind the design. The point is that the idea has never worked. If you define a destructor, you most certainly want the object to be non-trivially initialized at run time. New instances of that type, even "branded" with .init, are NOT initialized - they are in an invalid state. That is why almost every D struct with a destructor is compelled to implement those opCall/external constructor/lazy initialization hacks.
Sep 19 2019
next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
n Thursday, September 19, 2019 10:42:51 PM MDT Max Samukha via Digitalmars-d 
wrote:
 On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis
 wrote:
 It has to do

 with how D was designed with the idea that every type has an
 init value which is known at compile time, and various
 operations rely on being able to simply blit that init value.
We understand the rationale behind the design. The point is that the idea has never worked. If you define a destructor, you most certainly want the object to be non-trivially initialized at run time. New instances of that type, even "branded" with .init, are NOT initialized - they are in an invalid state. That is why almost every D struct with a destructor is compelled to implement those opCall/external constructor/lazy initialization hacks.
Sure, the situation is not perfect, but on the whole, IMHO, it works quite well. What it comes down to is that having init the way we do solves a set of problems that C/C++ has but introduces other problems. There is obviously some disagreement on whether the tradeoff is worth it. Personally, I don't think that the problems introduced merit trying to make it so that structs don't have their default value be init. Regardless, even if we all agreed that we needed to add default constructors for structs, that requires a DIP that goes into great detail about how that's going to work with everything that currently requires that init be the default value for a struct and that it be known at compile time. Given how init works in D, it is not a simple matter to add default constructors, and complaining about it in the newsgroup isn't going to change anything. An actual plan of action would be required, and Walter would have to be convinced that the result is better than what we currently have. Anyone here is free to put in that time and effort if they feel that strongly about it. - Jonathan M Davis
Sep 19 2019
prev sibling next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 20 September 2019 at 04:42:51 UTC, Max Samukha wrote:
 On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M 
 Davis wrote:
 It has to do
 with how D was designed with the idea that every type has an 
 init value which is known at compile time, and various 
 operations rely on being able to simply blit that init value.
We understand the rationale behind the design. The point is that the idea has never worked. If you define a destructor, you most certainly want the object to be non-trivially initialized at run time. New instances of that type, even "branded" with .init, are NOT initialized - they are in an invalid state. That is why almost every D struct with a destructor is compelled to implement those opCall/external constructor/lazy initialization hacks.
This is correct. Around more complex types, the blittability of "init" has always been a polite fiction at best, a schizoid inconsistency at worst. Consider the existence of disable this, consider that structs can have invariants, which would be completely useless if it had to apply to init - non-zero, non-null, non-empty, practically every invariant used in practice is violated in the init state. We put all this functionality on struct, when the simplest of it has already broken the POD myth. This is not a new thing. The language has been like this for years. This is not a fight - if there ever was a fight, POD has lost. Phobos is littered with the workarounds of bugs that stem from this confusion. I think it's time to give up and say "yeah, struct .init exists, don't call any methods on it, be extremely careful to dispose of it, preferably just store it in a union field so the broken destructor doesn't get run by accident, it sucks but what can you do." Doing it properly would require explicit lifetime control, but we're not gonna get that any time soon anyway. I'm not saying `this()` in a struct isn't broken. I'm saying it's not any worse broken than all these other broken things involving structs that we've already learnt to live with over the years, and in the meantime its absence is making code awkward for no realistic benefit.
Sep 19 2019
prev sibling parent reply Kagamin <spam here.lot> writes:
On Friday, 20 September 2019 at 04:42:51 UTC, Max Samukha wrote:
 That is why almost every D struct with a destructor is 
 compelled to implement those opCall/external constructor/lazy 
 initialization hacks.
It may be because the empty state is valid for them.
Sep 20 2019
parent Max Samukha <maxsamukha gmail.com> writes:
On Friday, 20 September 2019 at 08:20:05 UTC, Kagamin wrote:
 On Friday, 20 September 2019 at 04:42:51 UTC, Max Samukha wrote:
 That is why almost every D struct with a destructor is 
 compelled to implement those opCall/external constructor/lazy 
 initialization hacks.
It may be because the empty state is valid for them.
For some of them, it is.
Sep 20 2019
prev sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis 
wrote:
 with(Fixture.cons())
 {
 }
That is UB. The spec does not state clearly, in which cases RVO will be applied. One may not assume that the destructor won't be called again on the already destructed object.
Sep 19 2019
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, September 19, 2019 11:00:13 PM MDT Max Samukha via Digitalmars-
d wrote:
 On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis

 wrote:
 with(Fixture.cons())
 {
 }
That is UB. The spec does not state clearly, in which cases RVO will be applied. One may not assume that the destructor won't be called again on the already destructed object.
It's exactly the same as with(Fixture(42)) { } would be or with(Fixture()) { } would be if structs had default constructors. My point was that you can get the same behavior as with(Fixture()) { } right now by using a factory function. The syntax just ends up being a bit more verbose. It was the OP who wanted to actually use with in this manner, and if with(Fixture.cons()) { } didn't work, then they could just do something like auto f = Fixture.cons(); with(f) { } The issue of whether with allows you to construct a temporary that's valid for the entire scope of the with block is a separate issue from whether D should be altered to have default constructors. The OP just happened to use it in their example, and it does currently seem to work even if the spec is silent on the matter. Whether it ever gets changed to not work or the spec gets updated to define its behavior is irrelevant to the issue of default constructors. - Jonathan M Davis
Sep 19 2019
next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 20 September 2019 at 06:01:00 UTC, Jonathan M Davis 
wrote:
 My point was that you can get the same behavior as

 with(Fixture())
 {
 }

 right now by using a factory function. The syntax just ends up 
 being a bit more verbose. It was the OP who wanted to actually 
 use with in this manner, and if

 with(Fixture.cons())
 {
 }

 didn't work, then they could just do something like

 auto f = Fixture.cons();
 with(f)
 {
 }
For the record, the solution we've settled on as the "best compromise" is an external factory function feeding into a (generated) constructor. That *seems* to work, since the mocker at least is init/copy-safe, and usually none of the fields need to *reference* the other fields. But it's still a workaround that should not be necessary.
Sep 19 2019
prev sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Friday, 20 September 2019 at 06:01:00 UTC, Jonathan M Davis 
wrote:

 The issue of whether with allows you to construct a temporary 
 that's valid for the entire scope of the with block is a 
 separate issue from whether D should be altered to have default 
 constructors. The OP just happened to use it in their example, 
 and it does currently seem to work even if the spec is silent 
 on the matter. Whether it ever gets changed to not work or the 
 spec gets updated to define its behavior is irrelevant to the 
 issue of default constructors.

 - Jonathan M Davis
Ok, I agree that the unspecified copy elision semantics is a separate issue.
Sep 20 2019
parent reply Andrea Fontana <nospam example.com> writes:
On Friday, 20 September 2019 at 07:24:11 UTC, Max Samukha wrote:
 On Friday, 20 September 2019 at 06:01:00 UTC, Jonathan M Davis 
 wrote:

 The issue of whether with allows you to construct a temporary 
 that's valid for the entire scope of the with block is a 
 separate issue from whether D should be altered to have 
 default constructors. The OP just happened to use it in their 
 example, and it does currently seem to work even if the spec 
 is silent on the matter. Whether it ever gets changed to not 
 work or the spec gets updated to define its behavior is 
 irrelevant to the issue of default constructors.

 - Jonathan M Davis
Ok, I agree that the unspecified copy elision semantics is a separate issue.
I wonder why nobody suggest something like this: class MyClass {} struct Fixture { int i; immutable MyClass c; } auto fixture() { Fixture ret = { i : 10, c : new MyClass() }; return ret; } void main() { with(fixture()) { } } Andrea
Sep 20 2019
parent Max Samukha <maxsamukha gmail.com> writes:
On Friday, 20 September 2019 at 07:53:00 UTC, Andrea Fontana 
wrote:
 class MyClass {}

 struct Fixture
 {
   int i;
   immutable MyClass c;
 }

 auto fixture()
 {
   Fixture ret =
   {
     i : 10,
     c : new MyClass()
   };

   return ret;
 }

 void main()
 {
   with(fixture())
   {

   }
 }

 Andrea
How is that different from the function-as-constructor examples by the other posters? Anyway, the status quo is very unlikely to ever change, so I am shutting up.
Sep 20 2019
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 19 September 2019 at 09:02:39 UTC, FeepingCreature 
wrote:
 We're doing unittesting with fixtures. A Fixture is a struct 
 that contains default values and mocks of the class structure 
 that is being tested. Unittests will have the form

 ```
 unittest {
   with (Fixture()) {
   }
 }
 ```
I think you should treat a struct like you would treat a D array. would you do: ```
 unittest {
   with (new int[0]) {
   }
 }
``` ? I don't think so. You would put something else than the array default value to the with statement. Same goes for invariants. D code is not in an invalid state if `arr.length == 0 && arr.ptr == null` so an invariant would not check against that. Example of what is really invalid is `arr.length > 0 && arr.ptr != null` - that's the sort of thing invariants should check against, an instance that's not "honestly" null but is not fully constructed, or has impossible values. And for destructors. in this case, it would be something like: ``` for(auto elemPtr = ptr; elemPtr < ptr + length; elemPtr++) elemPtr.destroy; ``` If the array is in the default value, it's empty and this destructor will do nothing. Same for struct destructors, they should work for "null" values, just not do anything they would normally do. In fact, you need to do the above even if you never use `.init` values, as if you manually destroy on an instance, it will be set on the default value, and an another destructor will be run on it when it's lifetime ends. I know this all has the downside that you cannot implement more complex "never null" types in a practical way. But in fact, most of D stuff has "null" anyway: floats, chars, arrays, pointers, classes regardless of the definition. Only bools, integrals and enums (if you don't define one) do not, none of which are complex types. The philosophy AFAIK is that a type should always provide a null value, if it can be assigned normally "wrong" values without enlarging it.
Sep 20 2019
next sibling parent Dukc <ajieskola gmail.com> writes:
On Friday, 20 September 2019 at 07:48:25 UTC, Dukc wrote:
 Example of what is really invalid is `arr.length > 0 && arr.ptr 
 != null`
Meant `arr.length > 0 && arr.ptr == null`
Sep 20 2019
prev sibling next sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 20 September 2019 at 07:48:25 UTC, Dukc wrote:
 But in fact, most of D stuff has "null" anyway: floats, chars, 
 arrays, pointers, classes regardless of the definition. Only 
 bools, integrals and enums (if you don't define one) do not, 
 none of which are complex types. The philosophy AFAIK is that a 
 type should always provide a null value, if it can be assigned 
 normally "wrong" values without enlarging it.
Yeah and I don't much approve of all that either... how many unexpected crashes can be laid at the feet of objects being "segfault by default"? You're argument is basically "that's how it is, so that's how it should be." The proliferation of non-null invariants in our codebase begs to disagree.
Sep 20 2019
next sibling parent Dukc <ajieskola gmail.com> writes:
On Friday, 20 September 2019 at 07:58:42 UTC, FeepingCreature 
wrote:
 You're argument is basically "that's how it is, so that's how 
 it should be." The proliferation of non-null invariants in our 
 codebase begs to disagree.
Didn't mean to say that -just that the struct design is consistent with rest of the language. I personally like the current philosophy fairly much, not a lot of experience from doing things the other way so I'm not very qualified to make strong comments. Walter however has reasoning: https://digitalmars.com/articles/b81.html You can agree or disagree.
Sep 20 2019
prev sibling parent reply Kagamin <spam here.lot> writes:
On Friday, 20 September 2019 at 07:58:42 UTC, FeepingCreature 
wrote:
 Yeah and I don't much approve of all that either... how many 
 unexpected crashes can be laid at the feet of objects being 
 "segfault by default"? You're argument is basically "that's how 
 it is, so that's how it should be." The proliferation of 
 non-null invariants in our codebase begs to disagree.
When your code has a bug, a crash is the best outcome you can hope for.
Sep 20 2019
next sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 20 September 2019 at 08:24:29 UTC, Kagamin wrote:
 When your code has a bug, a crash is the best outcome you can 
 hope for.
Not so. The best outcome, and something D often strives for, is a helpful and informative compiler error.
Sep 20 2019
parent Kagamin <spam here.lot> writes:
On Friday, 20 September 2019 at 09:05:47 UTC, FeepingCreature 
wrote:
 On Friday, 20 September 2019 at 08:24:29 UTC, Kagamin wrote:
 When your code has a bug, a crash is the best outcome you can 
 hope for.
Not so. The best outcome, and something D often strives for, is a helpful and informative compiler error.
Formal verification hopefully solves it, but it's usually developed as a separate tool and still requires a lot of work(?).
Sep 23 2019
prev sibling parent Eugene Wissner <belka caraus.de> writes:
On Friday, 20 September 2019 at 08:24:29 UTC, Kagamin wrote:
 On Friday, 20 September 2019 at 07:58:42 UTC, FeepingCreature 
 wrote:
 Yeah and I don't much approve of all that either... how many 
 unexpected crashes can be laid at the feet of objects being 
 "segfault by default"? You're argument is basically "that's 
 how it is, so that's how it should be." The proliferation of 
 non-null invariants in our codebase begs to disagree.
When your code has a bug, a crash is the best outcome you can hope for.
And in the best case the language shouldn't encourage to introduce bugs.
Sep 20 2019
prev sibling parent reply Olivier FAURE <couteaubleu gmail.com> writes:
On Friday, 20 September 2019 at 07:48:25 UTC, Dukc wrote:
 I know this all has the downside that you cannot implement more 
 complex "never null" types in a practical way. But in fact, 
 most of D stuff has "null" anyway: floats, chars, arrays, 
 pointers, classes regardless of the definition. Only bools, 
 integrals and enums (if you don't define one) do not, none of 
 which are complex types. The philosophy AFAIK is that a type 
 should always provide a null value, if it can be assigned 
 normally "wrong" values without enlarging it.
I just want to point out that this isn't an inherently unsolvable problem. You can have a type system where leaving your variable uninitialized is statically forbidden, while using sum types (Optional!T, Result!(T,E), etc) to handle functions taking or returning "wrong" values. It doesn't even have to be as complex as Rust. Just disable default construction for types without a .init value, and encourage the user to use Optional!T for corner cases.
Sep 23 2019
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 23 September 2019 at 09:03:25 UTC, Olivier FAURE wrote:
 I just want to point out that this isn't an inherently 
 unsolvable problem. You can have a type system where leaving 
 your variable uninitialized is statically forbidden
The problem is containers like arrays. It is difficult to prove that every element has been initialized. Btw, having everything zero-initialized by default has a performance advantage (you can have a background thread zero out the unused memory pool).
Oct 04 2019