News for hackers. Stuff that matters.
Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @11:09AM (#37705393)
Unfortunately not.

    func main() {
     c := color.Red
     cptr := (*string)(&c)
     *cptr = "orange"
     PrintColor(c) // successfully compiles, and prints "orange"
    }
Re:Compile-time safety for enumerations in Go by tedunangst on Friday September 29, 2023 @11:40AM (#37705863)
At some point I think it's worth asking who are we trying to stop and why.
Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @12:13PM (#37706335)
Indeed. Go types are like simple locks: they keep honest users honest, but they don't prevent dishonest users from breaking your assumptions. The point is just that "compile-time type safety for enumerations" isn't actually true, or even possible. You always have to do some amount of runtime validation of received values.
Re:Compile-time safety for enumerations in Go by chabad360 on Friday September 29, 2023 @01:19PM (#37707351)
No, you don't. If your module relies on compile time checks for validity, there's no reason for you to also include runtime checks. Other developers breaking your defined contract is not your responsibility, it's not even a bug.
Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @02:33PM (#37708380)
> If your module relies on compile time checks for validity,

...then it's broken, because the Go compiler, factually, cannot (in general) check or enforce the validity of specific values.

> there's no reason for you to also include runtime checks. Other developers breaking your defined contract is not your responsibility, it's not even a bug.

It is absolutely the responsibility of the function

    func PrintColor(c Color)
to ensure, at runtime, that `c` is a valid Color. My example code -- which allowed an invalid Color value of "orange" -- is absolutely a bug in PrintColor.
Re:Compile-time safety for enumerations in Go by ori_b on Friday September 29, 2023 @05:27PM (#37710281)
Exactly. Someone could always connect to your program with a debugger and modify the instructions are being run, and that's why you need to do a runtime check of the validity of your program every time you execute an instruction. If you want to handle people actively trying to undermine the assumptions your program is making, you can't leave out easy to replicate cases like 'gdb attach', obvious, non-accidental use of the type system, and other similar issues.

What strategies do you use in your programs to ensure type safety in the event of debugging, mmap, and other systems that you can use to subvert the memory layouts your compiler thinks it produced?

Re:Compile-time safety for enumerations in Go by athrun on Friday September 29, 2023 @05:48PM (#37710501)
That doesn't make any sense to me.

Most instructions are not atomic, so even if you had runtime checks something like a debugger could still inject invalid state in between your checks, making them ineffective.

If real-time code injection is your threat model, I don't see how runtime checks would get you anywhere.

Re:Compile-time safety for enumerations in Go by wahern on Friday September 29, 2023 @06:53PM (#37711045)
You seem to be suggesting that (to use a common phrase) if something can be done, it should be done. The previous poster's example regarding a debugger was to challenge the notion that there was any meaningful reason to constrain non-accidental type subversion. You seem to be saying it's absurd to care about someone using a debugger to inject invalid code at runtime because there's nothing you can do about it, thus implying if you can do something, you should. But that only begs the question--why should you? To some people, defending against debugger code injection is no more absurd than defending against other examples where the programmer deliberately alters code to subvert static type checking. If you disagree then articulating more meaningful distinctions among cases would be worthwhile. Mere ability is generally not considered sufficient justification alone to do something.
Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @10:24PM (#37712286)
This is an inane comparison. Casting as demonstrated in the example is normal and has to be expected. Go types aren't as strict as types in other languages. Code can't assume otherwise.
Re:Compile-time safety for enumerations in Go by ori_b on Friday September 29, 2023 @11:04PM (#37712474)
> Casting as demonstrated in the example is normal and has to be expected.

Can you give an example where someone has done similar casts accidentally? It seems like it would be hard to accidentally typo similar kinds of casts. That leaves malice, which seems difficult to defend against, in light of the kinds of system calls that are available to a program.

Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @11:54PM (#37712647)
Sure, by parsing a string to a Color value.
Re:Compile-time safety for enumerations in Go by wahern on Saturday September 30, 2023 @12:43AM (#37712821)
The example given wasn't a simple cast of a value, and definitely not an implicit coercion. The example is more like C-style type punning where you explicitly cast a pointer to the value and then write through the dereferenced pointer.

I don't doubt that there are niches where such explicit type coercion patterns are common in Go and susceptible to mistakes, but I doubt usage of constant identifiers is such a niche.

Rust is currently the standard bearer for strong, static type safety, and it even has both the enum types and pattern matching construct which Go lacks. AFAIU, you can use unsafe{} Rust code to perform a similar type punning trick, successfully assigning an invalid value to an enum object. I don't know if Rust's code generator always inserts runtime validity checks in match statements on an enum value without a catchall/default case, but certainly it's possible for Rust code to have an explicit if/else chain that at compile time appears comprehensive but which would neither panic nor produce the expected behavior. Does that mean Rust programmers shouldn't rely on Rust's static typing, instead always adding explicit code to handle unknown/invalid enum values?

Maybe the assertion that Go code should have such checks is more reasonable than for Rust code, but you haven't explained how. At least to me, the simplest, minimal code to achieve the subversion in both Go and Rust seems similarly stilted and similarly unlikely to be written by mistake. (To be clear, the context of this subthread as I understand it assumes the interface method hack, the subversion of which requires the type punning.)

Re:Compile-time safety for enumerations in Go by kiitos on Saturday September 30, 2023 @02:59AM (#37713312)
I don't know what point you're trying to make, beyond just objecting to the points that I'm trying to make. Not interested in continuing.
Re:Compile-time safety for enumerations in Go by ori_b on Saturday September 30, 2023 @01:03AM (#37712886)
Can you point at a git commit where someone had a similar bug implemented by accident and fixed it? The code you posted earlier seems to be something that would be incredibly hard to write without malice. And if you're considering malice, you also need to consider mmap, foreign function interfaces, writes to /dev/memory, and other similar perverse mechamisms.
Re:Compile-time safety for enumerations in Go by gabereiser on Friday September 29, 2023 @06:35PM (#37710897)
It’s actually not and is one of those Java isms that have made its way into generalized software engineering.

If I write a function that takes Foo as an argument. I have a Foo implementation exposed elsewhere in my program. It is absolutely expected that I mean MY Foo, not your Foo. If you pass me your Foo, you will get unexpected results that are not a bug, not a side effect, not my responsibility. It’s yours, the caller, to adhere to the contract.

There are valid reasons to not adhere. To pass your own implementation. At that point, you are responsible for its use. Not me. If it adheres to MY Foo’s interface, you might get by, but it’s is not my responsibility to validate all permutations of unknown types to ensure you’re passing me mine. In go, it’s perfectly valid to return structs but accept interfaces as arguments so that you, the program author calling my api, can craft the correct program flow.

So please, leave that runtime type check reflection for Java and C# and the land of JS. We have no need for it here in machine code land.

Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @08:11PM (#37711620)
> it’s is not my responsibility to validate all permutations of unknown types to ensure you’re passing me mine.

Of course it is! If you provide an API that takes a Color, and you define valid Colors as exclusively Red, Green, and Blue, then you are absolutely responsible for validating input Color values and rejecting anything which is not Red, Green, or Blue. If you delegate that responsibility to the caller, then your API provides no meaningful encapsulation, and isn't a useful abstraction -- it's entirely leaky. No bueno.

Re:Compile-time safety for enumerations in Go by Nullabillity on Friday September 29, 2023 @08:24PM (#37711715)
By the same token, Rust has no type safety because std::mem::transmute exists. In practice, it's not a problem because transmute is not a valid way to construct or interact with values (outside of a few special cases). By calling transmute you're explicitly opting out of all safety and abstraction, and entering here-be-dragons territory.

(That said, the Go syntax looks far less "scary" than `unsafe { transmute::<_, Colour>("orange") }`, which I'd definitely call a design issue.)

Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @08:47PM (#37711831)
The code in my example is an entirely valid way to interact with values, and doesn't represent an "opting out" of safety or abstraction.
Re:Compile-time safety for enumerations in Go by gabereiser on Friday September 29, 2023 @09:31PM (#37712052)
‘type Color string’ is just a type alias for string.

Likewise GLint is just a type alias for int. There are only value types (str, int, float, etc), everything else is a construct. The only true types are those value types (and pointers to them). If you call a type a Color and I call a type a Color, you are using my lib to build a program (not me using yours), you must adhere to my contract of what a Color type is to my API. Period. You can not call a function with an unknown type and expect it to behave properly.

If it panics, it’s your fault.

Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @09:53PM (#37712161)
The point here is that, in Go, "what a Color type is" isn't something that can be enforced by the compiler. And more specifically, Go doesn't allow you to define reliable enums.
Re:Compile-time safety for enumerations in Go by gabereiser on Friday September 29, 2023 @10:41PM (#37712362)
It is enforced by the compiler, what it doesn't do is guarantee it at runtime. I agree with you on the enums. Go doesn't have a reliable enum construct. But the argument that I, the library author, must validate and check against any possible type of "color" you, the program author, can come up with is just crazy talk. I'll provide a Color type, I'll even pre-define some colors for you, but if you send me a Color that's rainbow, I'll panic.
Re:Compile-time safety for enumerations in Go by kiitos on Saturday September 30, 2023 @12:03AM (#37712681)
It literally isn't enforced by the compiler, because I can compile code which is invalid.
Re:Compile-time safety for enumerations in Go by gabereiser on Monday October 02, 2023 @08:58AM (#37737486)
It’s not invalid because a string is a string. You didn’t invent a new type.
Re:Compile-time safety for enumerations in Go by revoly on Friday September 29, 2023 @11:16AM (#37705494)
I don't think the solution is trying to cover this. If a user does type conversion then usually they know what they are doing. It's like going out of the way. The solution is to just hide a type behind an interface to be explicit about it and to avoid the error that would have gone unnoticed otherwise.
Re:Compile-time safety for enumerations in Go by throwaway894345 on Friday September 29, 2023 @11:22AM (#37705588)
The type conversion is a red herring. He's just changing the value of a variable by referencing and dereferencing a pointer. In other words, he's not using a pointer conversion to change the value of `color.Red`, he's creating a new variable `c` and assigning `color.Red` to it, and then changing the value of the variable through the pointer and type conversion. But he doesn't even need to do the pointer/type conversion stuff, he could just do `c = Color("orange")` and call it a day.
Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @12:09PM (#37706279)
> But he doesn't even need to do the pointer/type conversion stuff, he could just do `c = Color("orange")` and call it a day.

c = Color("orange") does not compile.

Re:Compile-time safety for enumerations in Go by throwaway894345 on Friday September 29, 2023 @03:29PM (#37709046)
Ah, I misunderstood what type color.Red had. My mistake.
Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @12:14PM (#37706349)
The article is titled "Compile-time safety for enumerations in Go" but my example demonstrates that this is not the case.
Re:Compile-time safety for enumerations in Go by taeric on Friday September 29, 2023 @12:21PM (#37706444)
The counter point is that this is like arguing that an article about safe knife skills are silly, because you can still just stab yourself?
Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @12:56PM (#37706960)
I'm not sure how that's comparable. Compile-time type safety is a boolean thing, you either have it or you don't. If you have it, then my example shouldn't compile. But it does compile, so you don't have it.
Re:Compile-time safety for enumerations in Go by taeric on Friday September 29, 2023 @01:55PM (#37707867)
I think by insisting to view it only as a boolean thing is very limiting. By that measure, are the type safety hints of Python not really giving any type safety? Just give up and go home, as it were? :D

I'm sympathetic to the points, I think. But I also don't have a problem with someone pushing for some type safety as long as you avoid actively trying to go against it.

Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @02:43PM (#37708510)
> By that measure, are the type safety hints of Python not really giving any type safety?

If we're talking compile-time, then: yes, absolutely!

Type hinting doesn't provide type safety, because type safety isn't a spectrum.

Re:Compile-time safety for enumerations in Go by throwaway46281 on Saturday September 30, 2023 @09:18AM (#37715157)
To me it's pretty clear that safety is a spectrum (e.g. we give cars safety ratings), and therefore so is type safety.

Is "type safety" something you saw defined in a PL book or something, which has a more rigid definition to you?

Re:Compile-time safety for enumerations in Go by arp242 on Friday September 29, 2023 @09:54PM (#37712166)
Out of 10 possible errors a compiler can catch 0, 1, 2, 3, etc. up to 10.

A compiler that catches 7 errors is more safe than one that catches 4, and one that catches 4 is more safe than one that catches 0.

Re:Compile-time safety for enumerations in Go by dgb23 on Friday September 29, 2023 @05:40PM (#37710417)
You demonstrated that you can deliberately circumvent it.
Re:Compile-time safety for enumerations in Go by paulddraper on Friday September 29, 2023 @06:23PM (#37710808)
It is the case under certain conditions.

Casting is by definition an escape hatch.

Re:Compile-time safety for enumerations in Go by kiitos on Friday September 29, 2023 @08:12PM (#37711633)
Casting (in the way I did it in my example) is entirely normal and common and in no way an escape hatch.
Re:Compile-time safety for enumerations in Go by paulddraper on Friday September 29, 2023 @10:13PM (#37712242)
Ask yourself: what does casting do?
Re:Compile-time safety for enumerations in Go by throwaway894345 on Friday September 29, 2023 @11:20AM (#37705554)
You're not changing the underlying value of `color.Red`, you're changing the variable (in other words, `PrintColor(color.Red)` will still print `red`). If that's the point you're intending to make, then couldn't you make it more easily with this?:

    c := color.Red
    c = Color("orange")
    PrintColor(c)
Re:Compile-time safety for enumerations in Go by masklinn on Friday September 29, 2023 @11:36AM (#37705800)
> You're not changing the underlying value of `color.Red`

Doesn't matter, the point is they're getting in a value which is not part of the defined set, demonstrating that this emulation is not actually typed-safe.

> If that's the point you're intending to make, then couldn't you make it more easily with this?:

    cannot convert "orange" (untyped string constant) to type Color
Re:Compile-time safety for enumerations in Go by throwaway894345 on Saturday September 30, 2023 @03:13AM (#37713360)
Yeah, I thought Color was `type Color string`. I misunderstood what he was doing.
Re:Compile-time safety for enumerations in Go by avg_dev on Friday September 29, 2023 @01:19PM (#37707343)
i appreciate this counter-example; thanks. i was hoping there was a way to get real enums in go.
Re:Compile-time safety for enumerations in Go by gabereiser on Friday September 29, 2023 @10:44PM (#37712369)
In this example, *cptr is a color by contract. A Color is a type alias of a string. *cptr is a string. Interfaces match.
Re:Compile-time safety for enumerations in Go by kiitos on Sunday October 01, 2023 @10:56PM (#37733436)
Technically not a type alias, just a normal type declaration. But yes, this is my point: you can't assume that an enum type will always be one of a fixed set of values that you've defined. You always have to do at least a little bit of runtime validation of the parameter. It's unavoidable.
Re:Compile-time safety for enumerations in Go by gabereiser on Monday October 02, 2023 @08:40AM (#37737325)
alias and type redefinitions are conceptually the same. int and int_t are conceptually the same. I’m going to assume it’s my type, as defined in the signature of my method you’re calling. If you don’t adhere to the contract then the onus is on you. I’ll check existence, I’ll check conformity, but I won’t check that your string = my string.
Re:Compile-time safety for enumerations in Go by kiitos on Monday October 02, 2023 @09:38AM (#37737926)
> alias and type redefinitions are conceptually the same.

In Go, they are not. Aliases establish precise equivalence between types, type redefinitions establish new types altogether.

Re:Compile-time safety for enumerations in Go by hamdouni on Saturday September 30, 2023 @06:09AM (#37714077)
It's the game of the mouse and the cat :-D We can change the package color like this :

  package color

  type Color interface {
    void()
  }

  type color struct {
    v string
  }

  func (c color) void() {}
  func (c color) String() string {
    return c.v
  }

  var (
    Red   color = color{"red"}
    Green color = color{"green"}
    Blue  color = color{"blue"}
  )
Re:Compile-time safety for enumerations in Go by kiitos on Sunday October 01, 2023 @10:37PM (#37733330)

    func fn(c color.Color) {
     switch c {
     case color.Red, color.Green, color.Blue:
      log.Printf("c=%#+v -- OK", c)
     default:
      log.Printf("c=%#+v -- should not be possible", c)
     }
    }
    
    func main() {
     var c color.Color
     fn(c)
    }    
Output:

    c=<nil> -- should not be possible
:shrug: