Like we do with async. Equivalent to @call(.{ .modifier = .always_inline } ...), but much clearer and more concise. It's already a reserved keyword, it won't be a breaking change.
This is intentional.
People shouldn't be manually inlineing functions except in very rare situations, in which case the explicitness is desirable.
I thought there might have been a reason like that. Fair enough, closing.
@call(.{ .modifier = .always_inline }, function, .{}); isn't any more explicit in what it's doing than inline function();. I'd say it's less explicit because it's not clear that function is being called if you're not familiar with the language.
There is some justification for having the decision to inline be at the callsite rather than a function attribute. First off, Zig already has precedent of giving the callsite the responsibility of specifying how a function is called with async and comptime. Secondly,
People shouldn't be manually
inlineing functions except in _very_ rare situations
Marking a function as inline is already manual inlineing, it's just applied to all of that functions callsites. IMO, this is a historical antipattern since the decision of whether or not to inline depends heavily on the callsite, and it is why manual inlining is largely seen as bad. Inlining is inherently an optimization at the callsite. Moving inlining semantics to the callsite lets both the programmer and compiler make informed inlining decisions.
Example to drive the point home. Say you have a very large function that is called all over your codebase. This is the classic kind of function you do not want to inline. But one of the places you're calling it is right at the beginning of your main loop. Here you do want it inline since it will be called several times each second and the performance gain turns out to not be insignificant. So if you mark the whole function inline your binary size blows up and the perf loss from instruction cache misses far outweighs the gain you thought you'd get. But if you mark only the call in the main loop inline, you get the perf gain, you get the small binary, and the compiler still gets to decide whether or not to inline that same function elsewhere.
I don't think this issue should be closed so quickly.
I think it's self-evident which of the following is more readable:
std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
std.os.exit(inline callMainWithArgs(argc, argv, envp));
I agree that manual inlining of functions should be used very sparingly, but this is not a reason to throw away readability of the syntax. The change would not make things any less explicit.
@EleanorNB would you mind reopening this for further discussion and consideration?
These are good points. Reopening.
Marking a function as
inlineis already manualinlineing
I agree with this reasoning quite a bit. For the usual suspects of accessor functions that simply forward data to the callsite these will of course be inlined at the compiler's discretion anyway.
Typically, if you're inlining code it's for a purpose beyond simple optimization. Transitioning this to the callsite allows for a more case-specific approach, similar to how in Zig defer is used over class destructors.
Subtlety: this would shift not only flow control to the callsite, but codegen coordination. When a function is marked as inline at the definition site, the compiler can optimise it for that use case the first time, and work around callers accordingly -- however, when it is only annotated as such at the callsite, it may have to perform that step multiple times.
Another point: with definition-site inlining, there is much lower friction to implementing intrinsics in userspace with inline assembly. On the downside, it is not immediately obvious whether a particular operation is a function call or a macro, although the false negative rate is still zero, plus this is already status quo with comptime and builtins.
Given this, and the relative nicheness of inlining a function some times and not others (plus the comparative lack of use case for inline-agnostic code vs., say, async), I think this issue should be closed.
Most helpful comment
@call(.{ .modifier = .always_inline }, function, .{});isn't any more explicit in what it's doing thaninline function();. I'd say it's less explicit because it's not clear thatfunctionis being called if you're not familiar with the language.There is some justification for having the decision to
inlinebe at the callsite rather than a function attribute. First off, Zig already has precedent of giving the callsite the responsibility of specifying how a function is called withasyncandcomptime. Secondly,Marking a function as
inlineis already manualinlineing, it's just applied to all of that functions callsites. IMO, this is a historical antipattern since the decision of whether or not to inline depends heavily on the callsite, and it is why manual inlining is largely seen as bad. Inlining is inherently an optimization at the callsite. Moving inlining semantics to the callsite lets both the programmer and compiler make informed inlining decisions.Example to drive the point home. Say you have a very large function that is called all over your codebase. This is the classic kind of function you do not want to inline. But one of the places you're calling it is right at the beginning of your main loop. Here you do want it inline since it will be called several times each second and the performance gain turns out to not be insignificant. So if you mark the whole function inline your binary size blows up and the perf loss from instruction cache misses far outweighs the gain you thought you'd get. But if you mark only the call in the main loop inline, you get the perf gain, you get the small binary, and the compiler still gets to decide whether or not to inline that same function elsewhere.
I don't think this issue should be closed so quickly.