[HN Gopher] Go Nulls and SQL
       ___________________________________________________________________
        
       Go Nulls and SQL
        
       Author : mnvrth
       Score  : 34 points
       Date   : 2022-05-22 11:43 UTC (1 days ago)
        
 (HTM) web link (ente.io)
 (TXT) w3m dump (ente.io)
        
       | tedunangst wrote:
       | > Pointers are easy first, but then you realize that you have to
       | put nilness checks everywhere.
       | 
       | Well, yeah, if something may or may not be there, you have to
       | check before you use it. How else do people want it to work?
        
         | solar-ice wrote:
         | In some languages, the concept of "may or may not be there" is
         | cleanly separated from "exists in a separate block of memory".
         | There's plenty of pointers which will never be nil - checking
         | that they're nil at the entry and exit points of every function
         | is line noise.
        
           | throwaway894345 wrote:
           | I don't think the parent was advocating for checking for nil
           | before/after each function, but rather noting that this
           | option pattern doesn't buy you any more type safety in Go
           | because there's nothing enforcing you to check appropriately
           | any more than there is for a pointer.
           | 
           | The salient rebuttal is that this pattern is a strong hint to
           | check, whereas a pointer is ambiguous (it's unclear whether
           | or not a bare pointer may ever be nil, but you would only use
           | this pattern to be explicit that a check is needed). This
           | pattern can also support value types so you don't have to
           | worry as much about unnecessary allocations.
        
         | xdfgh1112 wrote:
         | Sane languages have pointers which either can't be null or
         | require you to check explicitly. Rather than leaving it to
         | explode at runtime.
        
           | tedunangst wrote:
           | Tell me more about these unexplodable languages.
        
             | adastra22 wrote:
             | Rust being the popular example. But even C++ has this with
             | std::optional, for example.
        
               | tedunangst wrote:
               | So rust doesn't explode if I unwrap None?
        
               | Gwypaas wrote:
               | You're explicitly choosing to panic if it is None. Would
               | never pass a code review unless you can in very clear
               | terms tell why it will never now or in the future be
               | none.
               | 
               | The point is that you can't use the inner value without
               | choosing a course of action if it happens to be none.
        
               | stouset wrote:
               | Only if you want it to.
               | #[derive(Default)]         struct Foo {             ...
               | }              struct Bar {             ...         }
               | fn do_something(foo: Foo) -> Bar {             # do
               | something with Foo and return a Bar             ...
               | }              fn main() {             let opt_foo:
               | Option<Foo> = None;                  // panics
               | let foo: Foo = opt_foo.unwrap();                  //
               | panics, but with an error message of your choosing
               | let foo: Foo = opt_foo.expect("foo was supposed to be
               | there");                  // converts the Option (Some or
               | None) to a Result (Ok or Err,             // where Err
               | can contain an error type or message and then passed
               | // around or returned or whatever)             let foo:
               | Result<Foo> = opt_foo.ok_or("foo was supposed to be
               | there");                  // replaces it with a value of
               | your choosing if it's not there             let foo: Foo
               | = opt_foo.unwrap_or(Foo { ... });                  //
               | replaces it with the default value the type defines
               | let foo: Foo = opt_foo.unwrap_or_default();
               | // keeps it as `None`, or if it's actually something then
               | // replaces the internal contents with the result of the
               | // function call             let bar: Option<Bar> =
               | opt_foo.map(do_something);                  // does
               | arbitrary logic in the match arm if foo is there
               | match opt_foo {                 Some(foo) => { do
               | something with foo },                 None      => { do
               | something else },             }         }
               | 
               | There are a few dozen other things you can do with an
               | Option that handle the rarer use-cases, but the above are
               | like 95%+ of what you want.
        
               | avgcorrection wrote:
               | The comment that you respondend to:
               | 
               | > > Sane languages have pointers which either can't be
               | null or require you to check explicitly.
               | 
               | You cannot try to dereference a nullable pointer in safe
               | Rust. It has got nothing to do with Optional (Some/None).
        
               | tedunangst wrote:
               | That would make a rust pointer rather less useful for
               | representing sql null, no?
        
               | avgcorrection wrote:
               | What?
               | 
               | The comment that you -- with aloof disbelief -- replied
               | to only talked about pointers. I don't know what point
               | you think you are proving with these pithy replies. It
               | sure does go above my head. :)
        
               | tedunangst wrote:
               | Well, this is a thread about storing sql null. I'm trying
               | to suss out how sane languages represent sql null in a
               | way that can't go wrong. So far the answers mostly seem
               | to be use a type that can't represent sql null or use a
               | type that explodes when you use it wrong. With a side of
               | real programmers don't let bugs pass code review.
        
               | stouset wrote:
               | With all due respect, it appears as if you have _entered_
               | this conversation with this belief and have used that
               | perspective as a filter when other commenters have tried
               | to correct that perspective.
               | 
               | It may be surprising to learn that languages without
               | implicitly-nullable types are real, but you should
               | understand that none of these people would be trying to
               | tell you about this if these languages simply exploded
               | any time you used them the wrong way.
        
               | avgcorrection wrote:
               | > I'm trying to [sass] out how sane languages represent
               | sql null in a way that can't go wrong.
               | 
               | There is a difference between a type Pointer where `null`
               | is an instance of that type and a type Box which only has
               | valid (can be dereferenced) values. If the language has
               | e.g. algebraic data types and pattern matching then
               | Option(al)<Box> can be safely matched on and Box can be
               | used in the branch (the pattern match arm) where
               | Some(Box). Meanwhile in a language with Pointer and no
               | flow analysis any dereference of Pointer could lead to
               | some kind of "panic".
               | 
               | Say "unwrap()" all you want but the two approaches are
               | clearly very different.
        
             | giraffe_lady wrote:
             | Maybe Some other time.
        
             | worik wrote:
             | You cannot use a null pointer in Rust.
             | 
             | I am a Rust novice but I have not found a way to get to
             | uninitialised memory in safe Rust. Yet.
        
               | tedunangst wrote:
               | How do you create a pointer to uninitialized memory in
               | go?
        
               | TheDong wrote:
               | All it takes is a specially formatted comment
               | /*         int g() {          volatile int x;
               | return x;         }         */         import "C"
               | import "fmt"              func main() {
               | fmt.Printf("%d\n", C.g())         }
               | 
               | But I know you'll say that 'import "C"' or 'import
               | "unsafe"' is the same thing as using an unsafe block in
               | rust or such, and really shouldn't count against go.
               | 
               | Which is fair and true, but you're chasing down a
               | pointless detail. The point isn't that go is memory
               | unsafe. It's not. The point is that Go's type-system is
               | not powerful enough to express various types of type-
               | safety, and as such it's an error-prone language where
               | you can expect null pointer exceptions frequently.
        
               | stouset wrote:
               | You can't. But you can absolutely have a `nil` that will
               | unavoiad runtime panics if you don't check it. And
               | further, you only can't do this because go _defines by
               | fiat_ that the zero-value of a type must be legal. Of
               | course, this is wildly inconvenient and annoying for many
               | types, including those that come bundled in the standard
               | library.
               | 
               | There are other, very convincingly better options to this
               | that many in this thread have been trying to teach you
               | about in the expectation that your responses have been in
               | good faith.
        
               | Gwypaas wrote:
               | You can't. But a runtime panic also very easily causes an
               | outage. At least it's not as subtle.
               | 
               | Still. Rust is infinitely better in this regard. Go feels
               | like a crude hammer, especially regarding the default
               | interaction between deserialization and missing struct
               | members.
        
             | dtech wrote:
             | If this question is in good faith, for example Kotlin
             | differentiates between the nullable T? and the non-nullable
             | T. An if x != null check automatically casts T? to T. You
             | can't call a method on T?, preventing null pointer
             | exceptions. C# and Typescript have similar language
             | support. Other languages push users away from the problem
             | by favoring monadic Optional/Maybe solutions.
        
               | tedunangst wrote:
               | There are some differences here, but you can't for
               | instance use a string pointer with string functions in go
               | either. IMO The distinction between pointer and optional
               | is not as vast as people make it out to be.
               | 
               | (But thanks, I overlooked T? conversions inside if.)
        
               | akavi wrote:
               | The distinction between pointer and optional is not vast.
               | The distinction between pointer and _not_ optional is.
               | 
               | The whole idea of optional is to make _not_ optional
               | possible.
        
               | morelisp wrote:
               | You can call a value method on a nil pointer and it
               | panics, though, which I think was a mistake.
        
               | adastra22 wrote:
               | T? is just syntactic sugar for Optional/Maybe, no?
        
             | stouset wrote:
             | Type systems can enforce that types always contain a value
             | of that type and _cannot be null_. There is another type
             | that is either null _or_ a value of the enclosed type.
             | 
             | At any boundary where something might be null, you do the
             | null check. If it's null, you do whatever logic is
             | necessary _right there and only right there_. That might be
             | to skip the computation, to use a default value, to get the
             | value from somewhere else, to return an error, or whatever.
             | If it 's _not_ null, you use the internal value and from
             | then on every user can operate on a guarantee that it 's
             | not null.
        
         | [deleted]
        
       | AtNightWeCode wrote:
       | Maybe I passed out among the nonsense but most langs provide a
       | dbnull constant to check against, no?
        
         | jrockway wrote:
         | You could do that in Go, but it's not what database/sql's API
         | supports. You read columns out of each row with
         | "row.Scan(&col1, &col2, ...)". The types of col1 and col2 are
         | declared at compile time, and they don't have to be able to
         | represent the concept of null. So there would be no way to
         | store the state that represents that something was null.
         | 
         | You could of course have an API that just returns a slice of
         | "any", and conditionally check whether a value is of type
         | "string" or "mylibrary.NullValue" after the fact. This isn't
         | clearly better to me than the Scan API. You are going to have
         | to eventually cast "any" to a real type in order to use it;
         | with Scan the library does that for you.
         | 
         | Your own types can implement sql.Scanner to control exactly how
         | you want to handle something. (Indeed, your "Scan" method
         | receives something with type "any", and you need to check what
         | the type is and convert it to your internal representation.)
         | 
         | Also wanted to throw this out here; you don't have to be
         | satisfied with lossy versions of your database's built in
         | types. Libraries like
         | https://pkg.go.dev/github.com/jackc/pgtype@v1.11.0 will give
         | you a 1:1 mapping to Postgres's types. (I'm sure other database
         | systems have similar libraries.)
        
       | davidkuennen wrote:
       | Using go and postgres for my App's backend.
       | 
       | After using NULLs this way at first, I noticed it's generally
       | much much easier for me to just avoid nullable SQL columns
       | wherever possible (it was always possible so far). Most of the
       | time there is a much easier was to say a value is empty. For
       | strings '' for example.
       | 
       | This seriously made everything so much easier. Not necessarily
       | anything to do with go tho.
        
         | SoftTalker wrote:
         | I think this is generally good, unless your database treats
         | NULL and '' as the same thing.... e.g. Oracle.
        
       | eknkc wrote:
       | So, to avoid checking for null, you'll check for
       | `NullString.Valid` now? The string pointer is a part of the
       | language. You can pass it around, expose as part of a library
       | etc. And it conveys the intent perfectly.
       | 
       | I have no idea what is the issue here?
        
         | psanford wrote:
         | The nice thing about the Null* type is they can reduce the
         | number of allocations done on the heap and thus also reduce
         | total GC your program needs to do.
        
           | legorobot wrote:
           | I haven't thought about it that way! I've not hit the GC as a
           | performance wall when it comes to accessing nullable DB
           | values yet. Thanks for the insight.
        
             | throwaway894345 wrote:
             | It's generally useful, not just in SQL. Any time you have a
             | type that could be multiple things, you have to choose
             | between a reference-based implementation (e.g., interfaces)
             | or a tagged struct (a struct with a flag field that tells
             | you what its runtime type). The tagged struct version is
             | guaranteed not to allocate, while the interface version
             | very likely will. Most of the time it will only matter if
             | you're in a tight loop.
        
         | legorobot wrote:
         | I think the idea is `sql.NullString` can be used to have a SQL
         | NULL still be an empty string in Go (just avoid checking the
         | `Valid` field, or cast to string -- no check necessary).
         | 
         | It seems like the intent of a string pointer vs.
         | `sql.NullString` is their goal with this type anyways[1]?
         | 
         | In practice I've used a string pointer when I need a nullable
         | value, or enforce `NOT NULL` in the DB if we can't/don't want
         | to handle null values. Use what works for you, and keep the DB
         | and code's expectations in-sync.
         | 
         | [1]: https://groups.google.com/g/golang-
         | nuts/c/vOTFu2SMNeA/m/GB5v...
        
       | jackielii wrote:
       | I have an alternative solution to work with an existing struct
       | that you can't change, e.g. protobuf generated code:
       | https://jackieli.dev/posts/pointers-in-go-used-in-sql-scanne...
       | type nullString struct {         s *string       }
       | func (ts nullString) Scan(value interface{}) error {         if
       | value == nil {             *ts.s = "" // nil to empty
       | return nil         }         switch t := value.(type) {
       | case string:             *ts.s = t         default:
       | return fmt.Errorf("expect string in sql scan, got: %T", value)
       | }         return nil       }              func (n *nullString)
       | Value() (driver.Value, error) {         if n.s == nil {
       | return "", nil         }         return *n.s, nil       }
       | 
       | Then use it:                 var node struct { Name string }
       | db.QueryRow("select name from node where id=?",
       | id).Scan(nullString(&node.Name))
        
       | Mawr wrote:
       | Great, except:
       | 
       | 1. The type is merely a weak hint that you should check .Valid
       | before you use the value, there's no enforcement:
       | https://go.dev/play/p/nS8RxGujMBk
       | 
       | It's much better when the struct members are private and the only
       | way to access the value is through a method that returns (value,
       | bool):                 if value, ok := optional.Get(); ok {
       | // value is valid       } else {         // value is invalid
       | }
       | 
       | This is a strong hint that you should check the bool before using
       | the value. It's also a common go pattern - checking for existence
       | of a key in a map is done this way.
       | 
       | 2. We have generics now. Why use type-and-sql-specific wrappers
       | when you could use a generic Option? Example implementation:
       | https://gist.github.com/MawrBF2/0a60da26f66b82ee87b98b03336e....
        
         | psadauskas wrote:
         | Your comment is helpful, so this sarcasm isn't directed at you.
         | However, this looks like an extremely cumbersome way to wish
         | your language had the Result monad.
        
       | brushyamoeba wrote:
       | I'm surprised this doesn't discuss JSON marshalling
        
       ___________________________________________________________________
       (page generated 2022-05-23 23:01 UTC)