Thread overview
state of an object
Jul 02, 2012
Namespace
Jul 02, 2012
bearophile
Jul 02, 2012
Namespace
Jul 02, 2012
Jonathan M Davis
Jul 02, 2012
Namespace
Jul 02, 2012
Jonathan M Davis
Jul 02, 2012
Namespace
Jul 02, 2012
Namespace
Jul 02, 2012
Jonathan M Davis
July 02, 2012
Is it possible to figure out how is the state of an object at compile time?
E.g. if the object is null or not:

class Foo { }

Foo f;

static if (is_null(f)) { }

July 02, 2012
Namespace:

> Is it possible to figure out how is the state of an object at compile time?
> E.g. if the object is null or not:
>
> class Foo { }
>
> Foo f;
>
> static if (is_null(f)) { }

In general you need a tool that analyzes D code statically (and maybe in some cases doesn't give a certain answer).

Bye,
bearophile
July 02, 2012
> In general you need a tool that analyzes D code statically (and maybe in some cases doesn't give a certain answer).
>
> Bye,
> bearophile

Short: not so easy. Too bad.
July 02, 2012
On Monday, July 02, 2012 08:19:52 Namespace wrote:
> Is it possible to figure out how is the state of an object at
> compile time?
> E.g. if the object is null or not:
> 
> class Foo { }
> 
> Foo f;
> 
> static if (is_null(f)) { }

What are you trying to test exactly? Whether f default initializes to null? That's as easy as

static if(typeof(f).init is null);

But if what you want to test is what f is initialized to  - such that you can get the value if it were

Foo f = initialize();

then no, you can't do that.  D won't let you use any global or static variable at compile time in order to avoid subtle errors caused by ordering and circular dependencies.

If f were an enum you could, but then it's a manifest constant rather than a variable, and you can't currenly initialize classes to anything other than null at compile time.

- Jonathan M Davis
July 02, 2012
My intention was to avoid something like this:

[code]
class Foo { }

Foo f; // f is null

NotNull!(Foo) test = f; // should throw an compiler error, because f is null
[/code]

I can avoid

NotNull!(Foo) test = null; with

@disable
this(typeof(null));

but how can i avoid null objects?
July 02, 2012
On Monday, July 02, 2012 10:41:03 Namespace wrote:
> My intention was to avoid something like this:
> 
> [code]
> class Foo { }
> 
> Foo f; // f is null
> 
> NotNull!(Foo) test = f; // should throw an compiler error,
> because f is null
> [/code]
> 
> I can avoid
> 
> NotNull!(Foo) test = null; with
> 
> @disable
> this(typeof(null));
> 
> but how can i avoid null objects?

The _only_ way to statically disallow assigning a null Foo to a NotNull!Foo object is to statically disallow assigning a Foo to it period. Its nullity is not part of its type and therefore cannot be checked statically. As far as I can tell, you have two choices:

1. Throw when a NotNull is assigned or constructed from a null Foo.

2. Disallow assignment and construction of a NotNull!Foo from any and all Foos - in which case the NotNull!Foo needs to construct any Foo that it contains.

- Jonathan M Davis
July 02, 2012
Can you show me an example of your two options?
I'm not sure what do you exactly mean.
July 02, 2012
I cannot create the Foo objects _in_ the NotNull struct because i need the reference of an specific object.
And if i throw an error if the object paramter is null, it's not a compiler error.
So there is no way that a null reference as paramter can be detect at compile time? I tried different __traits but i wondered that there is nothing like
__traits(ThrowError, { object.toString(); }) which would be perfect to detect that cases.
July 02, 2012
On Monday, July 02, 2012 17:25:03 Namespace wrote:
> I cannot create the Foo objects _in_ the NotNull struct because i
> need the reference of an specific object.
> And if i throw an error if the object paramter is null, it's not
> a compiler error.
> So there is no way that a null reference as paramter can be
> detect at compile time? I tried different __traits but i wondered
> that there is nothing like
> __traits(ThrowError, { object.toString(); }) which would be
> perfect to detect that cases.

The state of an object is entirely a runtime artifact. Take this function for example

void func(Foo foo) {}

How is the compiler going to know whether Foo is null or not? Whether foo is null or not is runtime state. You could have called it with func(null). You could have called it with func(new Foo(7)). You could have called it with func(bar()), and who knows what the value is that bar returns. It could be null. It could be Foo(42). It could be any valid value of Foo. The compiler can't know what the value of foo is anymore than it the sqrt function can know whether you're going to pass it a 0. That's all runtime-dependent.

Now that typeof(null) is its own type, it's possible to specialize on that and prevent a function from taking a null literal.

void func(T)(T value)
 if(is(T : Foo) && !is(T == typeof(null))
{}

or

void func()(typeof(null) value) {static assert(0;}
void func()(Foo value) {}

or in the case of a constructor

@disable this(typeof(this));

But _all_ that that prevents is null being passed directly - i.e. func(null) is illegal. But func(bar()) could still result in func being passed a null Foo if bar returns a null Foo.

Whether an object is null or not is not known until runtime and the compiler _cannot_ determine that for you. Object state is a runtime artifact. The only stuff that can be tested at compile time are types, whether a particular piece of code will compile or not, and the state of a particular variable which is created as part of CTFE. You can't test the state of variable which only exists at runtime. Its state doesn't exist yet!

On Monday, July 02, 2012 12:33:26 Namespace wrote:
> Can you show me an example of your two options?
> I'm not sure what do you exactly mean.

1. Throw if passed a null Foo:

struct NotNull(T)
 if(is(T == class) || isPointer!T)
{
 this(T value)
 {
 enforce(value !is null);
 }

 NotNull opAssign(NotNull rhs)
 {
 _value = rhs._value;
 }

 NotNull opAssign(Foo rhs)
 {
 enforce(value !is null);
 _value = rhs;
 }

 T get() pure nothrow
 {
 return _value;
 }

 alias get this;

private:
 T _value;
}

As an alternative to enforce, you can assert, which indicates that it's a bug in the program using NotNull if it ever assigns a null Foo to a NotNull!Foo, whereas with enforce, you're indicating that it's not a bug in the program if it occurs (it's just something that's considered an error when it occurs). With the enforce, it's perfectly okay for the program to not worry about assigning a null Foo to NotNull!Foo, whereas with an assertion, it needs to check in any case where it might occur in order to prevent passing a null Foo to a NotNull!Foo.

2. Don't ever construct or assign a NotNull!Foo from a Foo:

struct NotNull(T)
 if(is(T == class) || isPointer!T)
{
 this(Args...)(Args args)
 if(is(typeof(new T(args))))
 {
 _value = new T(args);
 }

 T get() pure nothrow
 {
 return _value;
 }

 alias get this;

private:
 T _value;
}


There's currently a pull request for Phobos which takes the approach of asserting that NotNull!Foo isn't passed a null Foo. Once it's been fixed up, it'll likely end up in Phobos:

https://github.com/D-Programming-Language/phobos/pull/477

- Jonathan M Davis