All approaches come down to modeling it in the type system instead of it being invisible.
One approach is modeling it as a sum type [1]. A sum type is a type that models when it's one thing OR another thing. Like it's "a OR b". "null OR not null". So a lot of languages have a type called `Maybe<T>` which models it's a "T OR Nothing". Different languages use different names (like `Option` [2]) but it's the same idea. The key is that null is now modeled using normal / non-special / user-defined types so that it's no longer invisible.
Another approach is using so-called "nullable types" and flow typing [3]. For example, `Foo` is a normal type and `Foo?` is a nullable version of `Foo`. You're not allowed to pass a `Foo?` to a function that requires a `Foo` (the other way is fine). When doing a null check, the compiler narrows a `Foo?` to a `Foo` inside the non-null branch of the check. This is one capability of a more general compiler/language technique sometimes referred to as "narrowing" [4] or "static flow analysis" [5]
[0] https://yourbasic.org/golang/gotcha-why-nil-error-not-equal-...
[1] https://en.m.wikipedia.org/wiki/Tagged_union
[2] https://doc.rust-lang.org/std/option/index.html
[3] https://kotlinlang.org/docs/null-safety.html
[4] https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...
[5] https://learn.microsoft.com/en-us/dotnet/csharp/nullable-ref...
nil/null is really problematic, true. But how languages handle this otherwise? Is it that program must be statically analyzed to ensure that no nil/null path exists or there are other solutions as well?
Would be thankful for any pointers.