"We" and "Java" are not the same.
Java is like a third world village learning about boiling water killing germs.
"We" and "Java" are not the same.
Java is like a third world village learning about boiling water killing germs.
From the TIOBE index list[1] (pick any other list if you want):
- Only 3 languages from top 20 (Rust, Swift and Kotlin) started with null safety type features, some others developed this over time (JS with TS, Python type hints)
- Only 2 (Rust, and Kotlin) languages AFAIK are immmutable by default
It is no coincidence that all of these languages are modern and created after the 2000sF# also is immutable by default. Maybe it's not on that "top 20" but 20 is an arbitrary cutoff. There are many so many languages being used in production that it's hard to imagine "20" is a defensible sample.
It's also hard to argue Python has any type safety with "type hints". The interpreter doesn't do anything with those hints. Linters and safety don't belong in the same sentence.
All in all, that seems like a bad source.
But it's probably more important than all of these languages have been heavily influenced by the ML/Haskell family of languages. Most ML-influenced languages forbid null references, discourage them or make nullability a part of the type signature. I think the ML-heritage is the key factor, together with Tony Hoare's influence.
Languages released shortly after 2009 that have ignored the ML tradition like Go and Dart did not have null-safety. I believe ML-influenced language designers were more attuned to Tony Hoare's call for action and had a good idea about how to make null-safety work smoothly an industrial language.
Fortunately for us, nowadays the language designer doesn't have to be a Functional Programming aficionado anymore: null-safety has proven itself as relatively easy to implement and too useful to ignore.
---
[1] Rust is the odd one out here: Graydon has started working on Rust as a personal project back in 2006, but that language is bears little resemblence to what we call Rust today. Rust only became more than a personal project around 2009 and the first release (0.1) only came in 2012. Looking around, I find no reference for avoiding null reference in Rust before 2010.
Null is a perfectly valid and very useful feature, if modeled in the type system. A nullable reference of type, say, "T?", would accept values accepted by T or the special value "null", which is equivalent to a union type of "T | Void" (or whatever type is assigned to "null" in your language).
The billion-dollar-mistake is not the existence of null references, but the inability to model nullability in the type system, thus making references implicitly nullable. You can ban the concept of null from your language, but you're inevitably going to add lots of syntax sugar or helper functions to unwrap "Optional<T>" or "Maybe t" values, so making nullability a "type modifier" has equivalent behavior and, IMO, better ergonomics.
No it isn't, because union types leak through generics in surprising ways. For example:
// value is null if not loaded yet
struct AsyncSnapshot<T> { value: T? }
// returns null if user is not found
fn getUser(id: Id<User>): User?
fn runInBackground<T>(f: () -> T): Stream<AsyncSnapshot<T>>
If you run `runInBackground({ getUser(id) })`, `AsyncSnapshot<T?>`'s `value` gets the type `T??` which is typically coalesced to `T?` and you can't tell whether it's `null` because there is no user, or because it's still loading.Sum types like `Option<T>` do not coalesce, `Option<Option<T>>` is distinct from `Option<T>`. Better yet, you can declare your own clearer sum types, so you can have `MaybeLoaded<MaybeExists<User>>` rather than have to remember which layer is which.
It's about making our systems human-tolerant. Humans are absolutely terrible when it comes to consistent (or even correct) behavior, so we must design our systems such that the path of lowest cognitive load is also the path of highest safety and least astonishment.
For example, C++ has the concept of a const method. And that's great, because you can label which methods won't mutate an object's data. But it's actually backwards. It should be that mutator methods must be marked as such (because they're the ones with a higher likelihood of causing problems), whereas any non-marked method is assumed to be non-mutating.
In Rust, you must specially mark mutable objects. In Kotlin, you must specially mark nullable things. Each of these newer languages is taking on some of the lessons learned, but nobody is taking a serious holistic look at human psychology and how it contributes to system failures - it's always just a few pieces here and there that the designers had learned along the way. And then someone comes along and points out a lesson they didn't implement and everyone goes "Oh. Oops."
You don't get that in civil engineering because the lessons have been codified (and people who skimp out on them are liable).
Psychology really needs to have more of a center focus when designing software systems, because we can't take the human fully out of the works.
I don't think that's the case here. It's not "Oh. Oops". It's "Yes, we like it that way".
I think that readers of Java source code have the right to see a BufferedReader, and think "That's a BufferedReader".
Writers of Java source code have decided that that's too onerous. They would like the 'opposite' right - of taking a 0x0000, and declaring that to be a BufferedReader. That's really the core of it.
The debates always involve adding extra types, annotations, IDEs or external tooling into the mix, as if nulls naturally arise in nature, and some genius-level engineering is required to mitigate against them. But people routinely include them in their source code. I don't think you can win the war on accidental nulls if you can't swing opinion on deliberate nulls.
Then of course Rust came along and I'm questioning even that level of tolerance for them.
IntelliJ will issue you a warning for using an Optional either as a field, or a method parameter.
Java was an early 90s attempt to get C and C++ programmers to write safer code with the technology of the day. The design goal of the language was to get half way to LISP or ML for those C and C++ programmers. In an alternate universe where they had just made Common LISP, does it succeed? Perhaps not. Also consider that Java is a cross platform language, that must be backwards compatible, and work the same via a vm across architectures and operating systems, and basic things like just make variables non-nullable by default are not as simple.
* Haskell
* Rust
* C# (barely) with other .NET languages
Any others?
If "Java is like a third world village learning about boiling water killing germs" then how do you call other main programming languages - C, C++, JS, Python?
Everything is riddled with foot guns and escape hatches but references can't be null.
And also - you can get nullable references. The following compiles (and often runs) without a problem:
int functionWithReference(int &ref) {
return ref;
}
int main() {
int* pointer = NULL;
return functionWithReference(*pointer);
}Also the above will not compile. Because compilers can actually detect if you set a pointer to nullptr and then use it. Here, you didn't use it so maybe the compiler won't complain. But in the general case yes it will complain.
You can also get Null references in C# and Java in this same method. You call some unsafe code that directly interacts with memory. That's allowed, and yes it breaks the type invariants.
Point being, if you truly want to argue C++ is weakly-typed then you'd also have to argue C# and Java are weakly-typed. Most people don't do that though, so it's just a matter of bias or familiarity.
I love C++ to death, but calling C++ strongly typed when the only type enforcement that is actually done is either static typing at compile time (which a lot of casts can circumvent) or dynamic_cast at runtime is actually laughable. Like, there are literally 2 casts almost entirely ignore the static type system in reinterpret_cast and const_cast. This isn't even talking about the C style cast that still is possible, which can be any of static_cast, const_cast and reinterpret_cast depending on the context.
C++ will happily run with an invalid type assumption you introduced at compile time, while in both C# and Java you will have a type cast exception. C# and Java do runtime type checks at basically every cast.
Weakness consists of all that the type system doesn't catch.
Like oh, for instance: C++ local variables become pixie dust with terminates. Yet an address of these variables can escape from the scope. The type system does nothing to catch this.
Or: the pointer to a Derived can be converted to a pointer to a Base implicitly. But then pointer arithmetic can be applied. Even if there is an array of Derived there, the displacements are wrong.
Most of the unsafe features from C are present, like argument handling in variadic functions.
Explicit memory management. Code would work with an object may be perfectly well typed as far as the data representation goes except oops something already invoked the delete op on the object.
You can have code that's perfectly fine except oops when exceptions happen.
If C++ were type safe, they wouldn't be all these rules on how to write safe C++. Like do use this kind of smart pointer but don't use this other obsolete one that has issues.
And, to be clear, that behavior ALSO exists in C# and Java. You can absolutely 100% ignore the type system in Java and C# and its supported by the standard. Just like C++.
So if that's your own standard for what is weak, then you must also admit that Java and C# are ALSO weakly typed. But clearly you don't believe that, so you have some sort of logic problem here.
In my opinion, people see C++ differently because C++ code in the wild is fundamentally different. It's high performance and used in domains where that matters, so escape hatches are used more often than other langs.
However IMO the strictness/weakness of a language comes from the language itself. And both C++ and Java/C# provide static checks + runtime type checks with escape hatches.
The difference is that C++ code with raw pointers is (fairly) common.
It compiled for me (after changing NULL to nullptr).
> Because compilers can actually detect if you set a pointer to nullptr and then use it.
Apparently it didn't.
> Here, you didn't use it so maybe the compiler won't complain. But in the general case yes it will complain.
That's incorrect, there's a use of the nullptr.
> You call some unsafe code that directly interacts with memory.
I don't see something explicitly declared as unsafe from the source code alone. It looks like a normal pointer dereference to me.
No, there isn't.
> declared unsafe
Please, I have no patience for the purposefully obtuse. It's clear I was referring to other languages there.
Which, you failed to address. Probably because it was difficult, so you figured if you just said "I'm right" and elaborated nothing nobody would notice. Sorry, that doesn't work on people who are awake.
As I've said, you can argue that C++ is weakly typed but you MUST also address languages like C# and Java to make that argument. You haven't, so nobody will believe you when you say C++ is weakly typed.
fn function_with_reference(reference: &i64) -> i64 {
reference.clone()
}
pub fn main() {
unsafe {
let x = 0 as *mut i64;
function_with_reference(&*x);
}
}
This also compiles, runs, and segfaults. And it doesn't mean that references in Rust can be null - they can't.Your problem is with undefined behaviour or perhaps raw pointers, but not references. The reference itself isn't what's messing this up!
That way of obtaining a null reference is actually quite portable.
Where you going to run aground is when you have some null reference in some scope, and you have some code conditional on it being null. Yet the optimizing compiler assumes that since a reference is being checked for having a null address that comparison is always false.