*From Jan Mercl: * > To represent all those values and the new possibility > of the value being nil, we need 257 distinct values. But that does not > fit 8 in bits anymore. However, changing the size and/or the bit > representation of such a variable is observable, making it not > backwards-compatible.
I'm not proposing that *any* value be made nillable, I'm proposing the explicit syntax var x uint8 | nil that would create a nillable uint8. A variable of type `byte` would still only take up one byte; a variable of type `byte | nil` would be larger. Existing code, which obviously doesn't use the `| nil` syntax because it doesn't exist yet, would be completely unaffected by this change. *From Brian Candler:* > I also think that you need to find multiple compelling use cases (of which > that example is not one), before fundamentally adding complexity to the > language. Remember that "nil" is already overloaded in Go, so adding > another (similar but different) meaning increases complexity for the reader. Back when I worked at google, this was the number one cause (at least in code that I worked with) of runtime errors in go (mostly in code using protobuf v2 or ygot). I've also seen a lot of random github discussions that boiled down to "how do we differentiate between 'x is unset' and 'x is explicitly 0'; if I have some time next weekend I'll see if I can make a big list of those to support the motivation for this. *From Jeremy French:* > I understand the problem you are trying to solve, and it's valid, I > think. But this solutions "feels" bad and very un-Go-like. Usable zero > values is a feature in Go. Whether you agree with that or not, it's a > selling feature of the language and clearly something that was/is important > to the Go Authors - as is explicit vs implicit logic. This would make zero values *more* usable - it provides a way to ensure that the zero value of a struct is a valid and consistent empty value of that type, without requiring that users remember to set extra bools or do nil checks. `Certificate` is a good example of this - you would expect a "zero" certificate to not have a path length specified, but since 0 is a possible value for MaxPathLen in this context, the type needs the extra bool because otherwise `Certificate{}` would give you a certificate that asserts that its subject is a CA and that this certificate cannot be followed by any other intermediate certificates. And, to be clear, the developers of that library could have left the bool out - they already have the logic that -1 indicates an unset length, so they could've just insisted that anyone who created a Certificate must remember to explicitly set its MaxPathLen to -1. They added this extra boolean solely because they wanted the zero value `Certificate{}` to actually be an empty certificate, and the fact that they added a bunch of extra logic (and new ways to make a logically inconsistent certificate) in order to this is pretty strong evidence that this would be a useful thing for the language to support. It would not be an extremely common thing to do: for most types, the zero value really is usable! But in cases where the zero value of a field has an actual important meaning, it would be really great if there were a standard way to make zero values of structs still be useful. So having a zero value that implicitly means "don't use this" a a feature > that is built into the language seems like it could lead to confusion or at > least a very muddled message about the nature and purpose of Go. As far as I know, nobody has this issue with pointers, which very definitely have a zero value that means "don't use this"; I don't see how using 'nil' this way would confuse anyone about the nature of Go. Indeed, the most common way I've seen to solve this problem is just to use pointers, and live with the fact that if a junior dev forgets a nil check then your code will compile just fine but panic at runtime; this seems antithetical to the nature of Go. I think maybe where your opinion diverges from the "hive mind" here is that > you want the compiler to enforce this notion. You can already achieve your > basic objective with your "Additional Bool" example, and the confusion you > complain about there can be easily mitigated with a better named variable > "IsSet" rather than "IsZero" like Brian Candler suggested. This is a minor point, but in the example I gave, IsZero is only checked if the value is zero, so that users can write `cert.MaxPathLen = 5` without remembering to set the bool except in the less common case where you set it to 0. It obviously wouldn't be hard to have an IsSet, but both cases add operations or checks that users must remember to do to maintain consistency, with no compile-time mechanism to validate that they actually are doing this, and make it possible to construct inconsistent data (e.g. setting Value to something non-zero but also setting IsSet to false). > But you still wouldn't have compile-time enforcement. And that I think is > where Go is going to disappoint you. Go is a strongly typed language, at > least on the scale of QBasic to Pascal, but the compiler does allow you to > do a lot of things that you shouldn't, in the interest of letting you do > things that might be a little crazy but that still work. It seems to me > that Go likes to leave "should" rules to the linter rather than the > compiler, so maybe you could use a custom linter to achieve what you're > looking for? It's not a perfect solution, but it feels like it gets you > 90% there. Being able to tell the compiler "tell me if this code uses a value that isn't set" is a lot more important than "tell me if this code declares a variable but doesn't use it", which as far as I know is the established bar for whether a linter-type check should be enforced by the compiler or not. I'm also not sure how one would build a custom linter for this. I guess you could just use pointers and have your linter yell anytime you dereference a pointer without first nil-checking it, and just add a lot of linter-ignore comments in places where these checks aren't needed? That wouldn't help with the issues around assignment or mutability, though - that would require a preprocessor or something, which would be dramatically more un-go-like. Or you could go with a Maybe[T] and make a linter that checks that .Value is never accessed unless .IsSet is checked, and .IsSet is set any time .Value is? That sounds like it would be pretty error-prone. Is there a better way to achieve that? Thanks, Dan On Tue, Mar 19, 2024 at 12:22 PM Jeremy French <ibi...@gmail.com> wrote: > I understand the problem you are trying to solve, and it's valid, I > think. But this solutions "feels" bad and very un-Go-like. Usable zero > values is a feature in Go. Whether you agree with that or not, it's a > selling feature of the language and clearly something that was/is important > to the Go Authors - as is explicit vs implicit logic. So having a zero > value that implicitly means "don't use this" a a feature that is built into > the language seems like it could lead to confusion or at least a very > muddled message about the nature and purpose of Go. > > I think maybe where your opinion diverges from the "hive mind" here is > that you want the compiler to enforce this notion. You can already achieve > your basic objective with your "Additional Bool" example, and the confusion > you complain about there can be easily mitigated with a better named > variable "IsSet" rather than "IsZero" like Brian Candler suggested. But > you still wouldn't have compile-time enforcement. And that I think is > where Go is going to disappoint you. Go is a strongly typed language, at > least on the scale of QBasic to Pascal, but the compiler does allow you to > do a lot of things that you shouldn't, in the interest of letting you do > things that might be a little crazy but that still work. It seems to me > that Go likes to leave "should" rules to the linter rather than the > compiler, so maybe you could use a custom linter to achieve what you're > looking for? It's not a perfect solution, but it feels like it gets you > 90% there. > > On Monday, March 18, 2024 at 4:42:32 AM UTC-4 Brian Candler wrote: > >> I like Go because it's relatively simple and low-level, like C, except >> with things like garbage collection and channels integrated. If Go were to >> have "| nil" types then the internal representation of such variables would >> have to be something like this: >> >> type Maybe[T any] struct { >> Value T >> IsSet bool >> } >> >> In which case, why not just use that form explicitly? The code you write >> is then clear and obvious. Taking one of your examples: >> >> func foo3(x Maybe[int]) { >> if x.IsSet { >> fmt.Println("x squared is", x.Value*x.Value) >> >> } else { >> fmt.Println("No value for x") >> } >> } >> >> // https://go.dev/play/p/ci10fhU1zqL >> >> I also think that you need to find multiple compelling use cases (of >> which that example is not one), before fundamentally adding complexity to >> the language. Remember that "nil" is already overloaded in Go, so adding >> another (similar but different) meaning increases complexity for the reader. >> >> Full union types are a different thing again, and I can see the >> attraction of those - except at that point, the language is no longer Go. >> Having "int32 | int64" as a union type, but also as a generic type >> constraint, would also be somewhat mind-boggling. How would you write a >> type constraint that allows a union type? >> >> On Monday 18 March 2024 at 07:01:25 UTC Jan Mercl wrote: >> >>> On Mon, Mar 18, 2024 at 4:41 AM Daniel Lepage <dple...@gmail.com> >>> wrote: >>> >>> > This change would be entirely backward-compatible ... >>> >>> Let's consider, for example, the type uint8, aka byte. A variable of >>> type byte is specified* to occupy 8 bits of memory and has 256 >>> possible values. To represent all those values and the new possibility >>> of the value being nil, we need 257 distinct values. But that does not >>> fit 8 in bits anymore. However, changing the size and/or the bit >>> representation of such a variable is observable, making it not >>> backwards-compatible. >>> >>> ---- >>> *: From https://go.dev/ref/spec#Numeric_types: >>> >>> """" >>> The value of an n-bit integer is n bits wide and represented using >>> two's complement arithmetic. >>> """" >>> >> -- > You received this message because you are subscribed to the Google Groups > "golang-nuts" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to golang-nuts+unsubscr...@googlegroups.com. > To view this discussion on the web visit > https://groups.google.com/d/msgid/golang-nuts/e45d298f-154d-49c1-9316-73df4f250543n%40googlegroups.com > <https://groups.google.com/d/msgid/golang-nuts/e45d298f-154d-49c1-9316-73df4f250543n%40googlegroups.com?utm_medium=email&utm_source=footer> > . > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CANtoCQ9JP2VZLdQtrhy9FD2hZ%3Dr_iRpfXa8nY%3DUUB6hZ6LL9KQ%40mail.gmail.com.