Languages with Algebraic Data Types will help you get practical knowledge on how to use these types. Basically types share an isomorphism with algebra allowing certain types to share properties with algebra, hence the name sum types and product types.
Bool has a cardinality of two. True and False. A Product type (essentially a tuple) of two bools represents a bool and another bool and has a cardinality of 4. (True, True), (True, False), (False, False), (False, True). The cardinality of a product type is essentially the product 2 * 2 of the cardinality of both individual types. Product types represent the concept of AND: a bool AND another bool. The product type in itself is a type evaluated as the product of two (or more) types. In most languages this concept is represented as a struct or a tuple.
To remove confusion for sum types let us create a type with a cardinality of 4. FuzzyBool. It has 4 instances: VeryTrue, SortaTrue, SortaFalse, VeryFalse. A sum type represents the concept of OR. Let's see a sum type of a Bool OR a FuzzyBool (Bool + FuzzyBool). The new type can either be one of: (True | False | SortaTrue | SortaFalse | VeryTrue | VeryFalse). The cardinality is 4 + 2. Hence the term sum type.
Languages are typically asymmetric in types due to the lack of the explicitly offering the user the ability to combine types to form a sum type.
The Golang return value for functions is actually the opposite of a sum type. It is a product type. Rob Pike was likely unaware that you can create a type that is either (File* or Error) and instead utilized ((File* | null) && (Error | null)) to attempt to keep error handling within the type system. It results in IO functions having a return type with Cardinality of greater than 4: (File, nil), (File, err), (nil, nil), (nil, err) and they just expect you to ignore (File*, err) and (nil, nil) and assume it will never occur even though the type system does not restrict it from occurring.
If you want to learn this stuff imperatively I would recommend Rust, however you could miss the big picture with Rust due to the borrow checker and the option to do typical C-style programming that avoids utilizing the ADT's to the full extent of their power.
I would recommend diving into something like Haskell and getting to know algebraic data types and exhaustive pattern matching. You can avoid fully understanding Monads if all you want to understand is Sum types. Haskell will force you to utilize sum types very early on via exhaustive pattern matching.