I'm happy it's getting fixed!
Completely unrelated: can anyone tell me why language authors introduce in/out/inout function parameters (see also C#).
It feels like this is some rather bad workaround for something that breaks assumptions about parameters being passed into the function.
If the allocation is owned by the calling function which uses it internally and then doesn’t return it, it can be a stack variable. Or the same allocation can be reused many times in a loop. Or an array of allocations can be made all at once with foreknowledge of the total number.
But you might be conflating that with in/out for Contra/Co-variance when specifying a generic.
They are one way to implement multiple return values. For multiple returns, tuples can be used. In fact, async methods can’t declare out params
if (int.TryParse(str, out var value) && value == 5)
{
// value is available
}
// value also availableI feel that is the kind of broken default that will come back and bite a bunch of people, and will eventually need to be fixed in a v3.
type ListUsersResponse struct {
Users []User `json:"users"`
}
in Go. It's pretty common have code like userRows := db.Query(...)
var users []api.User
for _, user := range userRows {
users = append(users, api.User{...})
}
where you don't _really_ intend for the default nil-ness to leak out. (Apologies for any broken syntax, I'm realizing in real time that it's been a while since I've written Go)This is definitely not what I expect in other language. I would argue this is a quick of Go though and not JSON's fault.
That seems like a bad default to me.
Just look at the inane rules for map and slice access, what gets nil, what gets the zero value, which built-in mist be used. Even if that were consistent, then you'd still left to wonder if json.field was truly zero or not there.
Not necessarily. Among other things, MarshalJSON methods called during marshaling can return an error for whatever reason the developer sees fit - one possibility that comes to mind is that part of the object tree being marshalled is lazily loaded, and failed to do so.
And, as others have mentioned, there are types like "any" whose marshalability can't be determined at compile time. Those types aren't necessarily at the top level, either; they could appear anywhere in the object tree.
Plus the point of the marshal interface is that it’s reusable.
If you wanted type safety with JSON in Go then don’t use the marshal interface. Period.
And even for unmarshalling you could get a compile time error if you try to unmarshall to a type that doesn't support it.
Go has this concept baked into the language. For example read and write interfaces so you can write data to an endpoint without having to worry about whether it’s a file system object, TCP/UDP socket, gzip achieve, and a plethora of other options too.
It’s a nice convenience feature and makes up considerably for the restrictions that the Go type system places.
And if you need higher performance code, or compile time safety, or have any other constraints that limit your ability to use these generalised interfaces, then you can use different APIs from the Go standard library to achieve the same thing.
You don’t need to call json.Marshal() if you don’t want to. It’s an option, but it’s just one API into the JSON toolbox.
Secondly, other languages like rust, haskell, scala, etc. Have json APIs that are typesafe and give you compile time errors if you try to use a type that doesn't support (de) serialization . In the case of rust at least, the API is also general and can be used for other data types as well.
Even in go, you could have some more type safety if Marshall took an interface (such as MarshallerTo) rather than `any`. Although then you have the problem of how you implement that interface. So I guess you probably want some kind of "derive" functionality, or macros that can approximate it for a convenient and type-safe API.
P.S. gophers like to talk about how explicit being better than implicit, but using reflection for marshalling is very implicit.
If you want type safety then pass a struct to the marshal. Which is actually the idiomatic way to do it anyway. The type safety would be in that.
I’m not going to disagree with you that Gos type system is a rather blunt tool nor that Gos expressiveness is extremely rigid compared to many other popular languages. All of that is completely true. But I don’t agree that the v2 JSON API is a good example of that problem.
Compile time cannot check for runtime values (not even in Rust) - is your position that fields can only be populated at compile time?
I think the same is true for channels too but I’m not near a computer to check.
I’ve abused the v1 API heavily* and haven’t found anything it couldn’t marshal. Though I’m yet to apply the same “fuzzing” (for lack of a better term) to v2.
* the v1 JSON marshaller underpins some core functionality in my alternative $SHELL. So I’ve thrown a lot of exotic type structures (by Gos standards at least) at it.
Because they are common types, makes sense to precautionary have the error return.
"What if it sucks?"
"There are now TWO ways to do each thing! No more than TWO! I promise!"
It's like python again.
So yeah, if you ignore most of what matters, everything is the same.