I think my proposal in the comments of that issue was “”““probably””“” (Heavy pinch of salt) the simplest solution, have |T| for capturing the type for the type of arguments, also |R| for the return type, and have the |R| be inferred based on all the return types that are triggered by the combination of arguments passed to the function. This way we remove peer-type-resolution invocation from most of the language. We don’t need to implement a worst peer-type-resolution in user space and call it to get the return type of a function, we can actually name the return type of the function. and on top of that it goes well with the |T| syntax. And |R| would have side effects at runtime since it determines the type of the function. So unless i’m missing something I think that my counter-proposal was pretty good, and probably a good direction.
the one thing I don’t like about your proposal is that I need to read the body to understand the return type, the type signature isn’t enough. In general I don’t like the C++ auto f() because I’d like to just read the fn signature to have an idea of what it does.
Personally I much preferred the proposition that just as the argument types are captured from the call-site, so would the return type. It would mean that you would need to have a label of a known type that you were assigning to at the call-site. Assigning to something without an explicit type wouldn’t work.
fn foo(a: |T|, b: u8) |R| {
....
return something; // of type R
}
const a : i16 = 0;
const f : f32 = 0;
const x : u8 = foo(a, 3); // T=i16, R=u8
const x : u16 = foo(f, 3); // T=f32, R=u16
const x = foo(a, 3); // Error: No type specified
foo(a, 3); // T=i16, R=void - Probably an error because of the return statement.
It just feels simpler to use than having to write type expressions to resolve the return type, or understand the type resolution rules. It’s explicit, and if the types aren’t compatible the compilation will give the same error it gives for any other type mismatch.
However, I doubt this will happen, and it’s not that different to today’s syntax for controlling the return type:
fn foo(R: type, a: |T|, b: u8) R {
....
return something; // of type R
}
const a : i16 = 0;
const f : f32 = 0;
const x = foo(u8, a, 3); // T=i16, R=u8
const x = foo(u16, f, 3); // T=f32, R=u16
foo(void, a, 3); // T=i16, R=void - Probably an error because of the return statement.
So I end up in camp “type captures for arguments only”.
Now you’re making a comment against the core team, which isn’t really productive. Maybe I missed a joke there. Let’s keep the discussion to the technical details and tradeoffs.
Yeah I don’t like it either, but I think it’s the most intuitive behavior, and tbf the status quo isn’t much clearer, but obviously if you have an idea that’s better and not more complex I’d be glad to hear my idea isn’t perfect for sure, and while i think it’s better than status quo, it’s not clear to me that it’s the best.
I don’t take that to be a comment “against the core team.” More, it’s pointing out that one thing separating many builtin functions from being implementable in userland could be made available to users.
Inferring a function’s return Type would be immensely helpful in several contexts. It could also greatly reduce the number of builtins (though that might not be desirable for users forgoing the stdlib).
That’s not to say it would be easy for the core team to implement, or even that it would fit within their vision for Zig. But it’s perfectly fair for users to want the feature and expressing that shouldn’t be seen as a dig against the core team.
I framed it as a joke, but it’s a classical dilemma in programming language design. How much of the language can be implemented in user land? Eg for some time Go std hasmap was the only generic type of the language and you couldn’t do the same in your library.
Similarly in Zig builtins can leverage the LHS type to change their behavior.
Before it was x = @intCast(u32, len) now it’s x: u32 = @intCast(len). You can implement the original intCast in your library, you can’t implement the second.
a beginner programmer here, so not a very informed opinion, but i much prefer the ‘infer T’ syntax compared to ‘|T|’. to me, its more explicit, informative and easer to read. the ‘|T|’ works well as its used now, to capture payload and other things, i see no reason the reuse it for a different feature.