4 ms·
A quirk of Go is that I can cast a `[]string` to `interface{}`, but I cannot cast `[]string` to `[]interface{}`. This blog post is my go-to explanation for why
by zknill 2y ago
A quirk of Go is that I can cast a `[]string` to `interface{}`, but I cannot cast `[]string` to `[]interface{}`. This blog post is my go-to explanation for why the second is not possible but the former is.
- rednafi 2y agoI didn’t know even know this. TIL, thanks.
- saurik 2y agoIs that really a "quirk" of specifically Go? I am pretty sure Java--the implementation of which defined a lot of how numerous languages handle this kind of thing--has the same behavior: you can cast String[] to Object, but not Object[].
- kgeist 2y agoC# allows casting string[] to object[], but this requires the compiler to insert runtime type checks each time the array elements are modified: object[] array = new string[10]; // throws a run-time exception array[0] = 10;
- neonsunset 2y agoNote that array covariance is largely considered a design mistake and if .NET was to be redone it wouldn't have one (and a bunch of smaller things that are an artifact of generics getting introduced in C# 2.0 and not 1.0, well, the problem is much worse in Go heh). Other data structures like List<T>, Span<T>, etc. do away with covariance. There's an upcast for ReadOnlySpan<T> but only because it's zero-cost and does not introduce the issues stemming from covariance. Luckily, you almost never see someone use array covariance beyond occasional object[] upcasts (and the compiler is also good at reasoning whether to insert covariance checks or not).
- pjmlp 2y agoWell, .NET only exists in first place because of Sun's lawsuit, Ext-VOS was designed with J++ in mind. Thus having high compatibility with how Java used to be was a big factor designing C# 1.00
- neonsunset 2y ago25 years ago :)
- pjmlp 2y agoYes, and 25 years later Java is still relevant enough that Microsoft has become yet again a Java vendor, with their own distribution, made the key contribution for Windows ARM support, VSCode Java experience is still ahead of C# DevKit thanks collaboration with Red-Hat, and doesn't require an additional licence. At the same time, JVM has embraced the polyglot ecosystem CLR was supposed to be, while C# seems to have sucked the life of all those language implementations demoed at the launch back in 2001, and .NET SDK being offered on computer magazine CDs. Which is kind of interesting, how things have changed 25 years later. At least we finally have cross platform support (ignoring Mono and DotGNU efforts), and good AOT instead of NGEN, which should have been there on .NET 1.0.
- neonsunset 2y agoIf you have to reach for popularity as an argument in favour of technology X or platform Y, perhaps it's not a technical point you want to make? > VSCode Java experience is still ahead of C# DevKit thanks collaboration with Red-Hat, and doesn't require an additional licence. I'm not sure if you're intentionally attempting to make inflammatory replies or something in what I said rubbed you the wrong way. (for other readers - this marks me posting for 20th time here that DevKit is an optional product and thousands of developers are happily coding in C# in VS Code and VSCodium, Neovim and Emacs without ever running into it)
- 2y ago
- skitter 2y agoC# inherited this from Java, which added it because it was designed without generics and therefore decided that the additional expressiveness provided by enforced array covariance was worth the tradeoff of having an unsound type system.
- sltkr 2y agoActually in Java you can cast String[] to Object[] although this is technically unsound. This how generic methods like Arrays.sort() are implemented.
- saurik 2y agoHuh, fair enough!! What's then funny in the context of this thread is that, while I am sure I knew that 20-25 years ago, it feels so wrong that it would support that -- and more like a "quirk" that it ever would -- that it surprises me (though it certainly doesn't shock me: most things have many quirks).
- duskwuff 2y ago> I can cast a `[]string` to `interface{}`, but I cannot cast `[]string` to `[]interface{}` Surely that's just contravariance, though? You can't cast []string to []any because that would allow you to write non-strings to it.
- saghm 2y agoYeah, I have plenty of criticisms of Go, but this is something that it got right. For those who aren't familiar with the issue, in Java you can assign an array of a subclass to a variable declared as an array of the superclass, which leads to issues if you actually try to mutate it. Imagine if Cat and Dog both inherit from Animal, assigning a Dog[] to an Animal[] is totally valid, but then setting one of the elements to a Cat will throw an an exception.
- nlitened 2y agoI can’t help but keep noticing that all of the notorious problems with OOP are not due to inheritance, but due to mutability.
- saghm 2y agoI don't know if I disagree with you or not, but having not used a language with inheritance with a while, I also don't really miss it at all. Even if it doesn't cause problems on its own, it just doesn't feel like it provides that much that can't be provided via some other mechanism either. For what it's worth, I'm not sure this is even something I'd consider a "notorious" problem with OOP because the solution is really simple and wouldn't break anything else if done correctly from day one: just don't allow using arrays of subtypes in a place where an array of its supertype is expected. This was already known at the time Java was designed, and I'm pretty sure the people who designed Java knew it too; they just didn't pick the right way to handle it in my opinion. There's nothing inherent about OO that makes this bug that much harder to deal with than any other language because it can happen in any paradigm with subtyping (as demonstrated by the fact that it could have been present in Go if they did allow casting arrays of interfaces in that way).
- 2y ago
- philippta 2y agoBecause the latter would incur a hidden O(n) computation.
- TheDong 2y agosince when has that stopped go? “[]rune(someString)” is O(n) and quite inobvious The O(n) loop is here: https://github.com/golang/go/blob/215de81513286c010951624243c2923f7dc79675/src/runtime/string.go#L239 https://github.com/golang/go/blob/215de81513286c010951624243...
- foldr 2y agoIt’s obvious that this is O(n), no? You’re casting something immutable to something mutable, so it has to be copied.
- debugnik 2y agoAnd decoding subsequences of UTF-8 code units (bytes) into Unicode scalars (runes), which also forces a copy. But why did Go pick the same syntax for cheap conversions and expensive ones, though? I'd expect this to be a standard function, not a type conversion.
- foldr 2y agoDon't all conversions require copying the value at the level of the language semantics? You can cast a float to an int, but you can't assign an integer value to a float variable (leaving aside whatever optimizations the compiler might make). Casting a float to an int is only cheap because a float is a fixed size. So it seems unsurprising to me that casting a larger value results in a proportionally larger copy.
- TheDong 2y agoMost conversions are things like casts to enum types or aliases I think, which never are O(n), like: dur := time.Second * time.Duration(2) headers := http.Header(map[string][]string{}) httpDir := http.Dir(filepath.Join(parts...)) In all of these cases, it's just a type-cast which is zero-cost, and I think that's what makes it feel surprising that casting between strings/runes specifically incurs more significant computation than any other cast.