Proposal – let’s backport Go := to C

The Go language was designed with the intention of replacing C and C++ over much of their ranges. While the large additions to Go – notably automatic memory allocation with garbage collection – attract attention, there is one small addition that does an impressive job of helping code be more concise while not being tied to any of the large ones.

I refer to the := variant of assignment, which doesn’t seem to have a name of its own in the Go documentation but I will pronounce “definement”. It must have an unbound name on its left (receiving) side and an expression on the right (sending) side. The semantics are to declare the name as a new variable with the type of the right-hand expression, then assign it the value.

Here’s the simplest possible example. This

void foo(int i)
{
    int x;
    x = bar(i);

    /* More code that operates on i and x */
}

becomes this:

void foo(int i)
{
    x := bar(i)

    /* More code that operates on i and x */
}

A way to think about definement is that it generates a variable declaration with an initialization. In modern C these can occur anywhere a conventional assignment can.

Definement is a simple idea, but a remarkably productive one. It declutters code – scalar and struct local-variable declarations just vanish. This has two benefits; (1) it improves readability, and thus maintainability; and (2) it eliminates a class of silly errors due to multiple declarations falling out of sync – for example, when changing the return type of a function (such as bar() in the above example), you no longer gave to go back and tweak the declaration of every variable that receives a result from its callsites.

Definement syntax also has the property that, if we were to implement it in C, it would break cleanly and obviously on any compiler that doesn’t support it. The sequence “:=” is not a legal token in current C. In gcc you get a nice clean error message from trying to compile the definement:

foo.c: In function ‘foo’:
foo.c:3:5: error: expected expression before ‘=’ token
  x := i
     ^

This makes it a low-risk extension to implement – there’s no possibility of
it breaking any existing code.

It is worth noting that this will actually be slightly simpler to implement in C than it is in Go, because there are no untyped constants in C.

I think there’s a relatively easy way to get this into C.

First, write patches to implement it in both gcc and clang. This shouldn’t be difficult, as it can be implemented as a simple parser change and a minor transformation of the type-annotated AST – there are no implications for code generation at all. I’d be surprised if it took a person familiar with those front ends more than three hours to do.

Second, submit those patches simultanously, with the notes attached to each referencing the other one.

Third, wait for minor compilers to catch up. Which they will pretty quickly, judging by the history of other pure-syntax enhancements such as dot syntax for structure initialization.

Fourth, take it to the standards committees.

OK, am I missing anything here? Can any of my readers spot a difficulty I haven’t noticed?

Will anyone who already knows these front ends volunteer to step up and do it? I certainly could, but it would be more efficient for someone who’s already climbed the learning curve on those internals to do so. If it helps, I will cheerfully write tests and documentation.

EDIT: No, we can’t backport C++ “auto” instead – it has a different and obscure meaning in C as a legacy from B (just declares a storage class, doesn’t do type propagation). Mind you I’ve never seen it actually used, but there’s still a nonzero risk of collision with old code.

UPDATE, DECEMBER 2ND: I have been in touch with Ken Thompson. He approves, raising two minor technical caveats about stack growth and name shadowing.

121 comments

  1. Doesn’t C (or at least C++) already have this as “auto”?

    auto x = 1; // vs x := 1;

    Sure, the GO syntax would save a few characters, but not enough to be worth having two ways to do the same thing. And if “auto” is C++ only (I haven’t done pure C in ages), wouldn’t it be better to bring C closer to C++ instead of moving it further away?

    1. >Doesn’t C (or at least C++) already have this as “auto”?

      C does not. There is a legacy ‘auto’ keyword which some compilers may not even implement – I’ve never seen it used anywhere, because all it does is declare that a variable has auto class and that can be inferred from the absence of static or extern. It doesn’t do the type-propagation thing.

      C++ does have an auto keyword that has these semantics. I didn’t know this – it’s probably a recent feature in the ever-expanding blob. Now that I’ve seen it, I like the Go syntax better. It’s less obtrusive.

      1. Yes, it’s a relatively recent feature. Rather than invent a new reserved keyword, they reused an existing one that no one used anyway.

        The Go syntax is indeed less obtrusive, but it looks weird to me (maybe my Pascal history bubbling up?). It seems like something as important as “Hey, I’m declaring a brand new variable here” is worth more than a single character – but that’s obviously just an opinion and opinions will differ.

        More importantly, if C were to implement this, C++ would almost be forced to do so as well – because otherwise C++ would not be upward compatible with C any more. So now C++ would have two ways to do the same thing!

        Now I know there’s a lot of C people that don’t like C++ and would be more than happy to harm it, but I don’t think that “poisoning” any part of the C/C++ ecosystem is a good idea.

        1. How is it “poisoning?” It’s subjectively much better syntax than the new (C++ 2011) ‘auto’. If enough people agree, it will be supported soon enough; if not, it won’t. The fact that ‘auto’ is so recent is probably the only reason there is currently only one way to do it; C++ was probably perl’s inspiration for TMTOWTDI.

          1. It’s “poisoning” (or whatever you want to call it) because you’re effectively forcing C++ to create a second way to do the same thing… and forever after, people will have one more reason to mock C++ for “being too complex” and “having too many ‘useless’ features”.

            1. Ah. So the rest of us could pick our own terminology, as long is it has the appropriate negative connotations?

          2. Better? I’m not sure how you’d declare an argument or return type with := but you can with auto.

            OTOH, lambdas with auto arguments are another way to do things traditionally done by templates or function argument overloading, so it seems C++1y allows plenty of duplication already.

            Just look at what GCC accepts now:

            // A new generic function syntax in C++1y
            auto
            foo(auto y) -> decltype(y)
            {
            // Another generic function syntax, just because.
            auto lam = [&](auto x) -> decltype(x) { return x + 3.1; };
            return lam(y);
            }

            foo(5) returns 8, foo(5.1) returns 8.2, so the type inference is working.

            1. Better as a shorthand for auto for the fairly normal case of declaring a variable and inferring its type.

              1. “the” fairly normal case…except it’s only one of several normal cases, and ‘auto’ does better in all the other ones.

        2. If I could recommend the C++ designers do one thing, it would be to drop (explicit) backward compatibility with C. C and C++ are very different languages with different audiences, and trying to keep C code compiling under a C++ compiler is a waste of effort.

          Which I’m pretty sure they’ve done anyway; the biggest difference is that C and C++ have completely different atomic primitives (which is a good thing!).

          Doing some more research, Wikipedia has a nice list: https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B#Constructs_valid_in_C_but_not_in_C.2B.2B

          Some of these are minor, but some are not; for example,

          int *p = malloc(n * sizeof(int));

          is good style in C but illegal in C++; C++ never took up C’s variable-length and indeterminate-length arrays (which, again, are good features in C but not in C++); and C++ compilers don’t support C-style complex numbers.

          Here’s a g++ bug closed as WONTFIX asking for C++ compiler support for C’s stdatomic.h: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60932

          Given the size and complexity of modern C++, I don’t see any reason why C++ should be the ‘modern’ language that C converges to. Go is already much closer to C than C++ is, and there’s no reason not to keep going in that direction.

      2. “auto” in C++ does more. Fundamentally, it is a placeholder for an unknown type. So “auto* x=something;” declares a pointer, “auto& x=something;” declares a reference, etc. Lambdas can declare parameters with “auto” type, in which case they accept *any* type. This is a powerful general facility in C++, not a special case.

        Incidentally, I think “less obtrusive” is a bad thing. That little “:” changes an expression that would necessarily be an assignment to a non-local variable into a declaration.

        1. Shadowing globals with locals of the same name is something that any good compiler should spew warnings about that any good programmer should heed.

          1. …unless it’s a Rust compiler, where that sort of thing is a community convention baked right into the language syntax. :-P

      3. The only time I ever saw auto used in C was to explicitly distinguish which variables in a group were being declared as stored in memory or a CPU register. This was ~20 years ago, working with a compiler which was not trusted with those sorts of optimizations. I’d also think that repurposing auto to mean type inference shouldn’t break old code like that since auto int foo should produce a warning like ‘type inference aborted by explicit declaration’, not an error.

        1. “auto foo = bar” is still accepted by some C compilers (despite its deprecation three decades ago), and is equivalent to “int foo = bar” because C before C89 assumed type int by default.

          I think it’s better to pick exactly one of the two meanings of ‘auto’ (say, with –std=c17 or similar) for a given compilation unit than to subtly change the meaning of an expression whose syntax is valid in both cases.

          C++11 can get away with it with the rationale “it’s not C, so there.”

        2. Hazy memory says that the status of auto as a reserved word in C was a holdover from B.

          In B, auto variables were allocated when a function was called and were only available within that function (local variables, roughly), while extrn variables were allocated at load time and were in scope everywhere in the program (global variables, roughly).

          I don’t recall ever seeing auto actually being used in a straight C program.

      4. C++ is big, and messy because of its policy of not breaking past code. And in fact it does break past code. C++11 broke a lot of my C++98 code.

        It also leaves all the C broken glass lying around to cut yourself with, because of its policy of allowing all that past dangerous code to compile.

        But it is nonetheless really great – though if you found the learning curve for Rust tough, you would find the learning curve to take advantage of C++ supercool and superpowered stuff even toughter. C++ metacompilation used to be unbelievably contorted, though C++11 metacompilation now allows you to use most C++ at compile time to generate code at compile time. But just as C++ keeps all the old C broken glass, C++11 keeps all the old C++ twisted and unobvious metacompilation, which even when you get it right is apt to generate megacharacter compiler error messages with no obvious connection to the actual error.

        But it is still the greatest language ever, if you don’t cut yourself on the old C broken glass, and don’t get totally random and illogical error messages from metacompiler errors.

        And it is simply obvious that if we import this feature to C, we should import it in its C++ form, rather than its Go form.

        And we should also import the C++11 automatic memory allocation and garbage collection … hell, if we are going to improve C, let us just all learn C++11

      5. GCC and Clang both have an __auto_type specifier which allows for type inferencing a variable definition. Admittedly, it’s not standard, but as it exists in two major C compilers it has a higher chance of getting added than := does.

        One common usage is to define these macros:
        #define let __auto_type const
        #define var __auto_type

        Which can then be used as such.
        let x = floor(3.5); // x is const, so you aren’t allowed to mutate it.
        var y = x; // y is not const.
        y++;

  2. I’ll play the contrarian …

    I don’t want you to improve C, I want it to move into a quiet and well deserved retirement.

    Focus your energy on finding its replacement(s).

    1. >I don’t want you to improve C, I want it to move into a quiet and well deserved retirement.

      Hey, look at it it this way – deployment of this syntax would lower the transition cost to Go.

      1. Go is a proprietary language that has an onerous “code of conduct”. I won’t be migrating any of my code to it ever.

        C++ added the auto syntax in the first standard ratified after the momentous 1997 effort. Most of the work in the C++ standard since has been to simplify and streamline the syntax. I don’t have any objections to C++ generally.

        1. You don’t need to agree to the Go Code of Conduct to code in Go. If you want to continue being a fascist, or a serial sexual harasser while hacking in Go, by all means do so at your own peril; Go won’t stop working for you.

          If you want to hack on the Go runtime, or get your changes incorporated into the Go standard library, the Go CoC applies and you will be obligated to check your behavior while on official Go forums and other communication media. Or you can just fork Go; it is entirely open source.

          But really, the CoC for Go is quite reasonable, and helps keep toxic people out. Toxic people are highly corrosive to any open source community, irrespective of their talent level.

          If you refuse to use open source software written by leftists on ideological grounds, that covers a lot of software. Forget about Linux, Emacs, and GCC, just to name three high-profile projects.

          1. If you want to continue being a fascist, or a serial sexual harasser while hacking in Go, by all means do so at your own peril; Go won’t stop working for you.

            Except a lot of people, especially the kind of people who like to push CoC’s, use “fascist” to mean “anyone to the right of Lenin”.

            Heck, look at what happened with the Node.js kerfuffle.

          2. Since it came up, I just went and read the GO CoC.

            And, like most such things, it seems pretty reasonable.

            Except it will be hijacked by fascists (and I don’t mean Nazi types). That is what happens these days to most “moderated” areas. The definition of a “safe” place tends to move more and more towards a place where everyone marches in lock-step.

            I hope I’m wrong. I fear, based on what is happening in history, and on O’Sullivan’s First Law, that I am right.

          3. I haven’t read the Go CoC, but I recall some Rust people arguing that esr shouldn’t even be allowed to *use* Rust under their CoC. Because crimethink.

            From what I’ve seen, the “toxic people” are generally the power-hungry twits who push these CoCs in the first place.

            1. No one has ever said that, and there is nothing in the Rust CoC that would support that. The Rust CoC is merely guidelines for keeping discussions friendly within official Rust venues (ie: the official Rust subreddit, user forums, GitHub repository, Discord, IRC, etc.). You are just writing BS falsehoods.

          4. A CoC is an SJW invention that exists to expel badthink.

            It will always degrade a project until it is unusable, because the goal isn’t success, it is goodthink.

            Therefore, Go will fail as a technology. Therefore I won’t waste my time learning it or porting to it.

            1. Oh, addtionally, people are not toxic, unless they’re actually excreting nuclear material, plague, etc. You know, toxins. The conflation of the modern SJW of words with physical harm is another signature of poor thinking.

            2. It will always degrade a project until it is unusable, because the goal isn’t success, it is goodthink.

              Good thing it is open source then isn’t it? This is not the original problem that it was intended to solve, but well within the bounds of its solution space.

              Therefore, Go will fail as a technology.

              Nope. It is already succeeding because it is well adapted to its (large) niche. See also: open source.

              Therefore I won’t waste my time learning it or porting to it.

              I thought hackers cared about quality, not politics.

              1. Having looked at Go, I don’t really see the benefits that ESR does. I’m happy with C++, with no need to port.

                There are many growing technologies that I can choose from, and internal politics breaking apart the project is a reason to shy away from depending on the project (see the recent Node.js / Ayo nonsense).

          5. If you refuse to use open source software written by leftists on ideological grounds, that covers a lot of software. Forget about Linux, Emacs, and GCC, just to name three high-profile projects.

            I’m not sure I would count either Linus Torvalds nor Richard Stallman as leftists. Well, Stallman is questionable, but Torvalds is very much of a “live and let live” kind of guy and that’s about as anti-left as you can get.

            1. RMS is somewhere near the intersection of knee-jerk, hard-left and flaming-red. He doesn’t seem to let it out much during technical or Free Software speeches or essays, at least he didn’t before – been a while since I watched any videos of his speeches. But his personal home page looks like it was written by a committee of the craziest members of each faction of the American left. I haven’t done a side-by-side, but I can’t think of a single left-wing or far-left cause that he doesn’t vehemently support.

              Very interesting that I take his side about 99% on the topics of Free Software (sorry ESR) and privacy, but disagree probably about 113% on everything else (politics-wise, not technical), while I think that ESR is about 99% wrong on the ancient Open Source/Free Software debate and I agree with his politics probably 75% or more.

          6. > But really, the CoC for Go is quite reasonable, and helps keep toxic people out. Toxic people are highly corrosive to any open source community, irrespective of their talent level.

            CoCs like Go’s result in takeover by social justice warriors, who then proceed to ignore bugs and bitrot, devoting all their energies into hounding each other over minute failings in political correctness.

    2. We already have a replacement — Rust. Eric may not think it meets his needs, but the weight of the hacker community is behind Rust for C-like things. It’s kinda like the situation with Wayland — which I was right about too; all distros that matter have gone Wayland by default, and ChromeOS has a Wayland compositor baked into its windowing system.

      1. > Eric may not think it meets his needs, but the weight of the hacker community is behind Rust for C-like things.

        Well, the Rust zealots certainly make that claim, in much the same way that Marxists are always claiming that the proletariat is crying out for Marxism.

        Who, exactly, is the “hacker community” and when was the vote taken?

        1. >Who, exactly, is the “hacker community” and when was the vote taken?

          One vote we can track is the TIOBE top languages list. “The TIOBE Programming Community index is an indicator of the popularity of programming languages. The index is updated once a month. The ratings are based on the number of skilled engineers world-wide, courses and third party vendors. Popular search engines such as Google, Bing, Yahoo!, Wikipedia, Amazon, YouTube and Baidu are used to calculate the ratings. It is important to note that the TIOBE index is not about the best programming language or the language in which most lines of code have been written.”

          Go broke their top 20 in 2016 and is currently hanging out at 14, just above Perl. Its position seems to be pretty stable – it was 13 a year ago. Rust is at 31, just below lua. There numbers suggest that Go has about twice Rust’s mindshare.

          Their front page also notes that with the single exception of Python, going strong at #4, scripting languages seem to be falling out of favor. Interestingly, they interpret this as a comeback of statically-typed languages motivated by the need to hold down defect rates in ever larger projects – pretty much the same analysis as a couple of my recent posts. Good news for Go and Rust fans alike.

          But nothing in these numbers say there’s any pro-Rust wave happening. On the other hand, given the languages it’s now hanging out near, it looks like Go has crossed the chasm and is entering its early-majority phase.

          1. > Their front page also notes that with the single exception of Python, going strong at #4, scripting languages seem to be falling out of favor.

            The numbers I’m seeing on that page also show an increase for JS.

            > But nothing in these numbers say there’s any pro-Rust wave happening.

            Yes, hanging out down there in FORTRAN territory doesn’t exactly make one think it’s taking over the world just yet. :-)

          2. TIOBE is not reliable. It favors languages that are popular in high schools and community college’s, and languages that are so complex and poorly documented that they incur a swarm of Stack Overflow questions and tutorial websites.

            Rust has official resources and incredible documentation, and the Rust compiler takes care of most of the answers and points out all the issues, so our community does not reach out for and ask questions that often as, say, a C programmer would. Their algorithms also majorly change over time, so results aren’t predictable.

            See this chart instead: https://insights.stackoverflow.com/survey/2017#most-loved-dreaded-and-wanted

            1. >TIOBE is not reliable. It favors languages that are popular in high schools and community college’s

              I don’t believe this. The distribution is all wrong for that model. If your claim were true, their hot trend certainly would not be the fall of scripting languages. Instead, as they note, the languages on the rise are those suitable for development at scale of a kind you ain’t going to see at any community college.

              >Rust has official resources and incredible documentation

              Yeah, no. The non-fanatics in Rust-land have acknowledged on this blog what I found by experience – that, while the core theory of the language is well documented, the explanatory gap between that and working code integrating the crate system is large and poorly covered – also crate discovery remains difficult. This is a normal problem for a young language (or even a lot of non-young ones, see for example Java libraries) to have; Rust is not special in either a bad or a good way here.

          3. If I’m reading that graph right, it looks like assembly has has had a noticeable comeback over the last several years. What the hell?

          1. But if you examine the other reports on your link nobody is actually using it and nobody seems to want to be using it.

          2. >Stack Overflow took the votes this year, and last year. In both years, Rust won the most loved language of the year.

            “Most loved” ain’t the same as “most used”, or even “gaining mindshare”. I give you the tragedy of Lisp as an example. I love Lisp a lot, but realistically I am never likely to use it for production programming again (unless you want to count Emacs Lisp modes).

            The world is full of niche languages with passionate followers that have already gotten as big as they ever will because something else ate their future. Python and the scripting languages in its class did that to Lisp. (Functional languages in general seem highly prone to top out like this; I don’t know why, and rather wish it were otherwise.)

            It’s too soon to tell if Rust will suffer this fate. I think it still has a plausible growth story in kernels and embedded firmware, though I’m less optimistic there than I was before the Rustix guy bailed out to Nim. Next three years will be critical.

          3. … took “the” votes…

            yeah, no.

            Self-selected language zealots are self-selected language zealots.

        1. Speaking of systemd jokes, I once saw somebody use the term “rhinodaemonitis” to refer to the infamous possible result of constructs that have undefined behavior in C. I immediately thought “You mean ‘rhinodemonitis’. ‘Rhinodaemonitis’ is the result of using systemd for any vaguely serverish workload”.

  3. The specific syntax (auto vs :=) really shouldn’t matter very much as long as the claimed productivity improvements can be had. It seems very unlikely that := will happen, and it seems possible (though still less likely) that auto might be accepted.

    The reason I think it unlikely that any of this will happen is this: you are asking for C++. Nobody says that one must do C++ The Right Way. Instead, you can simply use C with ‘auto’ today by adopting a C++ compiler and be more productive right away

    1. If syntax didn’t matter, we’d all be using lisp. I think := is much better than auto for the concept of “hey, computer, you figure it out.”

      1. Lisp’s syntax is a red herring. There are much less frivolous reasons to not use lisp they are just much harder to communicate. So people just say “the syntax is bad lol” so they don’t have to waste their time arguing with lisp fanboys like me.

        ESR’s point about how you need good bindings to the outside (i.e. unix) world is part of it.

        Another part is that lisp is so malleable that it doesn’t provide enough creative constraints for people to gain productive traction. Think of programming languages from the perspective of a game designer. Games are fun because they are a constrained environment. The same is true for programming languages. Successful PLs provide the right degrees of freedom to get things done without too much work but also the right constraints to have something to grab on and move around the problem space.

        1. I wouldn’t say lisp’s syntax is bad; rather that it doesn’t really have one to speak of. Which, of course, is actually the core of your constraints argument. I definitely agree that constraints fuel creativity, and that lisp is the sort of blank page that could inspire writer’s block.

          Things like the bindings issue are secondary, and are driven by the same problem of lack of constraints. It is a bit of a chicken and egg, to be sure, but even if there were completely standardized bindings on the one true lisp, it probably still wouldn’t gain traction.

          1. > lisp is the sort of blank page that could inspire writer’s block.

            I never had that experience.

            I think what did in Lisp was failure to learn the harsh lesson of Perl before Perl demonstrated it. You *must* have a full binding to POSIX, or you lose.

              1. >But do you get writer’s block in general?

                Now you mention it, no. So possibly my experience is not representative.

  4. IIRC, you can in the middle of the function do a typed declaration in GCC and some other C compilers.
    {
    ….//code
    int x=1;
    ….//more code
    }
    I prefer the explicit typing of this method,
    One dark area of C is what type is an expression? Things are implicitly promoted, cast, converted, and if you are going to use casts everywhere anyway, you are back to what C does now.

    1. This.
      I do this all the time in my GCC world (no idea what other compilers do).
      Does Go allow for this style? I hope so.
      Because given Eric’s example, if bar() changes its return type, there still will be changes needed to how ‘x’ gets used anyway. Big whoop if you have to change the declared type.

      1. More complications if a bool is not an int, or uint or other things.
        I have enough trouble keeping track of proper types when they are explicit.
        I have trouble solving some of the bug puzzles where you have a (signed) char variable and a unsigned long (usually buried, e.g. #define LIMIT 999UL), so
        x := c + LIMIT; // what type is X without looking anything up?
        Howabout x:= 12UL + 12L;
        Worse, MAXINT*MAXINT requires a long to store.
        NO!, just NO!
        And you can’t do the GCC extension type casting except in a clunky way
        x := (int) (bar()); // instead of “int x = bar;”
        I’m not sure about Go’s rules for type promotion and changing.

      2. Worse from something below, what if the return type is const, volatile, static, register or some other modified type?
        const int bar();
        x:=bar();
        x++; // error, x is const

  5. I would much rather port Pascal’s := to C; that is, have := mean assignment and = mean equality. Then you wouldn’t have to do Yoda conditionals in order to avoid shooting yourself in the foot.

    1. That ship has sailed a long time ago. In the C-family languages sadly = is assignment and backporting Pascal’s notation (even though I prefer it), would be a breaking change that would cause more trouble at this point than it’s worth. Go’s declare as opposed to assign would cause less friction.

    2. OK, so you’re proposing that the language change in such a way that it will break literally every single C program ever written? Yeah, that’s not going to fly.

      1. >OK, so you’re proposing that the language change in such a way that it will break literally every single C program ever written? Yeah, that’s not going to fly.

        Where did you get that crazy idea?

        1. That was in response to:

          >I would much rather port Pascal’s := to C; that is, have := mean
          >assignment and = mean equality. Then you wouldn’t have to do
          >Yoda conditionals in order to avoid shooting yourself in the foot.

          If = only meant equality in C, then (nearly) every single program ever written would suddenly fail to compile:

          int x = 0; // “invalid comparison” or “extraneous type” or something

    3. Does that mean “Foo::operator:=(const Foo &)” is an alias for “Foo::operator=(const Foo &)”, or a separate operator?

      Same question for “Foo::operator:=(const Foo &&)”.

  6. Assuming backporting := to C is desirable, put a little weight behind the suggestion. See if you can get RMS and Linus to agree it would be useful, and reference their comments in your notes.

    RMS pushing eases the way into GCC. Linus pushing likely does the same for other compilers.

    >Dennis

    1. Howso? Last I’d checked, GCC is still the required compiler for building Linux, as Linux uses some GCC-specific extensions to the C standard. If Linus were to push the proposed idea, I could see that helping it land in GCC, but not Clang or any other C compiler. Do the authors of other compilers care what Linus thinks if he doesn’t use their compiler?

  7. There is an active C standard process, currently ISO C11, I guess they are looking at C18 or C19. This type of syntactic sugar doesn’t seem to be on the radar. It seems to be more decimal floating point and more secure library functions.

    ‘auto’ in C++ has a wider range of usage than ‘definement’, and it can be modified with const or ‘&’. Also it is visually replacing a type definition, so it makes sense to have a word in there.

    A trivially contrived, in the dread C++, example,

    https://godbolt.org/g/X2Uj2L

    where the only explicit type is for ‘main’…

    Paul

  8. on my bad days, of which I have many, I have an intuitive dislike of all the double character
    mechanisms in all languages.

    Would much rather have:

    ← than := or = for assignment
    ≅ approximately equal for floating point
    ≠ than !=

    for example. We’ve had utf-8 for ages now, and if the math looked more like the code and vice versa I tend to think that will cause less mistakes, and more crossover between programmers and math folk also.

    I know that normal programmers might prefer delta to ?, but knowing the math, english, and the code is a barrier to entry.

    1. The first two aren’t easily typed on a keyboard, so that’s not happening. Meanwhile, ? is already in use by C and many other languages as a conditional / null check / early return operator, depending on the language.

    2. IMO != for “not equal” is exactly right. It’s a negated equal.

      The others don’t show up on most keyboards, and I don’t think getting a set of APL keycaps is the right answer.

      Though it would be amusing.

  9. heh. and wordpress broke those symbols on the way in, which is in it’s own well a compelling argument to not adopt utf-8 in programming languages.

    google for left arrow symbol, approximately equal symbol, delta symbol.

    1. >heh. and wordpress broke those symbols on the way in, which is in it’s own well a compelling argument to not adopt utf-8 in programming languages.

      It is, alas.

      I fixed them up in your comment. You can in fact enter those in WordPress, you jusst have to do it as HTML entities.

      I’m presuming you actually wanted not-equal rather than Greek delta.

  10. Nothing to do with :=, just practising WordPressiness. Sorry for the waste of space.

    #include <iostream>
    #include <tuple>
    using namespace std;

    template <typename T, typename U> auto Package(T t, U u)
    {
        return make_tuple(t, u);
    }

    int main()
    {
        auto[a, b] = Package("Hello world", 1.414);
        cout << a << ": " << b << endl;
    }

    That was a bit of a performance.

  11. It may or may not be useful as a language construct, but if your previous post was correct–that outside of special niches C is going away in favor of a handful of other languages , that by the time you jumped through all the hoops–writing up the RFC, fighting the flamewars, getting it through the standards body or bodies, getting it into the compilers (or the other way around), and then getting people to use it the only people still using C wouldn’t use it because they’re the kind of people who consider anything post K&R C the way I consider Ham and Pineapple pizza.

    A shorter way to say that is that by the time you get it to be part of the language, there won’t be anyone left using it.

    Statistically speaking.

    Do you have any idea why all my posts go into moderation? Does WordPress consider me immoderate?

    1. >A shorter way to say that is that by the time you get it to be part of the language, there won’t be anyone left using it.

      I don’t think C will pass out of use in kernels and embedded that quickly.

      >Do you have any idea why all my posts go into moderation? Does WordPress consider me immoderate?

      No idea.

      1. I don’t think C will pass out of use in kernels and embedded that quickly.

        Linus may be slow to transition away from C, but Linus is about to find himself increasingly irrelevant. Google is already prepping for a transition away from Linux with its Fuchsia project.

        1. Fuchsia is targeted at IoT devices and will eventually “scale up” to cellphones and desktops.

          I suspect that Linus will be able find work until he’s old enough to retire. .

  12. FWIW I think ‘auto’ could be extended in C and that would keep it idiomatic and not add any keywords or other confounding factors. An original usage of auto would just work, since it would have an associated type and auto would be ignored unless it was an existing syntax error (static auto int x; for example), and a default int use of auto that didn’t actually define an int would be a bug regardless that would be detected now and fixed with a cast or just fixed.

    Compiler option to enable then makes everything fine.

    But not a good idea, because not C.

  13. Changed my mind.


    auto i = 2;
    auto d = 1.414;
    printf("%d %d\n", i, d);

    Is good, if bad, C and would change if ‘auto’ were auto.

  14. Would be great if C17 would import the whole auto from C++ since it is pretty nice.

    And that is said by somebody that really does not enjoy C++ at all.

  15. How long would the compiler let the ambiguity remain unresolved? Suppose you allow a variable to be assigned in a statement like x := i ? foo() : bar (). Now should foo() and bar() return different types, then typeof(x) would depend on the value of i, which in general isn’t known at compile time. In fact, if the compiler allows this kind of ambiguity to persist, the programmer could even manipulate the variable further in expressions of the form _Generic(x, ...). I’m not sure whether this sort of poor man’s polymorphism should be tolerated – it seems remarkably powerful, but would make reasoning about the state of the stack rather more difficult.

    1. I don’t think there’s any ambiguity with the ‘auto’. If the right hand side has a valid type then the new variable gets that type. If you want a specific conversion then you have to indicate.

      The ambiguity originates further back, in the C/C++ decision to allow implicit conversion from (say) double to int. So in your example if the ‘? :’ construct is valid then that’s all that matters. If foo and bar are double and int, no problem, x is double. If bar were char* then it wouldn’t matter how you declared x it wouldn’t compile without some bodgery inside the ?:. If you have double and int and you want int then you have to say so, and because you can assign double to int, it just works (possibly not as expected, but that’s another story)

      1. >The ambiguity originates further back, in the C/C++ decision to allow implicit conversion from (say) double to int.

        I think this is a red herring. To see why, consider that every variable to the left of an assignment with ta ? : on its right has to have an implied type. Granted in C the promotion rules make what type it is a bit obscure.

        1. Not quite following.

          C++ gives a useful test bench to play with this stuiff,

          int foo(); double bar();
          auto what = i ? foo() : bar();
          int ever = i ? foo() : bar();

          ‘what’ is a double regardless of i, because that is what the right hand side resolves to. But the second line compiles fine. Which I think renders d5xtgr’s point moot. If foo and bar have non-convertible types then the rhs simply doesn’t compile.

          1. >If foo and bar have non-convertible types then the rhs simply doesn’t compile

            I think this a different way of driving at the same point I was making. Either the compiler can assign a type to the value of the ternary expression based on promotion rules or it can’t. If it can’t game over. If it can, and the type is T, then “what = i ? foo() : bar();” is equivalent to “T what = i ? foo() : bar();”

          2. Implicit conversion isn’t at all what I was getting at. Here’s a more complete example that may be a useful illustration, where foo_type and bar_type are not convertible.


            #define foobar_to_int(X)\
            _Generic(X,\
            foo: foo_to_int,\
            bar: bar_to_int)(X)

            foo_type foo() {
            //function guts
            }

            bar_type bar() {
            //function guts
            }

            int foo_to_int(foo myfoo) {
            //function guts
            }

            int bar_to_int(bar mybar) {
            //function guts
            }

            int generate_foobar_as_int(int i) {
            // myfoobar may be foo_type or bar_type
            myfoobar := i ? foo() : bar();
            return foobar_to_int(myfoobar);
            }

            A sufficiently clever compiler could treat the type of the rhs of the definement to myfoobar as merely unresolved rather than invalid. The question is whether ambiguity of type created by := must be resolved by the end of the current line, or whether it may persist until the end of the current block. If the latter, it would effectively generate two code paths, equivalent to something like the following.


            int generate_foobar_as_int(int i) {
            if(i) {
            foo_type myfoobar = foo();
            return foo_to_int(myfoobar);
            } else {
            bar_type myfoobar = bar();
            return bar_to_int(myfoobar);
            }
            }

            This application is what I referred to above as a poor man’s polymorphism.

    2. A C compiler will try to find some type T that both foo() and bar() either already have or can be converted to. If it can, that’s the type of the ?: expression as a whole; if it can’t then the ?: expression is in error. There’s no ambiguity.

  16. Hi Eric,

    Are you aware of gcc’s __auto_type?

    float f(void);
    double d(void);
    int *ip(void);

    double foo(void)
    {
    __auto_type a = f();
    __auto_type b = d();
    __auto_type c = ip();

    return a * b + *c;
    }

    readelf(1)’s -w shows the DWARF that confirms their types.

    : Abbrev Number: 3 (DW_TAG_variable)
    DW_AT_name : a
    DW_AT_type :
    : Abbrev Number: 6 (DW_TAG_base_type)
    DW_AT_byte_size : 4
    DW_AT_encoding : 4 (float)
    DW_AT_name : (indirect string, offset: 0x0): float
    : Abbrev Number: 3 (DW_TAG_variable)
    DW_AT_name : b
    DW_AT_type :
    : Abbrev Number: 6 (DW_TAG_base_type)
    DW_AT_byte_size : 8
    DW_AT_encoding : 4 (float)
    DW_AT_name : (indirect string, offset: 0x25): double
    : Abbrev Number: 4 (DW_TAG_variable)
    DW_AT_name : c
    DW_AT_type :
    : Abbrev Number: 7 (DW_TAG_pointer_type)
    DW_AT_byte_size : 8
    DW_AT_type :
    : Abbrev Number: 8 (DW_TAG_base_type)
    DW_AT_byte_size : 4
    DW_AT_encoding : 5 (signed)
    DW_AT_name : int

    Cheers, Ralph.

    1. >Are you aware of gcc’s __auto_type?

      I was not.

      Wow, that’s going to make implementing := really trivial. Like, a couple of lines of code in the Bison parser trivial.

  17. I’m kinda confused, not about the proposal – it’s interesting – but about comments.

    Proposal has merit – it let’s machine do things machine is good at doing, freeing attention of an programmer and halving number of type inference bugs, at least. And it’s not C++’s auto with it’s three meanings. One thing that, I feel, could make it better, is to deny implicit type cast and promotions in this kind of variable declaration. As first step to bring strong typing into C.

    Most contrary comments are from people who C++, who don’t C (Variable declaration in-between code is from C99, each and every compiler supports that), or Rust fanatics, who are fanatics. I won’t read Michael’s comments or engage in discussion with him because they feel like bad faith arguments, tailored to Rust with blatant ignoring of any possible con. Hence half of the comments are… ideological from people who want C to die instead of evolving into something better.

  18. This seems to be called “type deduction” in some corners of the net. It gets really easily confused with “type inference” which IMHO ought to be reserved for things like Hindley-Milner that propagates things more widely than unidirectionally across an assignment.

  19. Eric, in response to your edit: the C++11 committee chose auto precisely because it was already a reserved keyword; there was no danger of breaking code that used it as a variable name. When they introduced its new semantics they decided to prohibit it as a storage class specifier (meaning a declaration of auto int foo; is now illegal) but in C I don’t think this is necessary. The rule can be: if auto is followed by a type specifier, it retains its legacy meaning as a storage class. Otherwise, it’s for type inference.

  20. __auto_type appears to be in Clang too.

    You can do stuff like,

    int foo() { return 3;}
    int bar () { return 6;}
    int main()
    {
    __auto_type fn = foo;
    __auto_type x = fn();
    fn = bar;
    __auto_type y = fn();

    printf(“%d %d\n”, x, y);
    }

    1. >__auto_type appears to be in Clang too.

      Well, that is interesting. Means the := implementation patch would be near trivial in both compilers. And the code generation for it is already tested.

      This is beginning to sound like a plan.

      1. Ralph, sorry I didn’t read your post closely enough and got carried away experimenting…

  21. I didn’t see anything like pointers to structures or functions, and the like mentioned.

    This page discusses declaration syntax and has some examples. Function pointers with arguments are always a mess, but need to be discussed if you are talking about declarations and making things more readable.

    My main point w/o repeating what the discussion already covers is that something more complex than ints should be discussed as well.

    https://blog.golang.org/gos-declaration-syntax

    1. Hi Jim, The proposal isn’t affected by the complexity of the RHS’s side, e.g. `old := signal(SIGHUP, SIG_DFL);’ will work just fine. It does now with __auto_type in gcc and clang.

  22. If I’m reading this correctly, the compiler checks to see what type bar() returns, and declares the new variable x to be that type, and if you change what bar() returns, it just automatically makes x be that new thing. The main problem I can see with this is that it’s no longer clear from the code itself what type of variable x is, so the programmer can’t even be sure it’s the right type to do y:=foo(x) later. Is there something I’m missing here?

    1. Hi Mo, You read this correctly.

      As for x being suitable for passing to foo() later, well, it might not be. For example, printf(“%ld”, x) would be fine when x was a long, but not when it changed to an int that used fewer bytes. Compilers tend to catch that kind of thing these days. Another problem is when maths is done with x knowing no overflow will occur because x is bar()’s type, but when that gets smaller it might. A new `:=’ doesn’t have to be used instead of `uint32_t x’, just as __auto_type is optional now.

      A lot of the time, x will just be a temporary local used to make decisions, or used in a way that the compiler would complain, and in those cases the `:=’ makes the code more succinct.

  23. It’s weird to see := suggested as fancy new functionality to me. That was how we did variable declarations in the very first language I ever learned(Turing), 20 years ago.

  24. C++ already has this feature through copy constructor.

    void foo(int i)
    {
    int x(i);
    // Do stuff with x, i here.
    }

    1. Hi pal,

      `const a := 5′, just as `const __auto_type a = 5′ works today. And if foo() returns a `const char *’ then `s := foo()’ forbids `*s = 0′, just as it does now with __auto_type.

  25. Another proposal: backporting return tuples from C to GO. This seems to be another really worthwhile sugar GO has to offer. The niceness and explicit in-place error-handling depends on this feature and replaces ref param for return values.

    in GO, this would look like this

    func foo(a int, b int) (c int, d int) {
    return a*2, b*2;
    }
    bar, baz := foo(1, 2);

    And in C:

    {int, int} foo(int a, int b) {
    return {a*2, b*2};
    }

    What do you think?

  26. I’m a Go enthusiast, proudly using Go on my day job since 2012, and I like this proposal. The Go specs call it “short variable declarations”.

    I would like to take this opportunity to discuss the issue of Go “multi-variable short declarations” from a design perspective (even though they don’t concern C).

    When you write something like

    a, b, c := f()

    you generally should have a clear understanding of which of a, b and c are being declared or redeclared, but that understanding is not reflected in that line of code.
    In fact the only thing you can infer from that line is that at least one of a, b and c is being declared or redeclared; what really happens depends on some preceding code and the short declaration in itself gives you no mean to change it.

    I’m aware that the current behavior of a multi-variable “:=” is necessary to make it play nice with block scope (Go block scope has been criticized but I think it is fine the way it is).
    But why not a more explicit syntax?
    Couldn’t short declarations be just special L-values rather than a different kind of statement? Eg:

    a, :b, c = f() // only b is being declared

    This seems like an obvious and simpler approach to me, but it would be very unlikely for me to have outsmarted Ken Thompson et al. on this ;)
    Is there some difficulty I haven’t noticed?

    I would really like to know some background history on the short declaration feature so that I could understand the motivations better.

    The current situation is not bad, but accidental redeclarations with “:=” may happen and the compiler doesn’t help.
    Allowing the programmer to consciously mark the variables he want declared would reduce this kind of problem.

    Moreover, this is a common situation I find myself in:

    | a := f()
    | {
    |      a, b := g() // wrong, I don't want to redeclare a
    | }

    Solution 1:

    | a := f()
    | {
    |      var b someTypeIReallyDontWantToType
    |      a, b = g()
    | }

    Solution 2:

    | a := f()
    | {
    |      tmp, b := g(); a = tmp
    | }

    Solution 1 is commonly employed but I think it somewhat defeats half of the benefits of short declarations.
    I used solution 2 for years but I don’t remember ever seeing it used, and gofmt doesn’t like my two statements on the same line to clarify the localized meaning of tmp.
    It would be nice to be able to write:

    | :a = f()
    | {
    |      a, :b = g()
    | }

    1. >It would be nice to be able to write [colon-prefix syntax]

      I agree, but my hopes of getting this mainstreamed are dependent on the fact that it’s a field-tested solution. If I tried to promulgate a new syntax I just don’t think it would fly politically.

      1. Thanks! Of course, I was not expecting you to promulgate it.

        It’s just that every time I stumble into it I ask myself, was my idea bad or just missed?
        This is a place more impartial about Go; you are talking about this feature and you are even in touch with Ken Thompson. I just thought you might know something or have a better explanation.
        Moreover I’m always interested in design discussion with unix philosophy-friendly people, though I know this is a little off-topic.

        In a 2010 go-nuts thread, someone pointed out this kind of issues with multi-variable :=.
        After an endless ramble about scoping that probably made everyone involved in Go design just run away, a similar syntax was proposed, but then the thread was just abandoned.

        1. >It’s just that every time I stumble into it I ask myself, was my idea bad or just missed?

          I think it’s good. Sometimes the way ideas take hold, or fail to, is inexplicable.

          1. I was not aware, thanks!
            Last time I searched I must have missed it, and that was years ago.
            The discussions I did find were very dispersive, attracting lots of “language x vs. language y” kind of arguments and unrealistic proposals.
            The discussion in the linked issue is more focused even though there was no agreement.
            At least now I know that the exact same idea I mentioned here was known and rsc commented on it:

            > Variants of this have been discussed in the past,
            > and they were never compelling enough to make us
            > want to change anything.
            > Personally, I think := is working well.

            I never had much hope for a language change in favor of my proposal, in fact I consider myself lucky if Go will remain the way it is.
            One of the issue comments criticize the :varname syntax as “magic syntax”, but I like it because it _takes out_ magic, not add to it, and I would be wary of proposals that try to add more smart behavior.

            I wonder how “:=” was conceived and how it evolved; was it thought without tuple assignment in mind and then adapted?
            Seems strange that whoever was inventing the current multi-variable “:=” smart behavior didn’t stop himself at some point and say “hey, we could just somehow mark the specific variables we want declared”.
            If what “:=” declares is not what the programmer want declared, he is forced to work around it.
            As others mentioned, it reminds me of implicit casting and is not very Go-like.

            Moreover the discussion about accidental redeclarations seems to always end up about scope and shadowing, but I think the root of the problem is just the lack of explicitness.

            I just watched Griesemer’s talk. The idea of a code rewriter to try out language changes did cross my mind and now I know where to start (when is another matter ;)

Leave a comment

Your email address will not be published. Required fields are marked *