Good article overall. However, I must object to this bit:
"C represents nothing as 0 for primitive value"
That's not true. There is no "nothing" for integer types, and floats either also lack "nothing", or have NaN as their "nothing", depending on your perspective.
One of the (many) annoying features of C-style NULL is that it means that some types are effectively option types while other types aren't, and there's no decent way to make them so. An NSString* is inherently "pointer to NSString or nothing", but an int is always an int, and I have to either carve out a special value to mean "nothing" (e.g. -1 used as the error return from a lot of UNIX syscalls) or use a separate flag.
To clarify: I mean to say that 0 represents "nothing" in C in the same way that it does in math. This is one of the things I love about C--the ability to exploit the properties of numbers directly, rather than needing to build abstractions around them. Bitmasks, for example, are one of my favorite things ever.
But yeah, point well-taken. Perhaps I'll take the opportunity to wax on the other special values used in Foundation & CoreFoundation sometime.
0 isn't really "nothing", though. It's special, in that it's the additive identity of a lot of useful sets of numbers, but it's definitely something. An integer containing 0 is completely different from a pointer containing nil, conceptually speaking. To really have "nothing", you could use an int*, where a NULL pointer means "nothing", and a valid pointer means it contains a value. Or something more efficient with e.g. a separate flag. But in any case there's no real native support in the language for the concept.
Step back for a second and think about what the number 0 means. If I give you 0 dollars, what have I given you?
Or go to Wikipedia: 'The wThe word zero came via French zéro from Venetian zero, which (together with cypher) came via Italian zefiro from Arabic صفر, ṣafira = "it was empty", ṣifr = "zero", "nothing". ord zero came via French zéro from Venetian zero, which (together with cypher) came via Italian zefiro from Arabic صفر, ṣafira = "it was empty", ṣifr = "zero", "nothing".'
I'm well aware of both the meaning and etymology of the word.
Now, you should step back for a second and think about what the value 0 means in a program. Take the following function:
int secondsUntilNextEvent()
Does 0 mean "the next event is immediate", or does it mean "there is no interval because there is no next event"? 0 here does not necessarily mean "nothing".
An option type gives you a different state for "there is no value here, at all". Putting a zero into an int certainly does not signify "no value". It signifies a value, and that value is zero.
What number is greater than three but less than one? Nothing. There is no such number. "0" is not a correct answer.
Glad you are aware that in at least some contexts, zero really is nothing, rather than '0 isn't really "nothing", though', as you claimed earlier.
You write: 'Putting a zero into an int certainly does not signify "no value". It signifies a value, and that value is zero.'
Again, the way you write this makes it seem like you say that this has always been the case and is the only way it could be, when in fact this idea of zero being "just another value" is a fairly recent one. And the way our machines handle scalar values (and where what you write is largely true) is even more recent and more of a special case.
'What number is greater than three but less than one? Nothing. There is no such number. "0" is not a correct answer.'
'Nothing' is also not the correct answer. What you have is a contradiction, which is not nothing. Or you could talk about the possible results as sets, in which case you have the empty set, which is also not nothing. It does have the cardinality 0, though.
"The empty set is not the same thing as nothing; rather, it is a set with nothing inside it and a set is always something. "
Of course, the empty set can be used to signify "nothing", just as the number zero can.
"The number zero is sometimes used to denote nothing. The empty set contains no elements."
Again, not arguing that you can't have contexts in which "nothing" and "zero" are distinct. Just pointing out that those contexts are hardly universal enough to justify a statement saying that nothing really isn't zero.
Things are a bit more complicated and less clear-cut than that.
Which is why I always liked C's and Objective-C's somewhat loose but intuitively (for me!) workable handling of NULL, 0, nil, false etc.
Zero is not nothing. Sometimes it means nothing, which is not the same thing.
C pointer types are always option types. They can hold "nothing" or a pointer. Code that deals with pointers always has to deal with this option nature of the types, whether it wants the feature or not.
C primitive types are never option types. They can never hold "nothing". Some of them can hold a value that is sometimes used to represent "nothing", but again, that is not the same thing. Code that deals with primitive types and wants to be able to represent "nothing" either has to keep a separate flag or borrow a value from the primitive type's range to indicate "nothing" without any language support for that.
In short: I object to C mixing option types with pointer types. Life would be simpler if they were separate. NULL (nil etc.) is how C indicates "none" for pointers. C has no built-in indicator for "none" for primitives.
Yes. In a computer, there is nothing that is nothing. You always have something that signifies nothing.
C pointers also do not hold "nothing". What you refer to as "nothing" is the address 0, which is a special value that the language makes certain guarantees about that you interpret as being "nothing" because of these guarantees.
> C pointers also do not hold "nothing". What you refer to as "nothing" is the address 0, which is a special value that the language makes certain guarantees about that you interpret as being "nothing" because of these guarantees.
Of course. The point is that pointers have that "special value", while primitives do not. The integer value 0 is not the same thing.
You're both right. Zero does mean nothing. Also, zero is not a special value for integers like it is for pointers. Enough of this self-righteous debate.
Definitely spot on regarding 0 not representing 'nothing'.
However, 'int *' is inherently "pointer to int or nothing" (in the same style as your NSString example), so I'm not sure I agree with your assertion that some types are option types while some aren't in C. Any type can be an option type (in your sense of the term, again a la your NSString example) by making it into a pointer.
Similarly, using an NSString without a pointer (which might not be possible in ObjC, but definitely possible for regular C/C++ objects) makes it not optional, like the 'int'.
int and pointer to int are two different types. Pointer-to-int being an option type does not make int an option type. So there are still option types and non-option types.
(Using the prevailing terminology. I'm not sure I'm comfortable with this use of the term "option type", but I'm rolling with it for now.)
Precisely. It's not the case that "any type can be an option type" as shock-value stated. Rather, in C, all pointer types are option types whether you want them to be or not, and all other types are not.
> In other languages, like C++, this would crash your program, but in Objective-C, invoking a method on nil returns a zero value. This greatly simplifies expressions, as it obviates the need to check for nil before doing anything:
// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }
// ...can be simplified to:
if ([name isEqualToString:@"steve"]) { ... }
Wow, that is some bad, bad code. Consider this snippet instead:
You do raise an interesting point that a nil swallowing language forces you to write your methods in such a way as to logically return false for a nil receiver.
I'd argue this is the way most methods are naturally written, so it doesn't have too much of an impact.
I'm struggling to think of an example where logically returning true for a nil receiver is a much more natural than the other way around. Most of them, including your example of isEqualToString vs. isDifferenceFromString, are at best equal.
It's probably something that should be made more explicit in discussions about nil swallowing, such as the OP.
A container might have an isEmpty function, and if the pointer is nil, that is probably more empty than not empty (though personally I don't believe either answer makes sense). "[x isEmpty]" might be more appropriate than "[x count]==0" for a list-type container.
I always check for nil explicitly, personally. It sidesteps any question of whether the method makes sense in the nil case. And sending messages to nil can't work in general, because the return value could be a struct.
We check there will be an item before we get a value out of it. This one would cause issues.
NSArray doesn't have an isEmpty method. I wonder if this is one of the reasons why, or if it's just something like wanting to keep the interface small.
[Edit]
You can of course invert the logic. Using [container hasItems] in the two examples above would function just fine. While isEmpty seems to be the variant used everywhere, hasItems doesn't seem too unnatural.
NSArray has a constant-time size query, so you might as well do "[x count]==0". If you had a linked list type structure, though, you might just have next/prev pointers in your list object, making a size query an O(N) operation. (The C++ STL provides an "empty()" function in its containers for presumably this reason. It has some containers that have to be implemented as linked lists or trees, meaning there's the possibility that "x.empty()" to be much more efficient than "x.size()==0". Not sure how often implementations take advantage of this, mind.)
As for how you'd use it, I've pretty much always used it for early outs:
I dunno. I think a nil container is neither empty nor full, it's nil. But trying to reason about 3VL in an environment that doesn't support it is … trying.
I can't think of a compare method in the Objective-C core lib (the biggest nil-swallowing lib I know of). Does anyone know of one? Would be interesting to see how it's handled and if it causes issues.
You could return an enum, which would allow you to use non-obvious values (Lower = 1, Equal = 2, Higher = 3), though this would make it a bit of a pain to interface with C-style libs.
Obviously the presence of a work-around doesn't mean nil-swallowing is a good idea, but rather cements the idea that library authors need to put in extra effort to make sure nil receivers are handled appropriately.
> I can't think of a compare method in the Objective-C core lib (the biggest nil-swallowing lib I know of). Does anyone know of one? Would be interesting to see how it's handled and if it causes issues.
I'd argue this is correct. If a is nil, it's not comparable. The alternative would be to throw an exception, which is pretty simple to emulate by checking.
Also, nils aren't allowed in the collection classes, so sorting with compare: will simply never encounter this case.
That seems like a naming decision, I think it'd make more sense to have an equals selector than a not equals selector in objective c. Plus isEqualToString: is already part of NSString
That's not bad code, you must be sarcastic. I've never heard of the method isDifferentFromString: and besides, the method name suggests it's the same thing as isEqualToString: approached from the other side. Are you referring to the capitalization mistake in @"steve"? When using sarcasm keep in mind there's no tone in text ;)
Properties and methods are given "positive" names as a matter of convention, and to make reasoning about Nil easier.
In Objective-C, the question "should I name my method isEnabled or isDisabled?" has a reasonable answer - you consider what makes most sense (or any sense) when called on Nil.
This is probably a good rule for other languages too - "negative" booleans can lead to double-negatives and unreadable code.
Even with a positive naming convention, you will have conditionals for the negative case, e.g. if (![str isEqualToString:@"myvalue"]) and falling into its block is not desirable when str is nil. Personally I have had plenty of bugs around things being unexpectedly nil, but I can't think of a case like this where I executed a block unexpectedly.
I'm sorry, I should have been more clear. I meant "I've never heard of that method, so I looked it up in Xcode and it turns out it is not a method on NSString (and why should it be if !isEqualToString: achieves the same purpose?) which leads me to believe that this comment is sarcasm, if it isn't can you please clarify?"
It's worth noting that Go has interesting handling for empty values as well. A nil basically means "the zero value for a given data type". Thus, an empty array compares equal to nil: http://tour.golang.org/#36
That is incorrect. Certain types can be nil, and those types have a zero value of nil, but nil is not equal to the zero value for all types. You don't have a "empty array" there, you have an uninitialized slice, with no corresponding array at all, empty or otherwise. Uninitialized slices are nil, but that is not generally true, only for the specified types. If you initialize it, even to an actual "0 array" (as close as we can get), it ceases to be nil. See http://golang.org/ref/spec#The_zero_value , and w.r.t. the tour code, switch it to:
package main
import "fmt"
func main() {
var z []int = make([]int, 0, 0) // jerf changed this line
fmt.Println(z, len(z), cap(z))
if z == nil {
fmt.Println("nil!")
} else {
fmt.Println("initialized slice is not nil")
}
// This won't even compile; "cannot convert nil
// to type int" at the if clause:
// var a int = 0
// if a == nil {
// fmt.Println("jerf is wrong; nil does == 0")
// }
}
(Oh, and for those that don't know, on the other end of that tour link is the live Go tutorial, which allows you to run arbitrary Go code in your browser. You can fiddle with this live, with no installation of Go.)
Nice article, but I just wanted to throw out that if being able to call a class method on a null object in C++ is desirable, you can actually check for that condition instead of just crashing.
if(!this) return false; // Or whatever
Of course it's open for debate as to whether this is something to encourage, but it can help from having to use a Null Object pattern in some situations.
No, you really can't do this. If you think you can, tell me what you think the output of this program will be:
class X
{
int a;
public:
X() : a(123) { }
int get_a() { return this ? a : 1; }
};
class Y
{
int b;
public:
Y() : b(456) { }
int get_b() { return this ? b : 2; }
};
class Z : public X, public Y
{
int c;
public:
Z() : c(789) { }
int get_c() { return this ? c : 3; }
};
#include <iostream>
int main()
{
Z *z = NULL;
std::cout << z->get_a() << '\n';
std::cout << z->get_b() << '\n';
std::cout << z->get_c() << '\n';
}
That's a good point, calling a virtual method on a null pointer is the source of some backtraces that seem to start a few bytes past 0.
Naming things is always difficult, but I meant class method, although what I should have said was non-virtual class method, which is possible in C++. Both virtual and non-virtual have "this" pointers, the difference is whether the class method to call is looked up at runtime or compile-time, so I still would consider both "methods". I didn't learn OOP from Smalltalk so I may be abusing the terminology (it's not intentional).
I believe the correct terminology for C++ would be "member function", and specifically "non-virtual member function" for the ones that actually work with NULL. I also wouldn't be surprised if using a non-virtual member function with NULL is actually undefined behavior that just happens to work with popular compilers, although I'm too lazy to look up the standard to see.
"Class method" is usually used to refer to methods that class objects have themselves, as opposed to an "instance method", which is a method that instances of a class possess. C++ doesn't have class objects or class methods.
Just a reminder for C and C++ programmers: NULL considered harmful. Instead, use a literal 0 or (void * ) 0 in portable code.
In the C Standard, an implementation can define NULL as either 0 or (void * ) 0. For an example of how that can go terribly wrong, consider the following:
printf ("%p\n", NULL)
on an architecture where sizeof (int) != sizeof (ptr)
It's actually a different answer for C or C++ programmers.
NULL should be defined as ((void * )0) for C always (and you should define your own NULL to enforce that if necessary in varargs functions). This is safe because C allows conversion from void * to other pointer types.
C++ does not allow conversion from void * to other pointer types, so what you were supposed to do was use plain 0, which introduced exactly the issue you mention (and which has bitten me before trying to use a C library from C++, where NULL was defined as plain 0).
With C++11 you should use nullptr, which does the right thing. Some implementations define NULL in C++ to a vendor-provided symbol which emulates nullptr so it might be safe to use NULL. If you have C++98 only and don't want to risk NULL then you should use static_cast<T*>(0) (where T is the actual pointer type), which is admittedly clunky.
No! C and C++ are define NULL identically: it can be either 0 or (void * ) 0 at the discretion of the compiler author.
So the bug you mention which bit you by using "C library from C++" was not caused by a difference between C and C++. It was caused by a difference in the definition of NULL between those two compilers. You could just as easily have found a C++ compiler which did work, or a C compiler which didn't.
If you think a C++ compiler has the option to define NULL as ((void * ) 0) then all I ask is that you simply try it yourself and let me know what compiler error you get. Make sure to turn off permissive flags.
class Foo { };
int main() {
Foo *foo = malloc(sizeof(Foo)); // returns void*
}
That's because NULL is the pre-processor define. The null pointer itself has no name in C or C++ (pre-11), except that the language will convert integer '0' into whatever the hardware's null pointer is (usually also 0) when converting integral types to pointer types. C++11 added a name and specific type for the null pointer but that's about it.
You could use it to test whether or not you really got a class from NSClassFromString(). For instance:
Class _myClass;
// aClassName was previously defined
if((_myClass = NSClassFromString(aClassName)) == Nil)
[NSException raise:@"InvalidClass"
reason:@"Unknown class named %@.", aClassName);
id myObj = [[_myClass alloc] init];
[myObj whatever];
Java is not identical to Obj-C but the 'null' fiasco is mindboggling...
Back when the book *"Effective Java" came out, a truly great advice was to use empty arrays everywhere you could instead of null.
The second enlightenment came to me when the smart brains at JetBrains decided to ship IntelliJ IDEA with the @NotNull annotation. I basically started to annotate as many methods returning something as @NotNull and then saw the number of NullPointerException go drastically down.
It's not possible everywhere but once you start to think about it, you realize that you don't need nearly as many null references as you think you did.
Then you started having IntelliJ IDEA warning you real-time (even on incomplete source file) about "reference xxx is never going to be null" (so the non-null check is pointless) or "warning: reference to xxx may be null". Great stuff. Years later there are still 99% of all the Java codebase not using the @NotNull annotation. Sad but true.
Now of course other language's take on the subject are interesting too: the maybe monad, the way Clojure deals with empty / nil sequences, etc.
"C represents nothing as 0 for primitive value"
That's not true. There is no "nothing" for integer types, and floats either also lack "nothing", or have NaN as their "nothing", depending on your perspective.
One of the (many) annoying features of C-style NULL is that it means that some types are effectively option types while other types aren't, and there's no decent way to make them so. An NSString* is inherently "pointer to NSString or nothing", but an int is always an int, and I have to either carve out a special value to mean "nothing" (e.g. -1 used as the error return from a lot of UNIX syscalls) or use a separate flag.