r/Compilers • • 3d ago

nullptr, zero initialization, is zero checks design question

Hi,

I'm working on mox (low level + metaprogramming) and currently in mox I have zero initialization for everything. For example:

x: T = 0;
x: []T = 0;
x: [10]T = 0;

Zero initializes the value. If T is struct, all fields gets zeroed, if it is fixed array, all elements are zeroed, if it is slice, it has ptr=0, len=0.

But the problem and question is with pointers. At first I thought adding explicit type for nullable pointers is a good thing. Because then you have explicit and readable contract on function arguments and you possibly could have some optimizations or checks in compiler. Because of this, not nullable pointer could not be initialized with zero (because it is semantically wrong).

But while I was writing in C++ and in mox I found that I never ever used nullable pointers. And not able to zero any pointer type just makes more headache. Having nil like in Odin instead of 0 makes not much sense for me. You just write more letters and think more about some special nil while 0 clearly says that you zero the value.

nullptr problem currently solved by having nullptr function:

fn foo(x: *i32) {}
fn foo(x: i32) {}

foo(nullptr(i32));

And in case we need zeroed value with expression, I have zeroed function:

fn foo(x: Smth) {}

foo(zeroed(Smth));

So the question is, what do you think about having explicit nil for zeroing and nullable pointers? How often do you really use it? Are there any other problems/things that I dont see here?

8 Upvotes

7 comments sorted by

3

u/yuehuang 3d ago

I added = default to mine. This applied to non-value types like structs as well. Sometimes enum don't start at 0.

1

u/morglod 3d ago

Its a good point. I thought about it, but I dont want constructors and want to follow "zero is initialization" principle. It is not hard constraint but it simplifies everything a lot.

1

u/whizzter 2d ago edited 2d ago

Personally I feel that 0-init is a useful middle step when going from very low-level languages to slightly higher level, however much meat in larger programs usually concerns more highlevel concerns, and while having good semantics for controlling how to make fast low-level code, being able to express higher level concerns in the language is better in the long run.

This is why C# is so successful in gamedev, it’s bloaty but a lot of things is possible quickly to prototype, and while you’ll have a time to clear out slow crap when nearing release, it’s got features to make it work well enough without adding an additional scripting language (so while C# isn’t 100%, something lowlevel if you add a scripting runtime isn’t always better).

Make common things easy, if that is 0-init then that’s the right thing, but facilitating immediate correct init helps a lot of logic be simpler (both in C# and C++ I can use non-null pointers(refs by semantic in C++) a large fraction of the time).

As a personal point where I think the is gaps is lower level languages to have facilities for static, stack, pingpong(buffer), etc arrays to be auto-sized/reused by compiler facilities so that you can init values into them, so there never is an ”uninitialized” state, just not existing or fully prepared. (Yes, this is a bit or functional thinking but there’s nothing inherently contradictory to lower level languages if you have in the design, and comptime helps that work better).

3

u/brat3108 2d ago

So the question is, what do you think about having explicit nil

I use 'nil' as a null value that works for all pointer types. (Probably I took it from Pascal.)

Its actual value, intepreted as bits, is understood to be all-zeros, but having a distinct type separate from an integer to me is useful.

How often do you really use it?

All the time. (I've just done a survey and it averages about once per 100 lines of code.)

Are there any other problems/things that I dont see here?

Not really; if it works, it works. This is not C where the language has to assume some bizarre hardware where a NULL pointer might not be all-bits-zero.

Although it looks odd initialising aggregate types with a single zero too.

For aggregates, I tried using the special expression empty:

   A = empty

But in the end I made it a statement:

   clear A

1

u/WittyStick 3d ago edited 3d ago

You probably want both nullable and non-nullable pointer types, with the subtyping relation

non-nullable <: nullable

Any non-nullable pointer can implicitly be used where a nullable one is expected, but where we expect a non-nullable pointer, we can't use an arbitrary nullable pointer - we must downcast it to a non-nullable one.

This downcast can be made implicit using Definite Assignment Analysis. If we've previously set a nullable variable to a non-null value, we can treat it as a non-null type. Eg, suppose we have ?i32 indicating a nullable pointer and !i32 indicating a non-nullable pointer.

x: !i32 = 0; // compiler error - can't assign null to non-nullable type.

y: ?i32 = 0; // this is fine.

fn foo(x: !i32) { }

foo(y); // compiler error - y is nullable but parameter is not-null.

if (y != null)
    foo(y); // not a compiler error because DAA has proved y cannot be null here.
else
    foo(y); // obviously a compiler error. DAA knows y is null.

a: !i32 = 1;
b: ?i32 = x; // implicit upcast to nullable
foo(b); // not a compiler error - DAA knows y is definitely assigned.

1

u/morglod 2d ago

Thank you for you answer, but I already implemented it (but without conditional stuff).

Will be great if you share your experience with using nullable pointers in other languages and what do you think about it overall?

1

u/ZachVorhies 2d ago

I don’t get it

If you have no null pointers then you don’t have default construction

End of story