What is wrong with NULL
lucidchart.com
lucidchart.com
Python's problem, meanwhile, is that any parameter could be any type. In C, you can pass NULL to a function expecting a non-null string. In Python, you can also pass 42, [], or the sqlalchemy module. None isn't special here. :)
Also, to quibble a bit: Rust's std::ptr::null() is just a raw pointer, and isn't actually usable in safe Rust. Actual safe pointer types are guaranteed non-null and you'd use Option<&T> to specify that they're nullable. Raw pointers cannot be dereferenced in safe Rust. It is true that std::ptr::null() is in Rust's standard library, but Foreign.Ptr.nullPtr is in Haskell's, and the purpose is the same (a special raw-pointer type only used for FFI purposes), so Rust isn't any worse than Haskell here.
...the interpreter's environment, my left elbow, the magic unicorn that lives under my steps,...
Ceci n'est pas une corne.
Sometimes a horn is just a cigar.
I'm not saying static typing has no value, but there are benefits to dynamic typing, and Python embraces its dynamic typing and is designed around it, so none of the problems given in the article really apply.
That could be the greatest intro sentence ever seen on Hacker News.
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)
int NAME(some_function)(int a);
From:http://stackoverflow.com/questions/1489932/how-to-concatenat...C preprocessor can be confusing at times.
https://github.com/ilinsky/xmlhttprequest/blob/master/XMLHtt...
The article is great, don't get me wrong, and I wouldn't have mentioned anything if you hadn't brought up this point, but I thought I would just provide a counter-point.
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
Or from this blog post's own example: option.ifPresent(x -> System.out.println(x));
In other words, I think the core problem still hasn't been attacked and we may end up in the same situation we were in with exceptions originally: programmers will just throw up their hands and wrap everything in a Maybe/Optional and/or just maybe-protect until the compiler stops bugging them. At the end of the day, if you ? all your values then you end up with something equivalent to having everything be nullable and correctly null-checking them.Obj-C had a form of this with nil calling of methods silently doing nothing (so you end up with methods that "conveniently" don't happen, and don't crash! when the receiver is nil). However, despite having lots of legitimate uses (don't bother checking your delegate exists), it can still very silently sneak in to other parts of the code.
None => raise Error
but practically speaking, when deciding it's time to make your code more robust, it's a lot easier to text search for instances of "None => raise" than to search for the absence of proper handling of the possibility of a null arg or return value.
You see None => raise Error, and a little alarm goes off in your head, and you look around for a reassurance that we really want to do that.
In contrast, you don't give x.toUpperCase() a second glance.
I don’t agree that lazy programmers will add optionality to please compilers rather than fix their program logic. In languages where optional types are ubiquitous, you usually see people avoiding needless optionality, because it’s simply easier.
For me it is the worst part of Objective-C. Instead of getting a crash at nil, you will get a weird program behavior later because something wasn't called.
And I never use this feature because it complicates code flow and brings no benefits.
Lisp's NIL is brilliant. It's in its own type, the null type! And it's the only instance of that type. No other type has a null instance.
Null references in the language simply reflect the tension between the static type system which is supposed to assure us that if static checks pass, then values are not misused, and the need to have sometimes (at the very least) a "Maybe" type: maybe we have an object, or maybe not. Null references are the "poor hacker's Maybe": let's make every type support a null value, so that Maybe shows up everywhere.
Maybe, or something like the Lisp NIL, are themselves not mistakes by any stretch of the imagination. Sometimes you really need the code to express the idea that an object isn't there: directly, not with hacks like some representative instance, and some booelan flag which says "that's just a decoy indicating the object isn't there". You want the exception if the decoy is used as if it were an object; that's a bug. Something is trying to operate on an object in a situation when there is no object; safely operating on a decoy just sweeps this under the rug. (Yet, not always: sometimes operating on the decoy is fine. Lisp's NIL lets us specialize a method parameter to the NULL class and deal with it that way in one place.)
Of course, if you want to use NIL as a character string or floating-point value, there is an exception. That's your fault: you needed a way to indicate "I don't have a string here" or "I don't have a number", and you bungled the logic.
And there's the rub: Just like NULL. And, in fact, that's the major problem with NULL. NIL is better than NULL, but not by much.
NIL is probably the best you can get once you realize that the empty state is unavoidable. My personal problem with this kind of rant is that is full of hate but lacking in general in alternatives to represent an empty state.
The author mention optional types as an alternative. That's fine for parameters but that doesn't cover when you need to have an empty reference.
There should be a way to express Maybe<Maybe<T>>, aka an optional optional value.
The Store article in the article is an example of when this would be needed: a cache optionally has an optional phone number for a person.
If Lisp has the equivalent of Maybe<Maybe<T>> (perhaps a singleton list of a singleton list?), then the optionality is composable. Otherwise, the optionality is not really composable, and you will have problems.
(defmethod quack ((foo string))
... ;; foo is never anything but a string here.
... ;; definitely not nil!
)
(defmethod quack ((foo null))
... ;; foo is definitely nil here
)
Unlike in languages where you have a String reference that can be null, a CLOS method parameter specialized to a string cannot be NIL!We can also have this piece of minimalism, quite often recurring in Lisp code:
;; yield x if it is not nil, else yield y
(or x y)
Or play hardball: ;; if calculation signals an error,
;; catch it and keep going
(ignore-errors (calculation x))
After programming in Lisp for a while you would never write: (if (not (null x)) ...)
because it is verbose way of expressing: (if x ...)
Every value is its own test for non-nullness, since every value is a generalized boolean which stands for falsehood if it is nil, and truth otherwise.C and Haskell for example have a void type, that is not null, and in Haskell has a value. But a static void is completely different from a null.
Can you describe when you want to directly have an object that isn't there? If you want a "decoy object" that faults if used... why isn't Maybe perfect for that?
I like how swift handles things. strong type system that makes it a little painful to do things unsafely. guarantees some things that make it a powerful and more safe language than objective c. I think they lost a few dynamic patterns though in the transition but that's just what i recall from a blog post and the details of it are a little fuzzy.
NULL has one instance, the object NIL. NULL isn't derived from anything; its superclass is the master superclass T.
The NIL type has an empty domain: no object is of the NIL type. (Somewhat like in C and C++, no object is of the void type, for those who like pointless C and C++ analogies that don't lead anywhere.) Furthermore, the NIL type is a subtype of every other type, the same way that T is a supertype of every other type. That is to say, every type is implicitly a supertype of nil. This goes hand in hand with the fact that the set of instances of any type is a superset of the empty set. E.g. set of integers (a type) has an empty subset (every set does), and that empty subset can be identified as the NIL type, whose domain is the empty set.
class Connection {
//Error instances
BAD_ADDR("The given address was not correct...")
...
}
These constants should throw, just like a null value would, but with the specific error-state message, and should be testable for both the general and specific cases.Try/catch has its place, but the persistence of null-as-error-state behavior among programmers shows that signalling-via-return-value is a practical and intuitive way to handle error states. We should make that better, rather than simply tipping our nose at null.
enum Connection {
ValidConnection {fd: RawFd, address: SockAddr, ...},
BadAddr,
BadPort,
...
}
In fact Rust's representation of the maybe type is just a generic enum Option<T> {
Some(T),
None,
}
so all you're doing is getting rid of the two layer Option<Connection> syntax and making it a single Connection, so you're not writing Option<this>, PossiblyBadAddress<that>, etc. everywhere.Then all your call sites can do one of two things. Either they can check for specific errors, like
match conn {
Connection {fd, address, ..} => write(fd, ...),
BadAddr => /* handle error */,
BadPort => /* handle error */,
}
or you can write a simple function that throws an exception, if you really want an exception-handling style: impl Connection {
fn get_fd(&self) -> RawFd {
match *self {
ValidConnection {fd, ..} => fd,
BadAddr => panic!("The given address was not correct"),
BadPort => panic!("The given port was not correct"),
}
}
}
and then call conn.get_fd(). For a simple command-line app, panicking and aborting is pretty much what you'd want to do anyway. For a test suite, panics are caught per test case (and you can even test that a function does panic with a given message), so it's very testable.- You don't need to define a constructor to take the error string
- You don't need to implement method dispatch (all methods of an error state instance automatically throw, like null does)
Enums are close, but not quite right.
You might, for convenience, have a single method that turns a Connection to a ValidConnection, or else throws, and you can encode your error messages in one place in this method.
I think this proposal permits such a thing, although I haven't read it closely yet: http://smallcultfollowing.com/babysteps/blog/2015/08/20/virt...
Sounds like you want to go a step beyond Maybe to Either.
That is to say, there's nothing wrong with null: There's something wrong with null-as-error, but it's not the blankness of the null that's the problem.
The user is frustrated by this: Errors are fixable, so more important than more types and more maybes is that the errors communicate how to resolve them and what happens next:
connection new_connection(address) {
if(!address_valid(address)) return new_connection(prompt("The given address was not correct...", address));
...
Look: The user is prompted to correct the address in an interactive implementation, but a non-interactive (command-line) instance can implement prompt() as a combination of perror() and exit().Then consider the following:
* Start writing a large file * Run out of disk space * Delete the whole file and return an error
The user has been frustrated by this for decades! It should be:
again: ret=write(...);
if(ret == -1) switch(errno) {
case ENOSPC: wait(ENOSPC); goto again;
...
and yet very few languages or environments have useful implementations of prompt() and wait() despite how trivial it is (even in C)! Even fewer libraries make shoehorning correct error handling in. Error handling just isn't part of our (collective) programming culture and if we're going to change something, we should fix it right.For example, if you have something like
x = emptyStringOnDoesntExist(read(path)) // "" on not exist error BUT NOT permission error
x = emptyStringOnAnyError(read(path)) // "" on any error
So right now this doesn't seem THAT different than using try/catch to select values depending on the type of error "thrown". But its critical to notice that we are going through normal programming control flow here (we pass in the result value, not some sort of weird function wrapper function that wraps read and inserts a try/catch). This becomes much more apparent how its useful when you have more interesting tasks: var filenames = [..,...,..];
var concated = filenames.reduce(pipe(read, emptyStringOnDoesntExist, concat), "");
So now we've done something really neat: we are concating a bunch of files, and accepting some may not exist, BUT if any of them have a permission error the whole thing will return Either _ [PermissionError]. Now are errors are ACTUALLY composable.C++ has an inbuilt alternative: references. References in conjunction with the Null Object Pattern have solved my problems until now.
References simply cannot be null -- and I generally do not use raw pointers in my code unless some library forces me to.
Maybe it could have been a less drastic change?
"NULL, the worst mistake of computer science"
or
"The worst mistake of computer science: NULL"
option.ifPresent(x -> System.out.println(x));
So instead of just checking to see if it is NULL you want me to create an instance of a specialized class that holds my variable that has a method that acts like an "if" statement that I need to pass an anonymous function to that receives the value I already have?
Why not just do:
x && System.out.println(x)
or:
System.out.println(x) if x
Or if you want to skip over the rest of the logic if it is null:
return unless x
System.out.println(x)
I don't see why I need to introduce a new type system and complicate things.
For default values you want me to create an instance of specialized class that holds my variable and has a method that allows me to get my value or return a default value?
option.orElseGet(5)
So instead of a memory lookup I now need the overhead of an entire function call?
Why not just do:
x ||= 5
Or better yet, just put it in your function declaration as a default value:
myFunc = (x=5, y, z) -> ...
The one benefit I see is type safety but it is extremely rare for experienced programmers working in dynamic languages to have bugs related to type.
You are layering on abstractions and forcing programmers to go through hoops just to get at their data. It is more complicated and increasing the cognitive load.
There's also the fact that you are going through functions and classes instead of just memory accesses. This makes code less performant as well.
That's a good one. I really needed that ;)
Type safety is one benefit, but composability is an important benefit (as, you know, is extensively discussed in the source article.)
"In fact, composibility is really the fundamental issue behind many of these problems. For example, the Store API returning nil for non-existant values was not composable with storing nil for non-existant phone numbers."
That's only because you invented some abstraction that got in your way in the first place. NULL is a perfectly acceptable value in dynamic languages and even in most document store databases.
(1) A cache returns something or NULL. It's generic; i.e. implementation doesn't care what it is storing: integers, strings, etc.
(2) A particular value of interest may be NULL or non-NULL.
But now I can no longer use my generic cache and my values of interest together.
A very real example of this is Java's Map interface. It's completely up to implementation on how they handle null, making interpreting null results difficult.
While often you use NULL and everything works, you're still limited to a certain set of circumstances. You can't pick up thing A and thing B and just use them together. Hence non-composable.
cache = {}
getFromCache = (key) ->
return cache[key] if cache[key]?
throw "not found"
doSomethingWithValue = (key) ->
try
value = getFromCache(key)
catch
value = getValueFromNonCache(key)
return _doSomethingWith(value)Rust doesn't have null, but it has Option<T>, which is often syntactic sugar for null.
It's not that null is bad in itself. It's that having variables which might be null need to be distinguished from ones which can't be null.
In a sense, yes. But you have to do something positive before you can pretend that nothing is something. And the call to unwrap can be annotated with an argument as for why it can never fail, or why it's unimportant if it panics, or even be outlawed by a style guide.
"It's not that null is bad in itself. It's that having variables which might be null need to be distinguished from ones which can't be null."
Amen, brother.
No. When you move the contents out of somewhere like an std::vector instance, that object is left in an undefined but valid state. You're free to continue working with that object (though the only meaningful thing you can usually do without undefined behaviour is destruction or the equivalent of clear). That's very different from null references.
If p1 is a unique_ptr, and you do
p2 = std::move(p1);
p1's pointer is set to null. Further uses of p1 will fail, or crash, or something. p1.get()
returns the underlying pointer or null, apparently escaping the unique_ptr protection.It's far inferior to Rust's borrow checker.
Edit: Btw, I disagree with how the article categorizes Rust in comparison to Haskell. It shows that Rust has std::ptr::null, but neglects the fact that Haskell has Foreign.Ptr.nullPtr. Either both should be "5 stars" or both should be "4 stars".
I think that anything which has no null in normal, idiomatic code, outside of "unsafe", "ffi", or similar subsets, should get 5 stars. The distinction is really about whether you need to worry about any possible value, or any possible reference, being null, which you do in languages like C or Java where all references are nullable.
Giving Java and Rust the same 4 star rating because they both have some form of null and some form of Maybe/Option is a bit misleading. In Rust, Option is what you use in any normal code, and so you don't need to check for Null everywhere. In Java, it's the other way around; nullable references have existed since the beginning and are not segregated in any particular way, while Optional is a recent addition.
Likewise, I think that in Scala and Swift, null is only present for compatibility purposes, and idiomatic code does not use them. I'm not sure about F#. Clojure does use nil idiomatically, and also has '(), which may even count as "multiple NULLs" according to this rubric, though I guess that in Clojure '() is just treated as an empty list, rather than a null value like it is in other Lisps.
I believe though that with the exception of Swift, null can infect these language's quite easily via the FFI, and the guarantees aren't as strong. I haven't used them much though, so I could be wrong.
I know that in practice in Rust, the general approach is to write safe wrapper around any C libraries you're using, so the use of nullable pointers is generally confined to those wrappers.
Admittedly, the "rating" is pretty rough, maybe even a bad idea.
I've seen std::ptr::null more than I ever have Foreign.Ptr.nullPtr.
But really, both are usually used for compatibility with external libraries/programs/runtimes, not for idiomatic language programming.
Both great languages in my book :)
Probably because we need to work with the C API a great deal right now, but that should change as more and more things are written in Rust. Still, the unsafe boundary helps by removing the ability to dereference a raw pointer in safe code - something that Haskell doesn't have.
I think Rust should also have 5 stars in this comparison as it forbids null pointers.
EDIT: The articles definition of 5 star null handling is "Does not have NULL." :)
edit: carsongross beat me to what I'm talking about with a better explanation: https://news.ycombinator.com/item?id=10149129
(And if not, please do explain why!)
Exaggeration much? Certainly not "the worst mistake of computer science". IPv4 is much worse, just for one example. NULL isn't even visible to end-users, many mistakes in CS are quite visible and really impact non-programmers' lives. NULL is just the color of the wallpaper in the engine room.
In the same way HIV isn't visible; sure, the cause isn't visible to most people, but the adverse effects are.
How is it worse, technically, beyond a constrained keyspace?
IPv4 is an unfortunate, entrenched reality.
NULL doesn't need to be, and isn't, in some ecosystems.
WITH NullCTE AS
(SELECT a.*
FROM
(VALUES (NULL), (1), (2)) a (Number)
)
,One AS
(SELECT Number = 1)
SELECT *
FROM NullCTE nc
WHERE nc.Number NOT IN
(SELECT Number
FROM One)
UNION
SELECT *
FROM NullCTE nc
WHERE nc.Number IN
(SELECT Number
FROM One)
Running this will give you back a two-row table, containing 1 and 2, but the NULL is excluded from both WHERE conditions.The naive expectation is that the combination of a condition and the NOT of that condition cover all possible circumstances.
Also, NULL pointers are the least problematic type of pointer if you ask me. If a pointer is NULL it is not likely a security problem, and certainly not on its own. Accidentally using a NULL pointer will cause a crash, but accidentally using any other pointer could cause unlimited damage.
> This is a bit different than the other examples, as there are no pointers or references. But the idea of a value that is not a value is still present, in the form of a char that is not a char.
It has nothing to do with the similarity in name (NULL / NUL). As you said, it could be terminated with $. The similarity is that they are both sentinel values. NULL is a sentinel for types; NUL is a sentinel for char arrays.
In both cases, they create exceptional and non-composable situations.
Good explanation here: https://www.reddit.com/r/programming/comments/3j4pyd/the_wor...
I get that a NULL pointer is in some meaningful way not a pointer. Because it literally isn't pointing to anything. It is not the case that NUL is a char that is not a char. NUL is a perfectly good char. Some functions treat it specially, but other than that it's just a regular char.
Even the sentinels similarity is quite weak. NULL is a sentinel that indicates "no valid object" whereas NUL is a sentinel that indicates the end of a perfectly valid object. Notice how we're not having this discussion about '\n' or the whitespace chars in general, even though they're also treated as sentinels by some functions.
Though I'm not sure if that problem is really that huge. Bad code will break in many ways. And breaking on nulls usually isn't that dangerous, data isn't corrupted and stack is safe.
There's another mistake of computer science in my opinion is inefficient array bounds checking and implicit integer overflow behavior. And those mistakes are more dangerous, they lead to data corruption and exploits.
If you care about performance, you really need two predicate values to cover this case. You could have (void * )0 for no entry, and (void * )1 for entry exists, but is blank.
So the author is trying to explain what the null pointer exception is, but uses the concept of sockets in his example. I wonder, how many people know what a socket is but don't know what null pointer exception is?
Of course, that's idealistic, most languages don't have some Option<T> type without a performance penalty, and pre-existing libraries exist. (Usually you should design around needing Option<T> too.)
The right language design decision for these languages (managed languages like Java) might have been to make uninitialized references have a garbage value that reliably throws an exception when used (i.e. null) -- you can copy the reference and pass it around, but you can't compare it for equality and any attempt to inspect its value results in an exception.
That's just a different kind of null. You still have the original problem: this thing is declared as a T, but sometimes it's not a T, so you have to inspect how the value is used before you can conclude whether it's a T or not.
The correct solution is to structure the language so access can't occur before initialization. For example, you could place severe constraints on constructors so they must initialize all the fields and do nothing else until that's done. Think initializer lists from C++ (but stricter), or tagged unions from Haskell.
- Require an explicit initial value to be provided.
- Or require that a collection/iterator/generator of values be provided (and fail if it's not large enough).
- Or have a built-in ArrayBuilder<T> type that accumulates all the needed values before allowing you to retrieve the array.
- Or make your language dependently typed, and change the signature of Array.get to take both an index and a proof that said index has been initialized.
Let's say you want an array of FileHandles. Under this proposal, you'd need to create an "empty" FileHandle value that you can use to fill arrays with and such. You'd have to create some "empty" state for every type. This is worse software engineering than having the possibility of a null pointer exception. (Also, you couldn't write generic code to implement an ArrayList<T> efficiently without some way to generically default-construct the T to fill most of the array entries, or the constructor would have to take a filler value.)
- Or require that a collection/iterator/generator of values be provided (and fail if it's not large enough).
This is a needlessly complicated way to use an array, and it still has the same downsides.
- Or have a built-in ArrayBuilder<T> type that accumulates all the needed values before allowing you to retrieve the array.
And how do you implement your own ArrayBuilder? What if you need something with a slower growth rate, or some other data structure that you could build out of an array with uninitialized elements, such as a deque?
- Or make your language dependently typed, and change the signature of Array.get to take both an index and a proof that said index has been initialized.
A far worse alternative.
Late edit: Generally speaking I think you're completely missing the problem of null pointers. The problem is not that a class of error exists. It's that people try to use null values in surprising ways in their APIs. If you make the null value impractical as a sentinel, you remove the mismatch between what programmers expect about whether a value can be null.
(2) It is true that you may have uninitialized values. There are several ways to address it. Java will not allow you to use uninitialized local variables. C/C++ leaves the values of some uninitialized variables undefined. There are several possible approaches. Treating null as a normal thing is not a good approach, though.
It's wrong to call null pointers a billion dollar mistake, too -- if we didn't have null (and in the absence of generics that permit Option, as things were long ago), programmers would end up using in-band sentinel values instead. That would be a much worse mistake.
x is None
seems to be just fine.
I'm still waiting for std::optional
for C++."No type safety at all" means you can misuse a value of one type using an operation that is appropriate for a different type, and some nonsensical behavior silently occurs (or perhaps some nonportable behavior that you sneakily intended). This characterizes assembly languages, certain machine-oriented languages like BCPL, and some immature dynamically typed languages which omit run-time-type checks (the type information is there, but not checked, so that string_length happily tries to operate on an integer, so that a type problem occurs in the interpreter's kernel itself.)
In fact, the example in the article of the Ruby K/V store illustrates this problem. If optional values were used instead of an ambiguous nil for both cases, there would be two distinct results:
* Some(nil) -- or something of similar shape -- where there is a value for the key, and it is nil,
* nil where there is no value.
Obviously, the typesafety issue doesn't exist for dynamic languages, but the composability/API quality issues do -- from the numbered issues in the article, I'd say at least #s 2, 3, 4, and 7 apply to dynamic languages in general.
http://www.boost.org/doc/libs/1_59_0/libs/optional/doc/html/...
template<class T>
using std::optional = boost::optional<T>; not x
would be sufficient in the case of Python. not x
is not equivalent to "X is not None", since empty lists, zero, etc., are not truthy in a Boolean context.if ( user.getScrollPosition() ) { whatever(); } else { die(); }
99% of the time, the code would be fine, but if the user scrolls just right, the whole thing would die. Stuff like this is literally death to debug because this bug is effectively indeterministic and can't be reproduced.
user.getScrollPostion() != undefined
or in CoffeeScript just use:
user.getScrollPosition()?
Arrays have various types of ints and floats of various word lengths and signs.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Type...
Then don't use it. The example given is a bad design. No phone number and not in cache should not return the same value. That bad design has nothing to do with nil, Return '' for no phonenumber.
Though, I think uninitialized variables or memory might be just as bad.
* unlike Pascal-style strings, they can be usefully sliced, especially if you can modify them strtok-style.
* unlike (ptr,len) "Modern C buffers"/Rust-style strings, references to them are pointer-sized, and they can be used as a serialization format.
This makes the kind of application that is based on cutting pieces of a string and passing them around a good measure faster, especially compared to say C++'s "atomically reference-counted, re-allocating at the slightest touch" std::string.
This style of programming is not particularly popular nowadays, so buffer-strings are better-fitting. Its main problem is its multitude of edge-cases, which tend to demonstrate C's "every bug is exploitable" problem well.
Slicing Pascal-style strings is also easy and constant-time: just track the buffer, offset, and length of the slice of characters you want. Java used to do it implicitly whenever you called `substring`.
> unlike (ptr,len) "Modern C buffers"/Rust-style strings, references to them are pointer-sized, and they can be used as a serialization format.
Every C method that takes a character buffer either a) has a corresponding length parameter or b) is avoided because of the security risks. In practice this means that C also stores the length information, just on the side instead of combined into a struct with the buffer.
That's just coercing into a "modern C buffer" and slicing it. It has the disadvantage that coercion is not equality or subtyping - i.e. you will have to do lots of wrappings and unwrappings in mixed code.
> Every C method that takes a character buffer either a) has a corresponding length parameter or b) is avoided because of the security risks. In practice this means that C also stores the length information, just on the side instead of combined into a struct with the buffer.
You are surely talking about the buffer's capacity, not the string's length. These are distinct concepts. Anyway, functions that only read strings, and structs that only store them read-only, aren't interested in the capacity of any buffer.
Anyway, C strings aren't responsible for the fixed-size buffers of Cold War-era code - that code uses fixed-size buffers for everything. Their main claim to fame is their popularity in parsing code, which is edge-case- and bug-prone.
char *myChar = 0;
std::cout << *myChar << std::endl; // runtime error
compile because it's undefined behavior?The entry for C++ in the final tables is:
C++ | NULL | boost::optional, from Boost.Optional
It does ignore that C++ has nullptr since C++11Why not have null (and emptystring) eval to false? And
if not str:
str = 'wordup'Deleted comment