13 ms·
I don't agree, especially on the second point. Having strings default to empty instead of nil is a feature not a flaw. What Go actually needs: Nil-Safe Types!
by welder 5y ago
I don't agree, especially on the second point. Having strings default to empty instead of nil is a feature not a flaw.
What Go actually needs: Nil-Safe Types! [1]
Programmers can work around verbose error handling(3) and lack of enums(1), but forget to check for nil before using a pointer... CRASH! And that's something the compiler doesn't warn about.
[1]: https://wakatime.com/blog/48-go-desperately-needs-nil-safe-types https://wakatime.com/blog/48-go-desperately-needs-nil-safe-t...
- stonemetal12 5y agoWhy is null even an expected value for strings? I get that it is a design decision for a low level language like C, but as soon as you go up a level the fact that strings are implemented as pointers to character arrays should be an implementation detail not visible to the language's user. Need a Null\Nil string? Use a pointer to a string, just like you would for an integer.
- oh-4-fucks-sake 5y agoBecause there's value in being able to differentiate between when a value is "truly absent" vs. a zero-length string. To take a trivial example with a simple API: a null string can carry the semantic that a value was (intentionally or not-intentionally) omitted in a request by a user/FE. What if that request schema grows to the point where there could be dozens of intentionally-omitted/optional fields--you then get into this awkward kind of scenario where either the FE must include those key-values as empty strings or the API must be able to interpret those omitted values and coerce them into empty strings. Even if a framework or whatever is abstracting that away from you--the can is getting kicked somewhere for someone to have to deal w/ the fact that other systems/langs--and their contracts--do, very much, have the concept of null.
- stonemetal12 5y agoWhy strings and not integers and doubles? Why strings and not structs? You have given plenty of reasons why nullability in general can be useful, but not why it is mandatory for strings to be nullable. Unless the core of your argument is C has null strings so now everyone must support that poor decision for interop purposes. Personally I prefer how Protocol buffers handles not present strings compared to C. If you stick to std::string, C++ isn't bad either.
- deleted 5y ago[deleted]
- klodolph 5y agoDo you mean non-nullable pointers? Go already has some non-nullable types, like integers, strings, and structs.
- welder 5y agoLike Swift, how once you check if a pointer (not integers, strings, and non-nullables) is null, the compiler knows from that point on the variable cannot be null.
- klodolph 5y agoGo already does that... if you have an "if x == nil { return }" at the top of your function, then the compiler knows that x != nil below that point, and elides nil checks. Like, I'm not trying to be cute here, but the point of having a "non-nullable type" is that you can get an error message when you use a nullable type in its place. However, if you're not talking about non-nullable types and just about whether the compiler knows a value is nil, well, the compiler knows that.
- richardfey 5y ago> elides nil checks. Are you sure? This would only work in a world with a single goroutine.
- chrisseaton 5y agoIt’s a basic optimisation. Java does it as well, for example, and it’s safe. But it’s not part of the type system and it’s not what people here are talking about.
- simiones 5y agoIt's more complex in practice, since you need to also check that the compiler needs to see the entire lifetime of the variable to check if it's not modified asynchronously, as you say. But for many common use cases, this is not hard to confirm, especially since the Go compiler has access to the entire source code of the entire program (unlike in C++, Java, etc).
- baby 5y agoAs an ex security consultant almost all the bugs I found in Go applications were crashes that happened because of nil. It’s crazy how Rust just gets rid of this whole class of bugs with option and result types (and sum types in general).
- brundolf 5y ago> It’s crazy how Rust just gets rid of this whole class of bugs As do TypeScript, and Kotlin, and Dart, and Python (with MyPy), and C#, and Swift... It's honestly inexcusable that Go, as a relatively new statically-typed language, doesn't have strict null-checks
- tticvs 5y agoUnfortunately it's easy enough to confuse MyPy that you can't really fit in with the rest of this list.
- rowanG077 5y agoUnfortunately in any real world typescript or python the type checking is almost always partial. Which means it doesn't actually save you from these kind of errors.
- cageface 5y agoI have not found this to be the case in Typescript code I've worked on. The main issue I've found with TS is stuff coming in from outside the system as JSON that doesn't conform to expected formats.
- CryZe 5y agoOne issue I've noticed is that indexing into a T[] array where T can't be undefined, results in the expression to be of type T, not T | undefined, despite an out of bounds index evaluating to undefined. I believe I've seen that they recently added a setting that changes this though.
- suddengunter 5y agoagree. In C# we were going from "every reference type can be null" to "only things marked with '?' can be null" with nullable reference types. When I started using Go it was nice to see that only something, that is explicitly created as pointer is nullabe, everything else can't be null / nil - and you can't get panic from nil referencing stuff
- simiones 5y ago> When I started using Go it was nice to see that only something, that is explicitly created as pointer is nullabe, everything else can't be null / nil - and you can't get panic from nil referencing stuff Well, it's the same in C# since 1.0 to some extent, except that people rarely used `struct` (value types) in C#. The ecosystem matters a lot, of course, but just at the language level, Go and C# structs have the same semantics (though in C# it's harder to get a reference to a struct).
- skohan 5y agoYeah ADT's, and specifically their application to prevent NPE's is one of those things you never want to go back from once you've used a language which has that feature. It seems like one of those fundamental advancements in language design, like when computer science collectively decided goto should be deprecated in favor of flow-of-control expressions.
- gameswithgo 5y agoIm in my 40s now and have seen many hyped language features come and go and very few that I think haven proven to be a net good thing that truly make programming better. Option types are on of the few. I think every language should have them now. It is more convenient, safer, and modern development machines can often compile away the tiny overhead.
- stewbrew 5y agoWhile option types are nice the much more light-weight approach of kotlin would suffice to solve this problem.
- divs1210 5y agoWhich is?
- rockwotj 5y agoExplicitly being able to mark a type as nullable and forcing null handlong if so. See https://kotlinlang.org/docs/null-safety.html https://kotlinlang.org/docs/null-safety.html it works pretty well with java interop by respecting jsr annotations and auto checking for null
- ninkendo 5y agoI think for most intents and purposes, option types and Kotlin’s approach are more or less equivalent. Either way, the compiler helps you avoid null dereference errors. I like Swift’s approach personally, which is that there’s an Option type, but loads of syntax sugar to make working with it easy (just append a “?” after the type to flag it as optional, “if let”, optional chaining with “?.”, “?()”, etc etc.)
- mumblemumble 5y agoFor Go's purposes, though, they aren't equivalent at all. Option types require generics, which Go doesn't (currently) have and may or may not be enthusiastically embraced by the community. Option types must also be baked into the language, standard library and culture from the beginning in order to be ergonomic; otherwise you're constantly having to wrap and unwrap them whenever you interact with code that was written before the type was introduced. Which is most the code you interact with. Finally, my own experience has been that Option types generally make things more rather than less complicated when you introduce them to a language that already has null. You'll need to make a decision on whether Some(null) is an allowed value, both options will introduce language warts. Kotlin's approach, on the other hand, does not require generics to work, and interacts very well with the JVM's existing libraries and its existing type system. It's not going to butter everyone's toast. You can't do anything monadic with Kotlin's approach, for example, but I don't get the impression that the Go community is just desperate for monads.
- wolframhempel 5y agoI'm really not a fan of the "initialize with empty value" approach. Especially when handling user input, e.g. marshalling JSON onto a struct, Go makes it impossible to distinguish between the user setting a value to 0 and the user not specifying the field. Instead, you end up with awkward "pointer to int" constructs to allow for nil checks. I'd love to have a "undefined" value instead.
- dilatedmind 5y agoI agree this can be awkward, especially if you let these constructs propagate through your codebase and database. However, if a string or int can be null, then all strings and ints are essentially pointers, so you've just introduced this construct everywhere. A couple things I have tried: - hope default values align with your business logic, eg an empty string isn't a valid name and 0 isn't a valid age. - for partial updates, populate the existing values before unmarshalling, then unmarshal on top. Missing fields in the json won't overwrite the existing values - unmarshall into a map[string]interface{}, which gives you the semantics you want.
- mumblemumble 5y agoI have never used Go in anger, only dabbled a bit and watched interestedly from the sidelines. So, my opinion probably isn't worth much. But here it is, anyway: it seems like this way of doing things would lead to a tendency toward messy, confusing domain models where you can't tell from types alone whether 0 or -1 or MIN_INT is a sentinel for "no value", or actually means that value, and possibly find yourself juggling multiple sentinel values for one type in the same scope. My own cranky opinion is that, in the 21st century, a static type system that can't even reliably distinguish between something and nothing isn't much of a type system at all. Say what you will about dynamic type systems, but it must be acknowledged that they do a much better job of maintaining a firm and logically consistent, if not statically verifiable, grasp of the most basic ontological distinction that the universe has to offer. There's always the option to wrap the atomic value in a struct of some sort. But, without generics, that's going to feel more like gRPC's awkward, verbose way of doing it than the ML family's Option<T> type. That approach is (mostly) tolerable in a datagram format, because I/O is going to be a hot mess no matter what you do, anyway, but I'd hate to have to do it with the types I'm using in my actual code. I'm generally fairly disdainful of the, "every language must cargo cult everything functional languages do," thing, but it is interesting to observe that generics and proper algebraic types would cover all of these use cases - and more - cleanly and without having to add a specific language feature to cover each one. Which has me wondering, if the goal of Go was to create a maximally simple and ergonomic imperative language, did they actually achieve that, or did they follow a greedy search into a local maximum?
- lbriner 5y agoI'm not sure I understand this discussion. It sounds like most people are saying that null/nils cause a tonne of problems because if you don't check them everywhere, you end up with a crash. I don't see how making types non-nullable solves the problem, doesn't it just create a new problem? When I load the string from somewhere outside of the program, if it is not present, I would either have to check for that condition otherwise I will crash, otherwise I would assign it some magic value and have to check everywhere "if not magic value". otoh, if the strings are not external then there is little danger of things ever being null no?
- jerf 5y agoThe advantage of having something like a non-nillable value is that it more strongly encourages (and/or forces, depending on the library) the programmer to move the handling of incorrect values to the edge, where they first came in. If you are parsing something, and you're trying to extract a string to pass it to an API that requires a non-nillable string, you are forced to handle not getting a string right on the spot, rather than passing it in to the API, where it may go who knows where. This is a very important programming pattern that should be used at every available opportunity, and probably one of the biggest programming failures that is rarely talked about in programming. Dynamically-typed languages encourage this style more than statically-typed languages but it can be done in either one. You need to do as much of your validation as close as you can to the time that data enters your system. If you don't, instead of validation living at your edge, it gets smeared through the entire program, and it will be done incorrectly when that happens. (If you have a deeper layer that has more restrictions on the validity, then the additional validation should be done on that deeper layer. This can be applied recursively within a program if it is big enough to have multiple domains. But you always want edge validation.) Architectures that fail to do this inevitably become a mess on the inside. Functions develop that are passed "strings" but they don't know if they're validated strings, or decoded strings, or what. Where things get validated and decoded becomes incredibly complicated. Functions start growing options describing what's being passed, but then the functions calling those functions end up eventually being wrong themselves. This eventually metastasizes into the sort of code that nobody can change because every attempt to change something screws up some delicately balanced code path. The only solution to this is to start over and do this edge validation I'm talking about, so that the entire rest of the code base just stops having to worry about it. This also goes hand-in-hand with the idea that you should decode data at the edge, and pass around data internally only in its natural format, and encode data only at the edge as it leaves. If you're in the "middle" of a program and suddenly there's code to URL decode a string, there's an architectural problem in that code. (This code is likely to become code in the future to conditionally decode the string, or much worse, guess at whether the string needs to be decoded, which is getting perilously close to a You've Already Lost situation.) If you come to a deep enough understanding of what it means you can start to see that validation and decoding into a canonical internal format, and the encoding on the way out from the canonical internal format, are the same thing. I know this is a common question since I also had it myself at the beginning, but see the strong types not as the assertion that the world will never have a nil string in it, because that's obviously an impossible assertion, but as a statement that any calling code is going to have to deal with what happens when there's a nil string (or whatever other invalid input), because I, this strongly typed library, am not going to deal with it. This statement is also almost always correct, too, because the library lacks the context to know how to handle things it can't handle. You shouldn't ask libraries to handle things they don't know how to handle, because, well, they don't know how to handle them.
- smoyer 5y agoProgrammers can write nil-safe types in Go but it requires some extra effort.