-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Make function definitions expressions #1717
Comments
I love the idea overall, but wonder about the syntax a little. Defining the function and the function type is a little too close: const A = fn(i32) void;
const B = fn(x: i32) void {};
var C: A = B; @Hejsil just redid the stage 1 parse and probably could say if this can be parsed correctly. |
given we have syntactic sugar already in the form of |
@bheads Parsing fn defs and fn photos uses the same grammatical rules already, so this proposal doesn't make a difference in how similar these constructs will be. @emekoi Given that Zig values "only one way", probably not. Pretty sure |
@emekoi I know. We give syntatic sugar when it really affects the readability to not have it. Things like |
@bheads To me the syntax seems consistent in that curly braces after a type instantiate that type. const A = struct {a: i32}; //type expression/definition
const a = A {.a = 5}; //instantiation
const F = fn(a: i32) void; //type expression/definition
const f = F { return; }; //instantiation When instantiating a function type ( |
@rohlem I've contemplated that "define/instantiate a function of named type
I agree that level of consistency is cool and enticing, but I think in this case it clearly works against Zig's goals. |
@hryx For the record, I overall agree with your stances.
Either way, just adding to the discussion. Sorry for hijacking the thread, I definitely don't think the details about decoupling parameters should stand in the way of the original proposal. |
I agree with @hryx:
We could approximate the switch case syntax and do something like, which opens the door for function expressions:
|
@raulgrell That would also solve the ambiguity with braces in the return type. |
What if instead if the fat arrow (=>) we instead use the placeholder syntax of while and for loops. This allows the separation of parameter names from the type specification. Examples: // Typical declaration
const add = fn(i32, i32)i32 |a, b| {return a + b;};
// Usable inline
const sorted = std.sort(i32, .{3, 2, 4}, fn(i32,i32)bool |lhs, rhs| {return lhs >= rhs;});
// With a predefined type.
const AddFnType = fn(i32,i32)i32;
const otherAdd = AddFnType |a, b| {return a + b;}; Additionally, in line with #585, we could infer the type of the function declaration when obvious // Type is inferred from the argument spec of sort
// However, the function type is created from the type parameter given
// earlier in the parameters, so I'm not sure how feasible this is
const sorted = std.sort(i32, .{3, 2, 4}, .|lhs, rhs| {return lhs >= rhs;}); We could even make the definition of the function take any expression, not just a block expression, but that may be taking it too far. I think there is a lot of potential in this feature to provide inline function definition clarity without a lot of cognitive overhead. (Please forgive any formatting faux pas, this was typed on mobile. I'll fix them later.) |
The following is already possible (version 0.4):
I prefer the "standard" way of defining functions as it is more visually pleasing to me, but I don't see any real problems with this proposal either. |
This is now accepted. @williamcol3 interesting idea, but I'm going to stick to @hryx's original proposal. Feel free to make a case for your proposed syntax in a separate issue. The path forward is:
Extern can be its own syntax, or it can be downgraded to builtin function, which might actually help #1917. |
Wasn't a goal of Zig to say close to the syntax of C? I would say with this change, there is quite a bit difference compared to C. This would make the step for current C developers to move to Zig way bigger. However, the change makes sense in the current expression system of Zig and I like it, but I think that this is one extra step to overcome for C developers moving to Zig. |
Extern functions could just be variables with a function type, but no content: // puts is a function value with the given function type
extern const puts : fn([*]const u8) void;
// main is a function with the implicit type
const main = fn() {
puts("Hello, World!\n");
};
// foo is a function of type `fn()`
const foo : fn() = fn() {
puts("called foo\n");
}; For me this seems logical if we treat functions as values, we can also declare those values extern => consistent syntax for declaration of |
the type here could be inferred (similar to enum literals), making it: const sorted = std.sort(i32, .{3, 2, 4}, |lhs, rhs| {return lhs >= rhs;}); Which isn't a bad "short function syntax" at all.... @williamcol3 please do make another issue for your proposal. |
Could the function passed to |
I just noticed that this proposal has been accepted and thought I'd throw my two cents in. I don't see a way of applying the |
@leecaonnon ahh, that makes a lot of sense actually, guess the only grounds i have is the whole "aliasing" thing from the additional reasoning. so it would be just used for type and function definitions/assignment, probably too much to introduce a new operator for that though lol, even if i do like how it looks 🥲. thanks for the clarification! |
Allow me to address the two goals of this proposal:
This proposal does an excellent job at accomplishing this goal, and is the reason I originally accepted it. However, I will now make an argument that there is a good reason for there to not be syntax consistency between function declarations and other declarations. Ultimately, Zig code will output to an object file format, an executable binary, or an intermediate format such as LLVM IR or C code that is ultimately destined for such a place. In those places, functions have symbol names. These symbol names show up in stack traces, performance measurements, debugging tools, and various other things. In other words, functions are not unnamed. This is different from constants and types, which may exist ephemerally and be unnamed. So, I think the syntax inconsistency appropriately models reality, making Zig a better abstraction over the artifacts that it produces.
I have rejected both of these proposals. In Zig, using functions as lambdas is generally discouraged. It interferes with shadowing of locals, and introduces more function pointer chasing into the Function Call Graph of the compiler. Avoiding function pointers in the FCG is good for all Ahead Of Time compiled programming languages, but it is particularly important to zig for async functions and for computing stack upper bound usage for avoiding stack overflow. In particular, on embedded devices, it can be valuable to have no function pointer chasing whatsoever, allowing the stack upper bound to be statically computed by the compiler. Since one of the main goals of Zig is code reusability, it is important to encourage zig programmers to generally avoid virtual function calls. Not having anonymous function body expressions is one way to sprinkle a little bit of friction in an important place. Finally, I personally despise the functional programming style that uses lambdas everywhere. I find it very difficult to read and maintain code that makes heavy use of inversion of control flow. By not accepting this proposal, Zig will continue to encourage programmers to stick to an imperative programming style, using for loops and iterators. |
Maybe I had to re-read this proposal first, but regarding function names there may be mixed declarations for functions: pub const sort = fn(comptime T: type, arr: []T, f: SortFn) {
// Anonymous, auto-generate name if exported (e.g. some_module_sort_auto_blabla_1)
// Bad for reproducible builds, but could be generated in a predictable way
}; pub const temp_sort = fn sort(comptime T: type, arr: []T, f: SortFn) {
// Named, exported as `sort`, if you need a cute name somewhere
}; pub fn main() void {
// Named, exported as `main`
}; Just some spontaneous 2¢-thoughts after reading the first reason. |
Is passing |
Forgive me if I don't completely understand how symbol exporting works, but why can't something like this be done/ would be a bad idea? // This will be exported to `file.zig.main` assuming the source file is named `file.zig`
const main = fn() !void {
// This will be exported to `file.zig.main.addOne`
const addOne = fn(x: i32) i32 {
return x + 1;
};
// Extern disables all mangling, it's your job to ensure no symbol collisions. This will be exported to `externAddOne`
const externAddOne = extern fn(x: i32) i32 {
return x + 1;
};
_ = addOne(1);
_ = externAddOne(1);
}; Anonymous blocks and statements would add another layer, something like |
I personally don't understand the justification for closing this. Specifically these points Andrew gave:
In most cases a first class function would have a name, it's the constant it is being assigned to, e.g.: const foo = fn () void {}; This is the same as this to any debugging tools: fn foo() void {} Sure by allowing first class functions you introduce unnamed functions, but it is incorrect to say functions have names. Functions don't even exist as far as the computer is concerned, they are merely blocks of code pointed to by an address which we conceptualize in a specific way. Names are for humans and the compiler, not the computer usually (other than a few specific things like say shared library exports). Of course from the POV of debugging tools as mentioned functions "need" a name, but there is no reason you cannot just auto-generate a name as a placeholder, the more important thing in debugging is having the source correlation and that works fine with an anonymous function regardless. C++ has lambdas for instance and they can be debugged just fine, so this clearly is not an impossible task for LLVM and similar tools. Furthermore, by this logic one could say a struct "needs" a name as well, so why does Zig support anonymous structs if this is such a big deal? Any codegenned C code from Zig or debugging information generated around such things will likely need an auto generated name and be marginally harder to understand debugging wise, but I think we can all agree that the patterns and elegance anonymous structs give Zig are great and definitely worth this small cost of being unnamed occasionally.
I really don't see how. Why would a lambda have issues with shadowing when the de-facto method of "member function nested in anonymous struct" or a nested scope in general not have shadowing issues? It is simply a nested scope, there's no issues there unless I am missing something obvious.
Why would first class functions be any different from normal functions in this regard? Most the time a lambda or similar nested function declaration would be either called directly shortly after declaration or passed into another function (e.g. It is worth noting that maybe in the past this was more of a concern when the type fn sort(comptime T: type, comptime compare: fn(a: T, b: T) bool) void {} Function pointers I'd imagine would be reserved for runtime-dependent function behavior which is a thing one can do already in Zig. I don't really see why first class functions specifically would encourage people to use function pointers any more than they can already do with normal functions, or again with the function-in-a-struct hack used in leiu of this feature.
This may be true but as others have pointed out control flow inversion is already a thing in Zig's own standard library, and really just it is the most sane way to do something like a sorting comparator. Callbacks are a staple of C programming and also qualify as inversions of control so this is not a foreign concept to a C-like language. I agree such control flow can be confusing to follow if abused and don't think it should become a /common/ thing, but these patterns are used occasionally to make code more readable than it would be with another approach. At the end of the day, I do not understand why I have to resort to an ugly hack like this when I want a nested one-off function closer to relevant code using it (and more narrowly scoped to avoid polluting other scopes with these otherwise not useful helper functions): const foo = struct {
fn impl() void {}
}.impl; When I could just be writing this instead: const foo = fn () void {}; If Zig already allows me to nest function declarations like this already then why avoid a more general and elegant solution like first class functions? This proposal is not asking for features that actually introduce runtime cost like captures (unlike other proposals), so this is really just a syntax change for how functions are declared that implicitly adds a bit of flexibility. If this is all really to avoid the notion of anonymous functions because they "don't have a name" then that seems a bit extreme to me. It is slightly less good for debugging potentially sure, but C++ and Rust both can be debugged fine despite this. Realistically I do not expect people to even use lambdas much and to prevent abuse Zig's best practices could always simply suggest avoiding using them except in idiomatic places (e.g. sorting functions) to avoid encouraging too much functional programming craziness. I am just tired of having to have to use a hack to do this given it adds syntactically noisy boilerplate and an extra indentation level to do what should just be possible to do in the language in a more clean way. Not having this feature is harming code readability more than I think any of the listed concerns would hurt it in the long run. There may be some unforseen challenges in implementation but I'm sure such things can be worked through rather than just giving up on the idea entirely. As such I request that this decision is reconsidered as it feels like it was dismissed a bit on a whim especially given the large community desire for something like this. Edit: Worst case if the whole first class function/lambda proposal is still deemed too problematic I'd still want something like the ability to define nested functions in any case to solve the code locality issue without relying on a hack. Just being able to do something like this even though it's not a lambda is still nicer than the current options of either using the struct hack or having the comp function 100 lines of code away and doesn't require anything beyond being able to nest declarations: fn thing() void {
// 100 lines of code here
fn comp(a: i32, b: i32) bool { return a < b; }
sort(i32, data, comp);
// Mode code here
} |
It's likely that you didn't understand the full significance of what that sentence entails. True, other kinds of value have entries in the symbol table. But to quote the Linux manual:
Functions have names in a meaningfully different way from objects ( Also, I think you're responding to a version of this proposal which was never seriously considered. When you say this:
There was never a reality where Zig had two different flavors of function declaration. That would split the language into two dialects, based on the sort of declaration authors prefer, and that sort of thing has always been anathema to the project. I want to note that a lot of your post here distinguishes between two different syntaxes for writing functions, by calling your preferred one "first class". That isn't correct, writing something in a different way doesn't make it any more or less first class. Rather, one of the motives for the proposal was making function declaration more consistent with other proposals for changes in function behavior which could be glossed as "first class", and those proposals were rejected. That fact weakens the strength of the proposal itself, which is why Andrew also mentioned the rejection of those proposals in closing the issue.
I don't agree with this, the use of a sort function pointer in the standard library has measurable negative impact on performance. It's of course true that various structures using function pointers are pretty important in systems programming, it's just that this is not a great example of that. I had hoped that the poorly-named stack capturing macros proposal would get picked up, because compile-time code injection is actually what we want here. It's possible that a more focused iteration of that idea which answers the various difficulties which #6965 identified might be considered. Your 'ugly hack' I view as simply the trivial case of something more broadly useful: struct types as a namespace. The case you make for using that pattern is a weak one from my perspective, it's your decision to want that code closer to where it's used than the language cleanly supports, there's nothing stopping you from using a utility struct to put all your helper functions in without any pollution of the larger namespace, and then localizing one of them is just const comp = comparisons.i32LessThan; This is clearer, it's a cleaner separation of concerns, and it's easier to refactor. It's a good thing when abusing a feature looks like an ugly hack. The actually-useful role of making a single-name namespace and extracting a function from it involves comptime specialization. I'm happy to agree that it isn't the prettiest construct, but that informs the reader that something weird is going on. If I saw that pattern being used to just define a function, I would be confused, if it were in a patch for a codebase I was responsible for, I would ask for it to be rewritten in a more natural way. I don't happen to share Andrew's dislike of functional programming at all, I think it's great. I firmly agree that it's a bad fit for a language like Zig, for the same reasons: chasing function pointers is something the optimizer can rarely fix, and that demotivates making it any easier to work with than it already is.
It won't for long. This seems to boil down to a desire to write Zig in a way you've become accustomed to from other languages, at the cost of adding a second, equivalent syntax to the language, for one of the most basic aspects of it. At no point do you describe something functional which Zig is preventing you from accomplishing, or some way that the status quo encourages bugs which the other style would reduce. No one has a problem writing a function and then calling But this isn't the nested function issue and it isn't the lambda/closure issue, it's just about the syntax used to define function bodies. We don't need two ways to do that, and the better of the two options was chosen. |
So what? In most cases the compiler can generate a name from
It doesn't seem like that necessarily has to be the case.
That isn't always possible. In particular if you need to use comptime parameters. Also, your
This is the biggest reason why I want this feature. If you need to create a function that depends on comptime parameters, the only current way to do it now is ugly and awkward. And I disagree that it is good to have ugly syntax to indicate that "something weird is going on". Especially, since in zig, using comptime parameters isn't really all that weird. The other reason is that I agree with others that a
No, it comes from a frustration with having to create anonymous structs that have a single function, then extract the function from them. And if you need to define a function that depends on comptime parameters, there often isn't any way to avoid that. p.s. The implementation for std.mem.sort (which just forwards to std.sort.block) hast this chunk of code at the beginning: const lessThan = if (builtin.mode == .Debug) struct {
fn lessThan(ctx: @TypeOf(context), lhs: T, rhs: T) bool {
const lt = lessThanFn(ctx, lhs, rhs);
const gt = lessThanFn(ctx, rhs, lhs);
std.debug.assert(!(lt and gt));
return lt;
}
}.lessThan else lessThanFn; which, IMHO would be a lot clearer if it could be written like const lessThan = if (builtin.mode== .Debug) fn (ctx: @TypeOf(context), lhs: T, rhs: T) bool {
const lt = lessThanFn(ctx, lhs, rhs);
const gt = lessThanFn(ctx, rhs, lhs);
std.assert(!(lt and gt));
return lt;
} else lessThanFn; |
I was referring to practice, not theory. That's why I said 'measurable' negative performance impact. We disagree on how ugly the single-reference pattern is, I think. Which is fine. Debates about how to sugar the cereal rarely lead to anything productive. My main point stands: you've offered aesthetic objections, but not practical reasons why your subjective preference will lead to better code. I expect you don't see it that way, which is also fine, but I won't find it worth litigating in further detail, and want to suggest that it isn't a good use of your time either. |
@mnemnion I disagree with that interpretation of the mentioned proposal. Direct quote from the comments:
The types are still unnamed. Further, type literals
You were referring to a practical/measurable impact without any indication you made any sorts of measurements, which might make it difficult to judge for others (including me) how you measured, how big the impact was, etc.. |
To address the stuff @mnemnion has said:
This is irrelevant, again, C++ has anonymous functions and it works fine on Linux. Again, auto-generating function names for the purposes of things that require a name is trivial. This is a non-issue. Functions do not need names, they purely exist for humans. It is good to have a descriptive name for a function yes, but the benefit in readability anonymous functions provides can justify some auto-generated naming here and there in a place few will ever look.
This is already the case, as I explained with The solution here is as it has always been really, Zig's "style guide" or documentation can simply recommend how to write idiomatic Zig that matches what you see in the stdlib and other codebases. That's kinda the only way you can rectify the many ways to do something in a programming language, or at least give people a path of least resistance in how stuff is written in the language, that way people just implicitly prefer it for the sake of readability and succinctness. In this case if there was a more verbose alternative way to declare functions (e.g.
Sure, but making functions actual literals and allowing them to be treated just like any other variable is first class, and that is part of the elegance this proposal has. There's other ways to accomplish what is desired sure (e.g. just allowing function decls to be nested), but having first class functions implicitly solves that problem while also allowing more elegance and flexibility to the language overall.
These are not function pointers as others have explained, they are passed as comptime function types. If Zig cannot figure out to inline something like that then it's a compiler bug plain and simple. This is simply misinformation anyways, note the distinct lack of dynamic jumps or even There may be cases where yes the compiler will keep it as its own function for code size (if it's deemed instruction cache is more valuable here) or if you request
This is not removing the first class nature of structs as far as I can tell. In Zig "anonymous structs" are actually referring to the weird const S = struct {}; Here const F = fn () void {}; The function here is anonymous as it has no name until it is assigned to fn Thing(comptime T: type) type {
return struct {
a: T,
};
}
sort(data, fn (lhs: i32, rhs: i32) bool {
return lhs < rhs;
}); If Zig forced you to name a struct like you have to do in C/C++ this elegant generic programming model would turn into something like this uglier approach: fn Thing(comptime T: type) type {
// C-Like Struct Syntax
struct Impl {
a: T,
};
return Impl;
} Same can be said for the current state of inflexible function decls right now. Even worse, they cannot be nested like this hypothetical struct example so such generic programming becomes impossible (though if comptime parameters will be exposed to nested function scopes like that I guess is a matter of debate, they are at least for structs but maybe this might cause problems for functions).
In some ways yes, but this is because I have years of experience programming with other languages and as such I have recognized the strengths and pitfalls of these languages. Zig in general does a good job of recognizing many of these pitfalls in say C/C++ and rectifying them while also keeping good ideas around (or taking others from say Rust), and that is part of why I use Zig since it feels fairly well thought out. So yes, I want Zig to be like these other languages, just because another language does something does not immediately make it bad and something Zig must do the opposite of. Languages like C++ while they have problems still have many good ideas in them, it's just a matter of picking them out and refining them in a way that makes sense for Zig.
Yes, because I hit this desire just a few days ago in fact when working on my physics engine, I have some math to perform various axis penetration tests and I need to do it for 2 axes. 2 is small enough to not warrant a for loop (or bother with a inline for one orsomething), but 2 of anything is enough to violate the DRY principle if I just copy pasted the code. As such I ended up with this: tryAddExistingVertex(
&intersection,
incident_edge_vertex_0,
perpendicular_reference_axis,
incident_edge_vertex_0_penetration_depth,
incident_edge_vertex_0_perpendicular_projection,
);
tryAddExistingVertex(
&intersection,
incident_edge_vertex_1,
perpendicular_reference_axis,
incident_edge_vertex_1_penetration_depth,
incident_edge_vertex_1_perpendicular_projection,
); This is fine and it removes the code duplication, but it is a pain now as this function declaration is nearly 300 lines away in my implementation. It's not the end of the world really but in C++ this is something I'd use a lambda for just to act as a nested function declaration (C++ also lacking elegance with its function decl syntax forces usage of lambdas for this sort of thing). Tldr, there is very good real world reason for this to exist. I do not know what the best solution is for Zig as there are clearly a lot of things to consider, but imo it is evident Zig is lacking something to handle these cases nicely. Using the current hack of a struct to declare a function is just a bit too detrimental to readability to me to be happy with it, especially for such a reasonably common thing to expect to do in a language. Ironically too such a pattern is only possible to begin with due to another bit of Zig syntax sugar, that being the member function decl syntax (which as I said before could be seen as "redundant" itself). |
Sure. But again, nested function declarations aren't this issue, they're another issue. My main motive in responding in the first place was about expression syntax, and we've run out of things to say about that. I maintain that code injection, not an anonymous function, is what is actually wanted here. You want to be able to define a small piece of behavior and have it appear inline in the function which receives it, that's broadly useful, but there's no reason why it should look like a function in the process. That is a solution other languages use, but not all of them, and I think Zig could do better. The thing about not liking the way a certain syntax looks on the screen, is that you can just change your mind at any time, there's nothing preventing you from overcoming the frustration you feel when you decide to make a one-declaration namespace. There's a reason the syntax exists. You'll discover that this doesn't compile: fn innerFunction(T: type, val: anytype) bool {
return struct {
fn inner() bool {
return (val < std.math.maxInt(T));
}
}.inner();
}
test innerFunction {
var runtime: usize = 129;
runtime += 12;
try expect(innerFunction(u8, runtime));
} This is something which would work in many languages, but not in Zig. Only If you replace Allowing bare nested function declarations would mean that functions so defined follow two rules: if they aren't nested, they can access anything in the outer context, including mutable state, but if they are nested, only comptime-known values. By reusing the generic struct creation mechanism, we have only one rule. That's better. |
I see no reason why that's relevant, the rules for how such things work can be figured out as needed. This is purely a request for more flexible syntax really in how functions are created to make them more in line with the "everything is an expression" model Zig already uses for everything else (be it types themselves, if statements, for loops, etc). Zig after all does let you do this sort of nested function decl as you showed, it just requires an ugly hack to do what people want. This is how your example would be if using said proposal which to me just is much nicer to read. All the same rules can still apply so I don't know what's so bad about it: fn innerFunction(T: type, val: anytype) bool {
return fn() bool {
return (val < std.math.maxInt(T));
}();
}
test innerFunction {
var runtime: usize = 129;
runtime += 12;
try expect(innerFunction(u8, runtime));
} |
Nested function declarations could be used in some cases where this would be useful, but not all. In particular consider my example from std.sort.block above. Nested function definitions don't help there, because you want to use different definitions depending on comptime state. Also, the issue you linked to is specifically about closures, which is not what any of us are asking for here. And for that matter, using const = syntax would simplify conditional definitions at top levels as well. |
I find the argument that "functions are special symbols" very hard to understand. Sure, they enjoy a special status at lower levels, but as others have pointed out there is no reason their uniqueness can't be handled entirely by the compiler, especially with Zig's comptime capabilities.
While I do agree that ultimately referencing the blandly-named method of an anonymous struct is an acceptable solution, it's still harder to read than both anonymous function expressions and nested functions.
To me "a small piece of behavior" comes very close to the exact definition of function. Why should it not look like one? Making up an entirely new concept to pass down behavior when functions are right there seems wasteful to me.
I think this is the real crux of the problem. Having two equivalent syntaxes for function definition would be just confusing; at best, one of the two would be dominant and the other remain obscure. I realize that making function definitions expressions goes against Zig's idiom, but I would also argue that anonymous functions are too simple and useful not to include them and that the anonymous struct's method hack is far too ugly (i.e. unreadable) to be acceptable. A middle ground would be nice, perhaps restricting anonymous functions to actually be anonymous (i.e. forbidding to assign an anonymous function to a variable). |
I appreciate @Maldus512's middle ground conclusion. |
Minor nitpick: this would be an arbitrary style restriction without any semantic benefit IMHO, pushing programmers into full-on doing lambda calculus to use parameter names instead of stack variables. Names are essential to reduce the mental overhead, one of which (inversion of control flow) was cited for rejecting this RFC. I understand the motion for the proposal after the rejection as primarily against the
I'm not attempting to spark flames here, just want to point out that the issue can be a lot simpler than we make it to be. Since |
Overview
This is a proposal based on #1048 (thank you to everyone discussing in that thread). I opened this because I believe that conversation contains important ideas but addresses too many features at once.
Goals
Non-goals
Motivation
Almost all statements which assign a type or value to an identifier use the same syntax. Taken from today's grammar (omitting a few decorations like
align
for brevity):The only construct which breaks this format is a function definition. It could be argued that a normal function definition consists of:
Ideally, number 3 could be decoupled from the other two.
Proposal
Make the following true:
Roughly speaking, assigning a function to a
const
would equate to existing behavior, while assigning to avar
would equate to assigning a function pointer.Benefits
Examples
The
main
function follows the same rule.The
extern
qualifier still goes beforefn
because it qualifies the function definition, butpub
still goes before the identifier because it qualifies the visibility of the top level declaration.Functions as the resulting expressions of branching constructs. As with other instances of peer type resolution, each result expression would need to implicitly castable to the same type.
Defining methods of a struct. Now there is more visual consistency in a struct definition: comma-separated lines show the struct members, while semicolon-terminated statements define the types, values, and methods "namespaced" to the struct.
Advanced mode, and possibly out of scope.
Calling an anonymous function directly.
Passing an anonymous function as an argument.
What it would look like to define a function in a function.
Questions
Extern?
The use of
extern
above doesn't seem quite right, because theFnProto
evaluates to a type:Maybe it's ok in the context of
extern
declaration, though. Or maybe it should look like something else instead:Where does the anonymous function's code get put?
I think this is more or less the same issue being discussed in #229.
Counterarguments
The text was updated successfully, but these errors were encountered: