I repost because I'm becoming more and more frustrated with C, and wondered if others feel the same. Not because it's too low-level, rather because it seems to be trying (with limited success) to move to being a higher level language. There are times when I really do want a "thin layer over assembly", and C seems to have evolved to a place where that is no longer possible. I explain my distress more completely in the comments of Yann's article here: http://fastcompression.blogspot.com/2014/11/portability-woes...
If C was ever a thin layer over assembly, it stopped being that sometime in the 1980's, not recently.
C has not radically changed since the 1989 ANSI C standard. The library is bigger and there are some bells and whistles in there like designated initializers, structure literals, complex numbers and (now optional again) variable-length local arrays.
The trick of abusing C as a higher level assembler that are undefined behavior today were already undefined behavior already in 1980-something, and before that there was no standard at all, other than the K&R1 book.
If tracking changes in the specs, you'd be right, but I'm referring more to the mindset. And I don't mean to imply this is anything sudden --- if pressed, I'd probably roughly gesture in the direction of 1998-2000, possibly related to a slow "changing of the guard". In this incredibly long but fantastic thread, Anton Ertl points to GCC 2.9.5 as the turning point. http://compgroups.net/comp.arch/if-it-were-easy/2993157 (search for Ertl). His rhetoric might be excessive, but he makes a good point that in practical terms, his research on "threaded interpreters" went from being possible to do in C to impossible.
I'm also not referring to syntax --- I think many of the syntactical improvements are fantastic and have no downside. And the reliability of modern compilers is much better, and by-and-large the optimizations produce smaller and faster code. But what I think we're losing is the transparency of being able to reason about code execution by looking at source code. It's still the norm for academic papers to offer source for two approaches to a problem, and use execution time to prove that one is faster than the other because "it has more operations" or "it has more memory accesses", without ever looking at the generated assembling and noticing that one approach has been vectorized and unrolled, and the other has substituted a completely different algorithm that drops all the conditionals because the compiler realized they were never being exercised.
And I don't think it's simply a matter of programmers exploiting undefined behavior without properly considering the consequences. The inclusion of the standard library into the spec for the language creates "spooky actions at a distance" that never existed before, where passing a variable to "print_debug(a)" allows the compiler to remove the later check for "if (a == NULL)" if print_debug() is observed to pass the variable to printf() and if whole program optimization is being used. Whereas if print_debug() is in a compiled shared library, this won't happen, and the null-check will work as intended. I see the reasons compiler writers want this flexibility, but it sure makes development and debugging much harder than it used to be.
Summary: about a decade ago or so, Ertl ran into some regressions in GCC uncovered by benchmarking his Forth implementation, logged bugs for them, and they were fixed.
Some of them were in obscure GCC extensions, like notably computed goto. The compiler started generating code involving branches to a common instruction that does the actual indirect jump, instead of just doing that indirect jump.
It's easy to understand how a performance regression affecting such a feature can slip through, if the behavior is otherwise correct.
It doesn't mean that the GCC sky is falling, as all the trolling in that newsgroup would have you believe.
I come back to this a day later, after anyone is likely to read it, because this response continues to bother me. Your response isn't false, but I feel it really mischaracterizes the issue. These are not trolls in a newsgroup simply trying to get a rise out of people. They may be wrong, the current optimization approach may be the best choice available, and GCC may have made all the correct choices to fulfill its mission as it defines it, but summarizing as ~"once there bugs and then they were fixed" misses the point.
These are multiple well-respected CS researchers and extremely experienced C programmers saying they can no longer use C (not just GCC) in the manner they once did because they believe it's no longer a possible to make the language work the way they want it to. You appear to be an expert C programmer as well, and don't agree. Disagreement is fine, but I think you would be wrong to completely discount what they are saying.
I think it's a matter of what level of optimization you are aiming for. I'm mostly working on integer compression, and find that I am no longer able to use C to generate code that maximizes the performance of modern processors. Ertl's work on threaded dispatch has led to large improvement in the implementation of modern interpreters (http://bugs.python.org/issue4753). As a tool, C is stronger if it can take advantage of techniques such as this, and there don't appear to be any other languages higher than assembly where tip-top performance is possible. I may be just a cranky anonymous troll, but Ertl should be considered a canary telling us that we are in danger of losing the ability to make similar optimizations.
The best optimizing compilers are C and C++ compilers (as far as I know), perhaps Fortran but even that is getting more esoteric. I think that's why the assembly idea persists.
Restrict is a big hammer. You can work around aliasing suspicions simply by imagining you are programming a load-store machine whereby local variables are registers. You have some algorithm that works with several different objects, such as structures and arrays. You can help the compiler (and the reader!) by writing code that isn't repeatedly dereferencing pointers, but which loads values into local variables.
Concrete example, doubly-linked list deletion, bad:
Here, the compiler doesn't know whether the assignment to victim->prev->next clobbers victim->next, which is evaluated again in the second line. If you write it like this, you eliminate that concern:
The assignments here do not interfere; the first line assigns to a "prev" structure member, and the next line doesn't access any such thing; it uses the "prev" local variable which isn't a structure member.
Caching in local variables is preferrable to using some C99 "restrict" voodoo that introduces undefined behavior.
In the case of the above linked list, where are you even going to stick "restrict"? There is only one incoming pointer, victim. Making that "restrict" doesn't help. You need it in the actual structure declaration on the next and prev pointers. Yuck, what?
The gcc Fortran compiler is unfortunately pretty far behind the state of the art (or at least was a couple of years ago). To really realize the true potential of Fortran you unfortunately need to get a commercial compiler.
Before reading the comments on that article I knew that it's about C compiler authors (ab)using properties of the C language for optimizations (and other code-breaking behavior).
Properties that some language lawyers cooked up back in the stone age to keep all compilers on all architectures compliant without further effort for developers, which is typical design by committee cruft to get people to buy in on the standard.
Not so long ago C was the 'high level language' to assembly. Now few people use assembly and C is 'low level'. Its a weird transition to make. There's still plenty of people around who want to use C for high level tasks.
If I follow exactly what you are saying, you should say it's C coders who rely on undefined behaviour and compiler writers you are frustrated with, not the language itself.
Honestly, sometimes I think that the best solution for fixing the security quagmire we're in would have been not by creating a new system programming language like Go, Nim, Rust, D or even reviving one of the Wirthian languages (though having them on the table is very much a good thing), but by creating a memory safe dialect of ANSI C, and perhaps standardize on a better string handling library.
In fact, such an effort was started at AT&T back in 2001. The language was named Cyclone [1]. It met its 1.0 release in 2006. It has since been abandoned. Which is a damn shame, because it looked like it had real potential.
Is C a primitive language? Yes. In fact, it's almost stupidly simple compared to most modern languages. But its greatest weakness also proves to be its greatest strength. The main cognitive overhead from using C is in its manual memory management model and certain gotchas in standard libraries. It being bare bones as it is (but not too much) also makes it surprisingly pleasant at times. I get the feeling that plenty of modern languages try too hard to be novel and, for all the freedom you get from not having to worry about anything below program logic itself, you still end up expending a lot of effort into complicated high-level patterns that are frequently interspersed with arcane PL theory.
But that's not even the main reason why something like Cyclone is much needed. No. It's that we've pretty much perfected a ton of our APIs, like POSIX, to work with C. It simply feels most natural to work with POSIX in plain C. FFIs and bindings vary, some of them like Nim's actually feel quite clumsy (otherwise Nim makes it absolutely trivial to create FFIs, which is great).
Finally, refactoring 30 years worth of code into Cyclone sounds far more realistic than rewriting the world in Rust.
I can't say I agree with anything you say though... So only memory safety is C's problem? Not its type system that's very weak in every aspect (not just memory safety)? Not its missing module system? Not its ambiguous syntax?
You also confuse "simple" with "primitive" and perhaps "familiar". C surely is primitive, but how is it simple? Simple to use? No. Simple to implement? Not really. Has a simple mapping to the underlying hardware? That stopped being true three decades ago.
> Has a simple mapping to the underlying hardware? That stopped being true three decades ago.
I've seen this claim repeated a few times in the thread and I still have no idea what it means.
For medium levels of optimisation it's usually very simple to predict what assembly code will be generated by C compilers. On many platforms the operators often match directly to a instruction (e.g. arithmetic and bitwise operations) or short patterns of instructions (the control flow primitives and conditional execution, equality testing, function calling, etc). At a higher level, compiled C functions look pretty much exactly like manually written assembly language procedures and structs are just blocks of memory with clear order and offsets. It's also very simple to interface C and assembly code without any hacks on either side.
I think he's right, though. It's too much to ask for all this memory unsafe code to be rewritten into Rust, which is a large leap in semantics from C. There really is a space in the market for a safe variation on C which is easy to port to.
Now, how much safety you can demand before the language turns into a unicorn is another question and would be interesting to explore.
Its basically C with Go like syntax, and a package system instead of headers. I aim to fully interop with existing C code and have no runtime requirements beyond what C has.
I'm not trying to go crazy with extra features over what C has though. An important part of what will make it work, is perfect interop with existing C code.
Yes I understand memory safety issues, but there is always a need for machine independent assembly languages. which C essentially is.
Unfortunately, no one will agree exactly on which aspects of C to change and which to keep - that's why we now have hundreds of languages used by less than 10 people each.
E.g. while I applaud the idea, I found many aspects of your example code snippets ugly and un-C-like. This is not to say that I'm worthy of judging your work, but that a) so much of it is a matter of taste rather than objective thought and b) humans can adapt to literally anything, given time to get used to it.
They are closer to Go than C I admit. Many languages are actually shifting to grammars similar to this for a few reasons. At the very least the grammar is less ambiguous, and its easier to code editing tools to recover after parse errors.
G will use llvm as its primary backend, and my intention follow the C ABI exactly so interop requires no work.
I haven't considered the standard library much beyond the fact that I would G to have its own that is inspired by the good parts of other languages. At first it would have to rely on C libraries.
G won't be able to support every way that the Go standard library works. But if you need everything Go has, then you probably want to use Go to begin with.
I think its important only add those things after the language can run the same sorts of programs C can currently.
For the future, some of my ideas include:
a form of combined data types that require a type-switch to disambiguate (and is checked by the compiler), like algebraic data types. (essentially sugar over tagged unions)
It may also have a form of defer blocks similar to Go defer statements. (sugar over the C idiom goto cleanup;)
A final thing I considered was a built in reference counted pointer type. Just to partially alleviate memory management, but without resorting to a garbage collected run time.
> I always have it in the back of my head that I want to make a slightly better C. Just to clean up some of the rough edges and fix some of the more egregious problems.
I'm always mulling over that. "Maybe just namespaces, better macros, function overloading and member functions (eg this->) would work." Yet I'm sure Bjarne Stroustrup was thinking the same thing when he started on "C with Classes." The power to add features must be so overwhelmingly corruptive. So justifiable in each and every case, in the moment. And then you just can't stop. "Closures would be helpful. I'd love to write generic containers. Inheritance would save so much typing.", and the next thing you know, you're working on virtual inheritance, concepts and user-defined literals. :/
In my opinion, C really does need just a bit more to be great, but it would need someone with a will of steel to stop and leave things be after that.
Since it's a superset of C, why not just use C++ to program C-style code, using only the C++ features you want? C++ is a multi-paradigm language; you don't have to write object-oriented code if you don't want.
Code portability is a big one. People write C compilers in a week as a hobby. Now compare to how long it took Clang to fully support all of C++. Now certainly, C++ is on every major platform in broad use. But maybe you are targeting something embedded, or a hobbyist OS. Or maybe you want to write up a quick embedded interpreter for scripting in your application. There's something intrinsically satisfying about minimalism and simplicity.
It's also really hard to control the feature-set of C++ you want. The second you start pulling in code libraries, you find yourself needing to use the parts you don't want. Not in your own code directly, but in your interactions with their library code. Catching exceptions, needing RTTI, etc.
Symbol exporting is also a really sore point in C++. The symbol names C++ compilers spit out are just terrifying. With a C language, it's trivial to write bindings for other languages like C# and Python.
But for what it's worth, I still program almost exclusively in C++.
In fact, the Itanium C++ ABI (which has no relation to the architecture) specifically uses valid C identifiers to mangle C++ names to support products like EDG.
Looks like a decent start, but not enough there to really judge it yet. Best of luck, I'll keep an eye on it! If you'd like feedback (ignore this post if not):
Caseless switch is a great idea, especially no-fallthrough by default. Consider extending it with substituion, eg "case > 7:", "case >= 13 && < 16:", or "switch longvariablename as N { case N > 13 && N < 16:". Return types after function parameters are nice, consider saying "function foo(params) returns type" instead.
Tuples always feel important but in practice I tend to not like them. Variable definitions are a bit clunky, and inconsistent with struct variable definitions. Consider ctors/dtors and/or inline member initialization. I would recommend not to compress keywords: func -> function, const -> constant, etc. "type name struct" would feel better and more consistent as "class name". The C. prefix feels like it'd be great if you only use a very small number of C APIs, and very annoying if you used a lot of them.
The boldest and coolest part is building this directly on LLVM. My thought was always to transform the IL into C (trying my best to preserve line numbers and variable names), and then let GCC or Clang build that.
My ideas were unified function call syntax (never any functions inside classes), subclasses (declare a class inside another class, but the child class has direct access to the parent class' scope), namespace-aware multi-line macros (trailing \ is grotesque), and if implementing a backend instead of a translator, zero undefined behaviors in the language (do what people expect on eg signed integer overflow instead of GCC doing whatever is fastest yet obviously wrong. Aim for hardware that is actually used today instead of worrying about hypothetical PDP-11 programs, so no 9-bit chars.)
I've put some thought into macros into my various cross-assemblers. One thing that's important is to have different types of macros, and different types of argument values (some that are evaluated for you, some that are intentionally not evaluated.)
macro function square(integer value) {
return value * value;
}
integer x = square(foo() + bar()); //foo() and bar() each only called once
//square(integer) will exist as an exported function symbol
macro inline twice(statement operation) {
operation;
operation;
}
twice(foo()); //invokes foo() two times
//twice() will not generate an actual function
macro class vector(type T) {
T* pool;
integer capacity;
integer size;
function append(type U) { ... }
...
}
vector(integer) powersOfTwo{2, 4, 8, 16, 32, 64};
//any vector(type) will generate all the class functions for it
> I think if you emit C code, it is much harder to get good debug information, which is important.
I think you can annotate the generated code with certain preprocessor instructions to tell the compiler about the real source lines in order to get proper debug information. I think lex/yacc or flex/bison use that. Don't know if it's a gcc extension.
"C has the fastest development interactivity of any mainstream statically typed language."
This is not true. Using debuggers and crash dumps to reason about your running program is not fast or interactive at all. It doesn't matter how good the tools are. They interrupt the workflow.
Working in a REPL on the other hand, is real interaction with your program. You speak the language to the program and the program responds in the same language. This is fast.
> Using debuggers and crash dumps to reason about your running program is not fast or interactive at all.
Says you. Using core dumps is a different kind of interactive. Rather than testing out what a program would do or could do (as in a REPL), you're iterating on theories of what it did do. It definitely takes getting used to, but I'm often much faster debugging (even non-fatal problems) from a core file than iterating with a step-through debugger or rerunning the program.
It also helps a lot when the tools within the core dump debugger are good -- you should be able to quickly dump what's relevant to your program (not just threads and stacks and individual objects).
True, but that's an argument to move on to other, less popular (for now) statically typed languages which are better than C and have REPLs.
We could debate whether it's wise to move on from C (I think in many situations it is), whether it's likely there will be more industry adoption of $MY_FAVORITE_LANGUAGE, but that's a different debate.
Also, if we keep favoring C (or Java, or whatever) because they are mainstream, then other languages will never become mainstream, and we will be stuck forever coding in horrible languages. Who wants that?
Well, we can debate how mainstream Haskell is, but especially if you mean ML in the sense that does not include also OCaml, then ML definitely is not a mainstream language.
OCaml might be mainstream to armchair FP developers on HN and language theorists at Facebook, but it remains a fairly unpopular choice for developing software outside academia. Even Haskell is pretty unpopular relative to the amount of software developed daily.
"You wanted a banana but what you got was a gorilla holding the banana and the entire jungle."
- Joe Armstrong
One could argue, easily, this is true for many of the popular garbage collected languages. It is certainly why I do not care for them. It is also why I do not care for some of the popular OS's, or most of the popular "software solutions". It is the unwanted delivery of the kitchen sink when all I want is a glass of water.
The OP says he's frustrated with C "because it seems to be trying (with limited success) to move to being a higher level language. There are times when I really do want a "thin layer
over assembly", and C seems to have evolved to a place where that is no longer possible."
"seems to have evolved"
Some of my favorite programs that continue to work reliably, year after year, are written in what I guess some would call "primitive," K&R-like C and with little need for "the" standard libraries. I am not an expert C programmer, but I have no complaints when reading and editing this "primitive" C. I have seen others complain about it in forums.
One of my favorite source code comments is in the netcat source where the author says he's going to write the program as if he's writing in assembly. In my mind C should be just a thin layer over assembly. Essentially, to me, it should be a kind of "shorthand" for writing assembly. (That implies the author should think as if he's writing assembly. And C just takes away the drudgery of actually typing out all those lines of assembly instructions.) Now, that is just my opinion. I am not an expert C programmer. I know nothing.
"One could argue, easily, this is true for many of the popular garbage collected languages [...] It is the unwanted delivery of the kitchen sink when all I want is a glass of water."
Exactly. It's basically like forcing people to throw all their garbage on the ground and then forcing them to pay for the garbage men who collect said garbage. It would be easier for everyone to just go to a bin and dispose of the trash themselves.
The problem is that there's a great deal of data structures that are difficult or impossible to write in a provably safe way without garbage collection (or reference counting). Pick up _Purely Functional Data Structures_ and take a crack at implementing them in Rust without unsafe{} and you'll see what I mean.
The reason that some problems can simply be solved more efficiently (on current hardware architectures) in C than in other languages is that there's a difference between what is "provably safe" and what is "provably safe to a compiler". That's the whole point of Rust's unsafe{}; not to write code that's actually unsafe on purpose, but to say, "this code is safe, even though you can't prove it" (indeed, perhaps unsafe blocks ought to be renamed to safe blocks; that would also alleviate the problem of opposite meanings of unsafe blocks and unsafe functions).
That is to say, it's not true that there are data structures impossible to write in a provably safe way without automatic GC, since whatever automatic GC can do the programmer can do too (although, admittedly, at some point one begins to blur the line between GC and manual freeing, such as with reference counting). It's just that it's a lot easier for the compiler to prove that GC-managed code is safe.
I'm not sure that being forced to have the compiler prove that your code is safe rather than being able to say, "no, I proved it myself with more advanced techniques" is a good thing. Admittedly, most programs written today in so-called "safe" languages are probably beyond the scope of their programmers' abilities to prove safe, but then again, a lot of those programs turn out to be horribly unsafe even with the compiler's help!
> perhaps unsafe blocks ought to be renamed to safe blocks
We have had long and heated discussions on this topic, actually. My personal position is that the code inside such a block has fundamental tension: the compiler must assume that it is safe, while the programmer must in all cases admit the possibility that it is unsafe. Given that keywords should be optimized for the reader rather than the compiler, I prefer `unsafe` to `safe`.
However, that's not to say that there doesn't exist a better word entirely. Perhaps `unchecked` would suffice...
Not when you have an awful, tangled mess of garbage that's not all "loose leaf". Then a special person to disentangle and find out what truly is garbage can be a lot easier than trying to do it manually, every time, as a part of the computation itself.
Rust is aiming more to be a better C++. However that doesn't preclude it from being used in the same space as C and can become impressively lightweight if you forgo things like the standard library.
I would argue that it is better than C for its safety features alone. I hope in the future we look back at this decade with its never ending stream of buffer overflow vulnerabilities with disbelief that we could use something so insecure for something so vital.
I've always liked to think of myself as a programming polyglot, able to pick up and reason about nearly any standard language. I'm not sure how Rust is doing it, but the more I read and learn about it, the more confused I am. The syntax is just so bizarre and counter-intuitive to me. And yet I seem to be the only person in the world who doesn't "get" it. It's as if the language is being explicitly developed to be my kryptonite.
impl<'a> AnyRefExt<'a> for &'a Header {
#[inline]
fn as_ref<T: 'static>(self) -> Option<&'a T> {
trait UncheckedAnyRefExt<'a> {
unsafe fn as_ref_unchecked<T: 'static>(self) -> &'a T;
}
//I am guessing the second line is special because Rust can't deduce "to" should be a TraitObject type?
let raw = require_single_field!(raw);
let to: TraitObject = transmute_copy(&self);
//is this adding header and 'static together?
pub trait HeaderMarker<OutputType: Header + 'static> {
//I have zero clue what this is doing
//I can't even fathom a guess
match *self {
Past => write!(f.buf, "0"),
ExpiresDate(ref tm) => tm.fmt_header(f),
}
//so _raw is a reference to a vector of bytes inside [] and it might possibly return whatever a box is of headers?
fn parse_header(_raw: &[Vec<u8>]) -> Option<Box<Header>> {
//is there really a language keyword called macro_rules! ?
macro_rules! impl_header_from_fromstr {
//what in the world is ($ty:ty)?
($ty:ty) => (impl Header for $ty {
fn parse_header(raw: &[Vec<u8>]) -> Option<$ty> {
//who thought "h.is::<H>()" was a good way to test a type? Why not "h.is(H)?" Why the => ?
//what is this even returning? a tuple of false and None?
Typed(ref h) if h.is::<H>() => {
(false, None)
},
//... why not UTF-8? Because "{}"?
format_args!(format_but_not_utf8, "{}", HeaderShowAdapter(h))
The names don't make any sense to me at all. What the hell is an AnyRefExt? How does an UncheckedAnyRefExt differ? What in the world is the match thing doing? Why the asterisk on self? Why all the apostrophes and colons and dollar signs and exclamation points? Why is there both -> and =>? How does an &' differ from a '?
I could probably learn the language with a huge time investment. But for something aiming to fill the role of systems programming, it's just so incredibly different from everything else. It almost feels like they are going out of their way to make it as different as possible.
I know, this is all syntax stuff. C++ template meta-programming syntax is pretty insane, too, and took me a while to pick up. Yet when I attempt to read up on the syntax and the reasoning, it becomes even more confusing. I don't expect them to write a C++ clone, I understand this is their own language. But just a tiny modicum of reflection that people have spent 15+ years learning the syntaxes and behaviors of C-like languages would be so greatly appreciated. I mean, you see how Haskell is doing, and a lot of people blame that on the absolutely alien syntax to anything programmers are used to.
Yet, with Rust, everyone else with C-family backgrounds just seem to look at this, and go, "oh yeah that makes sense." Boggles my mind.
Have you written any Java code? There is basically the same thing, except they use & instead of + to build unions of type bounds.
And about all the * and & stuff: That's pointer stuff. It is very similar to how it works in C/C++. I think it's just a bit simpler in Rust (you are required to use & to pass something by reference where in C++ you don't see at the call site if something is passed by value or by reference).
About Box: Terms like "auto boxing" are known from other languages (Java), so this is not a new terminology invented by Rust.
-> for defining the return type of a function is also not an invention of Rust. I don't know many functional languages but Haskell uses it, and C++11 uses it! So coming from C++ you should know that. ;)
Before I begin, let me just say that you have chosen the worst possible piece of Rust code for someone who has never looked at the language before. :P
> I am guessing the second line is special because Rust
> can't deduce "to" should be a TraitObject type?
`transmute` and its kin are stdlib functions used to completely subvert the type system by casting any one type to any other type of the same size in memory. Their use is highly discouraged since they're so easy to misuse. You're correct in that Rust cannot deduce what `to` is supposed to be on this line (because by definition `transmute` is freeform and there is no context to infer the resultant type), hence requiring an explicit type annotation.
> is this adding header and 'static together?
Within trait bounds, `+` allows you to specify multiple bounds for a generic type. So it's not adding, but it's taking the union. :)
> so _raw is a reference to a vector of bytes inside []
> and it might possibly return whatever a box is of
> headers?
`_raw` is a reference to an array of vectors of bytes (in this case, a reference to an array of various binary data which has already been read into byte vectors). `parse_headers` will either return a heap-allocated header (`Box` is just a bog-standard thing allocated on the heap), or, if it fails to parse for whatever reason, it will return a value that clearly indicates that an error occurred.
> is there really a language keyword called macro_rules! ?
Nope, it's just a macro (that just so happens to be used to define other macros).
> who thought "h.is::<H>()" was a good way to test a
> type? Why not "h.is(H)?"
Type parameters are usually inferred, so you don't want them mucking up your actual parameter list. In C++ it would look like `is<H>()`, which is much nicer, except that this requires conflating parsing with type checking (see also http://en.wikipedia.org/wiki/The_lexer_hack). Rust thinks this is gross, so it arbitrarily uses `::` to denote the beginning a type parameter list in that context. It's gross, it sucks, it's literally the worst thing about Rust's syntax and 100% of people you survey will agree with you. But fortunately you rarely need to use it because type inference works so well. But inference can't here because Chris is trying to show off Rust's `Any` type, which is a library type that can be used to emulate dynamic typing, which requires a type switch to actually use the type for anything.
> what is this even returning? a tuple of false and None?
Correct!
> why not UTF-8? Because "{}"?
Beats me, this line is goofy. :) `format_args!` is an implementation detail for string formatting in Rust, it's unheard of to use it directly.
> What the hell is an AnyRefExt?
It's an implementation detail that was copied-and-pasted out of the stdlib specifically to facilitate this code snippet. We typically don't bother to fuss over the identifiers of things that users should never need to worry about. :)
> How does an UncheckedAnyRefExt differ?
You'd have to ask Chris Morgan, Rust has no control over what users name their own types!
> What in the world is the match thing doing?
It's just a beefed-up switch statement, ubiquitous throughout Rust code as it is used for working with enums (tagged unions). See http://doc.rust-lang.org/guide.html#match
> Why the asterisk on self?
The asterisk is the dereference operator, the same as C.
> Why all the apostrophes and colons and dollar signs and
> exclamation points?
Apostrophes: ML-inspired syntax to syntactically distinguish lifetime parameters. You see one, it means you're looking at a lifetime.
Colons: you'll have to be more specific, colons are literally every programming language designer's favorite symbol.
Dollar signs: only ever used while defining macros, which you should rarely ever read and even more rarely use.
Exclamation points: used to denote that you're using a macro and that what looks like a function call is actually going to expand to arbitrary code at compile time.
> Why is there both -> and =>?
`->` is used on functions and denotes the return type of the function. That's all it's used for. `=>` is used within `match`: `foo => bar` in Rust is the same as `case foo: bar` in C. That's all it's used for.
> How does an &' differ from a '?
An apostrophe just indicates a lifetime, which all references have (though they're inferred in the overwhelming majority of cases).
Many of the above things would have been made clear by reading the tutorial (http://doc.rust-lang.org/guide.html). I encourage you to do so if you'd like to learn more! Feel free to also hop onto our IRC channel at irc.mozilla.org to ask any questions at all, even ones that you think are stupid or obvious. :)
Thanks a lot for taking the time to reply! That did help a lot.
> you have chosen the worst possible piece of Rust code for someone who has never looked at the language before
That's my luck :P
I was in the middle of turning my HTTP message library from a series of "setHeaderName(value)" functions into just a single "setHeader(name, value)" and was told it had some innovative ideas on how to handle HTTP headers. And I have no doubt it does ... if I could make any sense of it.
Really glad to hear a lot of the vague names were library-specific, the really evil stuff shouldn't ever be used (though I'd argue it shouldn't be exposed outside of the stdlib if true), you definitely cleared up -> and => beautifully, I think the excess colons are a type annotation (used to 'type name' and not 'name: [type]'), and although I still have some questions (around ' and & specifics), I'll read up on your guide instead. Thanks again!
Have you read the Rust Guide? It starts off with the basic cases where the syntax makes sense. Most of the weird stuff is either macro weirdness or making sure the grammar is simple (e.g., the function::<blah>() business).
> But for something aiming to fill the role of systems programming, it's just so incredibly different from everything else. It almost feels like they are going out of their way to make it as different as possible.
Different compared to: most programming languages? Systems programming languages? Just C/++?
> I mean, you see how Haskell is doing, and a lot of people blame that on the absolutely alien syntax to anything programmers are used to.
Its syntax fits how it operates. Should Haskell have -() at the end of functions, like C-like and ML? No, since Haskell uses partial application and currying - the ()-stuff would likely end up as cruft. A function of three arguments in Haskell is not passing in three values as a tuple: it is a function that takes one argument and returns a function - that function is a function that takes one argument and returns a function... how is that clear when you write it as `f(x,y,z)? Not to mention if you partially apply it.
Can you write Haskell code as a sequence of declarations and expressions? Arguably no, since it has non-strict evaluation; having a list of declarations and expressions would be misleading, since they can really be evaluated in any order.
What can be contested is the significant layout (though it is optional) and user-definable operators. Though I think Haskell code would become really crufty without operators, since they sometimes make expressions more clear than a series of prefix-oriented functions.
Interesting, that could well be it. Languages I pick up very easily (in rough order): C, C++, PHP, C#, Java, Scala, Perl, Python, Ruby, Javascript, Lisp. Haskell is definitely one that gives me trouble as well. It's not the functional aspects: I've no trouble writing recursive descent parsers and the like in C++. I can't stand the parentheses, but I can understand and reason with Lisp.
I think in Haskell's case, it's just doing too much with too little. Like how they can implement quicksort in three short lines of code, and to me it's just so much to unroll in my head at once; but it's hard to be sure for obvious reasons.
All the languages you list maybe except for Lisp are basically the same. They are all imperative, some of them are object orientated. If you understand e.g. C++, you should understand any of the others with not much effort. Really different languages would be Haskell, Prolog and maybe APL. Or recursive (hence Turing complete) SQL queries.
I've been using Haskell for a few months now, and quicksort has become trivial to read. Although, I agree that knowledge of other programming languages barely transfers to Haskell. I think being very different is the main factor that restrains Haskell's growth. It says a lot that Haskell's popularity grows anyway.
I doubt it's a uniformly better C, just because the space has so many dimensions. I hope it's a better C, on balance, for the things I want to use C for. I've not yet dug in, though.
My impression of Rust is that it focuses too much on safety at the expense of making it hard to do what you want, especially when dealing with low level things.
... but I like adding different kinds of numbers without compile errors. I get that floating point and integers can be dangerous together, but forcing me to cast in order to add an i32 to an i64 seems like a case of missing a reasonable default.
Preventing buffer overflows requires runtime checks. That's a performance hit. Memory leaks require either GC or a restrictive type system. The first is another performance hit, the second is cumbersome to use.
I think Rust does a lot of things nicely, but I don't expect it to replace C simply because it adds a lot of complexity that people don't really want to deal with.
Buffer overflows occur in C because the language doesn't force programmers to perform those runtime checks and so many don't (or don't do it correctly).
Rust makes sure you are checking those things. Some of those checks are only at compile time and have no runtime cost. Others have a runtime cost but in the vast majority of situations the 'performance hit' will be negligible. Anywhere it is important you would rearrange your code to move the checking out of any critical path.
Note that it's still the same thing you'd have to be doing in C anyway to be both correct and fast. Rust just makes sure you don't forget the correct bit.
Regarding complexity and being cumbersome, Rust increases compile time dependency, but reduces debugging complexity.
Personally I would rather spend a bit more time at compile time thinking about memory ownership than spending hours (or even days/weeks) tracking down memory problems.
"By using theorem proving and strict type checking, the compiler can detect and prove that its implemented functions are not susceptible to bugs such as division by zero, memory leaks, buffer overflow, and other forms of memory corruption by verifying pointer arithmetic and reference counting before the program compiles."[0]
Here's a question: if you write the same code in Rust and C and Rust refuses it on the grounds that you did something wrong and C accepts it, how likely is it that the C code is going to crash at runtime, and you'll have to fix it anyway?
"C has the fastest development interactivity of any mainstream statically typed language."
I wonder if this statement is true since the invention of Go. A very fast compile was one of the design goals of Go, and its authors could afford luxuries (esp. In terms of memory use and object file size) that were unthinkable in the days of C. For example, IIRC object files contain the compiled code of their source plus the top hierarchy of modules called.
People have mentioned that changing a .h file in a large C project could trigger a lengthy recompile. AFAIK, Go minimizes this problem, so if the hype is correct there should be lots of projects where similar situations will be handled faster in Go.
My own empirical experience is very limited. I can confirm that Go compiles much faster than Java for my code, but that will surprise no one and it doesn't help us compare with C.
Does anybody have more tangible data regarding Go's alleged speed demon nature esp. as compared to C when compiled with e.g. VS or gcc?
First, I think the speed of C relies heavily on the compiler. C is not fast, not if I wrote the compiler. But GCC is very fast, and ICC at least as much.
I was a little confused by the Erlang wariness, but the reasoning seemed sound
> At Couchbase we recently spent easily 2+ man/months dealing with a crash in the Erlang VM. ... ... it was a race condition bug in core Erlang. We only found the problem via code inspection of Erlang.
That does not sound like a fun time, but once the bug is fixed, what's the big deal? Is there good reason to believe more bugs will come? It wasn't my ass that got bitten but I'd forgive Linux even if it had a race condition bug. (or if it crashes touching 1000 files in /etc)
No, C is fast, even tcc which does essentially no optimization except for constant folding is about half as fast as gcc -O1 I think. Basically no special work is needed to be 50 times as fast as python.
C was built as a replacement for assembly programming. It seems low-level today but it does a huge amount of work for you. Call stack manipulation, computing offsets for nested struct members, converting arbitrarily complex conditionals into the right branching instructions, coalescing a sequence of expressively named intermediate values into the same machine register... these are the things C does for you. I've never written a serious assembly program but I can imagine how annoying it is to do all that stuff manually. It would be easy to screw up.
I think C has been so successful because it made really good decisions about what to do for you. It eliminated a lot of those busy-work programming tasks without taking away your fundamental control of the machine. Automatic array bounds checking, garbage collection, ... they would have been unthinkable for the original goals of C. The stupid warts are annoying but I think C made fundamentally sound choices of what to do automatically and what to leave up to the programmer.
We all talk about modular, abstract code, but back in the day "modular" meant "use the same calling convention for every int(int, int) function", not "dependency injection". From that perspective, C must have felt like a huge leap in modularity and abstraction from assembler.
> That some of the most heavily used and reliable software in the world is built on C is proof that the flaws are overblown, and easy to detect and fix.
Easy to detect? Multiple times per year we find out about a security bug in Linux or Windows that has existed since the 90s.
Some of the unhappiness stems from C++, which was supposed to "fix C". Instead, it created a big mess. C++ is the only major language to support hiding ("abstraction") without memory safety. This turned out to be a poor feature combination, never again repeated. C has neither hiding nor memory safety, so if it's broken, at least it's visible at the user program level. Java/Pascal/Modula/Ada/Common LISP/Go/Rust, and the interpretive "scripting languages", all have both hiding and memory safety. We've at least come to an agreement on that feature set.
Because of the existence of C++, mainstream C language development more or less stopped. ANSI C, ISO C, C99, and C11 don't differ by much.
(At one time, I was promoting an approach to make C memory-safe in a way that allowed mixing safe and unsafe modules to allow a gradual transition and rewriting of old code. See (http://animats.com/papers/languages/safearraysforc43.pdf). It's technically feasible but politically hopeless. I'm now pinning my hopes on Rust and hoping they don't screw up.)
The reason I love C——I wouldn't like to love it but I am dragged down back to C each time so I think I've just given up——is that it costs a bit more to write.
I used to prototype things with Python by the method of sheer experimentation. When I found a suitable design, I'd rewrite it "properly" in Python and if that wasn't fast enough I'd think a bit and rewrite it in C. But that's happening less and less often these days.
Because C costs a bit more to write it makes me think ahead. And I've (once again) found that while prototyping in Python is fast it's even faster to think and then just write C. I write some boilerplate C and start adding the data structures... this could take a "long" time but once I've figured it out, writing the actual C code is a breeze. All the mental work has already been done and once the data structures are right, the implementation just follows.
Curiously, this doesn't exactly happen with Python. Because you can make a Python program run almost immediately, there's nothing forcing you to the productive idleness. In C, you'll have to write a bit of code first until you can get something meaningful up and running: this is the hatching time for what you really want to write.
I'm not sure why I haven't observed this with other languages. I suppose I would need to write hefty amounts of Java until I could get a program running somewhat, but this process of forced productive idleness mostly happens in C. Maybe it's because there are no layer beneath C and assembly and you intuitively know the tradeoffs and only a negligible delta of what you write and what the machine will actually do. Maybe it's because C feels so physical. I don't know. But so far C is the only language that gives me that process in a native, intuitive way, constantly telling me that "you must not rush, because you can't rush anyway".
Go is garbage collected though, and a lot of people using C don't want a GC'd language. (It's up for debate whether GC actually matters for most of them, but that's the feeling nonetheless).
Edit: also, not sure why you were downvoted, since your question at least deserves a reasonable answer.
I don't think tossing out the name of a popular alternative really contributes anything. I especially don't see what's 'reddit' about downvoting such a comment.
His comment was one made in good faith and included a sincere suggestion. Responses to his comment can educate him, the participants and the lurkers, which is where the contribution appears. Just because a comment doesn't instantly add to the conversation doesn't mean that it won't, given the good-natured participation of others.
Drive-by downvoting is what doesn't contribute anything in this case.
The thing is, there wasn't really an insightful question either. Just "Go. Discuss."
Perhaps if someone actually makes a detailed post on that subject then the post can be undownvoted. But at the moment there is no contribution, and the drive-by downvoting sorts it to the bottom, which is a good thing.
So you have the knowledge to judge the question uninsightful, but you don't lend your own insights. And instead of taking the trouble, you're content with the question being knocked down the page, which reduces the chances of anyone else doing it. Like I said, very Reddit. Would that we had a community more interested in illuminating than mercilessly judging.
I'll do it.
@aosmith: It's interesting, because Go's creators explicitly aimed it at the "systems" niche, which is what C was created to do, but C programmers don't seem to have picked up the baton the way Pike/etc hoped.
I think it comes down to a handful of things, in no particular order. YMMV, of course.
(1) Control. C gives you minute control over memory, Go of course does not. That's very satisfying and useful to many C programmers. Also, it's often helpful to have a good idea what your code is mapping to in terms of assembly instructions.
(2) Speed. Go still isn't anywhere near as fast as C in many, many cases.
(3) Portability. Lots of C programmers need (or get satisfaction from) the ability to compile C on a zillion platforms. Go supports only a few.
(4) Garbage collection. (Sorta a revisiting of point #1). Many C programmers don't want GC in their code. Now, while I wouldn't mind if my copy of 'less' used GC, some programmers would. I know I don't want GC freezing my game. So this is an issue, not one that will ever go away.
(5) Libraries. Go's made strides but can't touch C's library selection. Of course, you can use C libs from Go, but that has its own complications, and if the author already prefers C, why pick Go?
(6) Newness. In the context of systems languages, Go is still very new. Nobody's built an entire userspace out of it, ya know? Not that that's necessary, but certainly when the Unix programmer sits down and selects C, s/he knows anything can be built with it. It's long since passed into respectability. Go's on its way, but isn't there yet.
I'm sure there are a few other things. Don't think there's a way to run a runtime-less Go. As Damien mentioned, C has a bevy of tools, Go's selection is much more limited. I'm sure others can think of some more.
Thank you for the insightful response. I think you're mostly correct here, though there are, of course, subtleties.
Other than C and C++, Go is one of the best languages for controlling memory. Not freeing, of course, but making it very clear where there are allocations, where there are copies, exactly how much memory is being used by any particular data structure.
Portability - you can run binaries created by gccgo in many many places beyond amd64/x86/arm. No, certainly not as many as C... but probably as many as anyone would ever want to support. So unless you're targeting a specific embedded application, then Go is probably fine.
GC is getting a big push in Go and in 6 months they're going to make hard real time guarantees about the GC... i.e., no more than 10ms pause every 50ms, and the GC will be hybrid stop-the-world/concurrent, so some memory may be collected while other code continues to run.
Otherwise, yes, you're right, there are (of course) some limitations to using Go over C.
However, there are some huge benefits to using Go over C as well... such as memory safety. There will never be a buffer overflow in your code. Also, concurrent code will be a thousand times more readable and easier to reason about.
That's not to say that C isn't the right choice sometimes... I just think the places where C is the best choice are becoming more and more rare, and not just due to Go, but Rust as well.
Interesting. I didn't know about gccgo, and it'll be fun to see what problem domains Go ends up in with a predictable GC. Thanks for expanding on the topic.
> Other than C and C++, Go is one of the best languages for controlling memory. Not freeing, of course, but making it very clear where there are allocations, where there are copies, exactly how much memory is being used by any particular data structure.
It doesn't matter. I'm not going to start a new project at work in either language. But I might conceivably start one in Python, Ruby, go, JavaScript, java, Scala, or another well-used language.
I don't have enough knowledge of Go to answer it. And I'm perfectly content pointing out a problem without fixing it myself.
I think you're taking downvotes far too seriously. I might even call caring about karma a reddit attitude ;) And as far as illumination, I think you have it backwards. Posts that actually have illuminating answers should be brought to the top, so that many people can learn from them. Posts that only have a chance of getting such an answer should lie further down. There's probably also a positive correlation between people likely to form a detailed illuminating response and people that read further down the page on a particular subject.
What's a good practical way to get my feet wet with C? I want to learn it, but I'm the sort of person that can only learn a language by actually trying to build something useful in it. Sometimes it seems like a language that everyone thinks is important to know, but that no one thinks should actually be used in a real world project unless you're working on embedded systems or building an OS. What are some ideal (and beginner-friendly) use cases?
Go download SDL 2.0 and build a simple breakout game, or space invaders, or something. Add networking support.
You won't get to hide from the annoying stuff in C (memory allocation, string manipulation, etc.), but the problem space (a simple game) is right there. It's also something that many, many people have done before, so it'll be easy to start.
Thanks, I really like the simple game idea, and SDL looks like a cool framework. Also neat that it can compile to ios/android-compatible binary (though perhaps that would introduce a new can of worms).
Thanks, I'm actually going through Learn C The Hard Way now, but it's the sort of thing that doesn't really work for me. I just need to work on some cool little app idea that C is well suited for and learn/bang my head against the wall until I figure it out. The problem is that I can't seem to come up with anything small and practical to build that anyone actually thinks should be done in C. It's like there are beginner tutorials on one end, linux kernel hacking/database internals/NASA robots on the other end, and nothing in the middle. My experience with other languages and areas of interest are fairly wide, so it seems like I should be able to come up with something, but I'm feeling sort of at a loss.
I think writing a C compiler in C is probably the most effective way of learning pure C, although probably not the most efficient. You don't even need to generate assembly code, you can just compile to C itself or some other language.
Maybe just reimplement some piece of existing software you wrote with a C version? I want to port a small node-based API to C in the future, just to see what it takes.
It claims C has the fastest build/run cycle. Is this true? Many non-compiled languages I can redefine my functions at runtime, and with Java I can hotswap.
I don't think it's true. Back in the day Pascal was known for a fast compile, single pass compile was a major selling point for Borland's Turbo Pascal.
I don't know if a single pass C compiler ever existed but current C compilers use multiples passes (in addition to preprocessing).
As far as I remember C was always known for a slow turn around cycle compared to other languages.
Borland used to tout the virtues of their single-pass C/C++ compiler too, even as far back as version 2.0.
Most modern C compilers have used an integrated preprocessor for quite some time, and Clang/LLVM can emit machine code directly without going through a separate assembler.
go has a very, very fast build cycle, partly due to the language and partly due to conventions (the standard preprocessor in C is absent in go). But, that might not have been on Damien's radar when he wrote this.
And, go is built by some of the same people who built C for some of the same purposes (plus some new ones, like concurrency and networks), so it's arguably a "better C", and probably prioritizes some of the same goals. Simplicity and small surface core language area, in particular, but removes some of the dangerous aspects of C (which might be a problem for something performance-oriented, since well-written C is still faster than well-written go for most tasks, and may always be).
But, as you note, if you don't have to build, the cycle is even faster. I still consider a compile phase a slowdown over dynamic languages that don't compile.
Then again, so many people have introduced "compile" like stages into their dynamic process (SASS, LESS, test coverage checks, etc.). It may be difficult to completely get away from a compile like stage in any app of significant size.
Anyway, a large project in go is faster to compile than a large project in C, in my experience, but my experience is exceedingly limited.
I don't see why people keep calling Go a better C. Most of the C programmers I know use it because they want manual memory management which Go doesn't have.
Go has the fastest build/run cycle I've seen. You can usually compile compile to a binary and run it quicker than it takes a python runtime to start up. It's totally viable to use Go as a scripting language while compiling to binaries from source.
I mostly work in Python but I had a similar scripting-like experience during my Go project -- with a small codebase, the time it spent compiling was barely perceivable, if at all. Very impressive, and shows you what's possible.
Semi-relatedly, the downvoting around here has gotten completely out of control. It's sad, what's happened to HN.
C isn't trying to fit in that space. regardless, C code takes far less time to parse, optimise, and convert into machine code than all the languages i know of.
Nope. Pascal is still king here. The speed it compiles at is simply ridiculous. Largely this is because the way the modules are defined makes it a snap to pre-compile aggressively. You're simply not allowed to do things that would change the way a module is understood by the compiler based on context.
The biggest hold-up in the C world is rampant abuse of #include files.
> The biggest hold-up in the C world is rampant abuse of #include files.
true.
> Nope. Pascal is still king here.
for some values of "king" perhaps - perhaps it's faster to compile, but no arrays of varying lengths in a function call? and if i'm not mistake, a function must be defined before use..? pascal is incredibly restrictive, and perhaps that's why it's so fast.
It's very restrictive, but the compiler is unbelievably fast because of that.
I'm not going to argue that Pascal is somehow superior to C, it's far more limited, but there's lessons here to be learned on how to make a compiler rip through code.
> for some values of "king" perhaps - perhaps it's faster to compile, but no arrays of varying ...
Well, let's not change the goalpost - your initial assertion was that C has faster compile times. Then chances are that something's got to give, whether that is in the grammar, declaration order, etc.
There is currently a really cool project I suggest (if you're interested in learning / seeing C in action) called Handmade Hero[1].
He[2] is making a game, from scratch, in (mostly) C (but with a little bit of C++ niceties thrown in), and streaming[3] the entire process for an hourish, Monday -> Friday. It's intended to be a tutorial in both learning C and learning from-scratch game development (where from scratch means using GDI and like, parsing your own wav files).
It's really cool to watch, he's an entertaining guy, afaict is a pretty decent teacher (I know zero C, but C-style languages, and I _think_ I'm keeping up), so I recommend checking it out.
The good news is that with VS 2015 you will be able to use Clang for compiling C code, so hopefully a complete C99/C11 implementation on Windows at some point.
Microsoft reiterated at Connect 2014 that only the parts required by the C++ standard are to be included, as well as, smaller parts required by well known FOSS projects that they see value in supporting on Windows.
Clang support is only planned as compile target for Android and eventually iOS.
> Clang support is only planned as compile target for Android and eventually iOS.
I know they plan to officially support only Android and iOS. However, even today I can use Clang to create Windows executables from the command line. Once the support for Clang will be included in VS, it is only a question of time until someone will swap the Clang from the Android NDK with a Clang compiled for Windows.
The boring response would be that different languages have different strengths. C is indeed capable of being very fast, mainly by virtue of letting the dev have fine control over memory. But execution speed isn't the only metric. Sometimes you just need something to be done quickly and you know you aren't going to hit the constraints (time, space) of the system, so you might as well go with something like .NET to reduce your development time.
The point about the tooling I'm not so sure about. There's a lot of languages out there with well developed tools. And the tools tend to address the pain points: you have GC profiling in .NET, and you have Valgrind-type tools in c++.
"I always have it in the back of my head that I want to make a slightly better C. Just to clean up some of the rough edges and fix some of the more egregious problems."
This is called Go. Brought to you by the same people who gave you C.
No it is not. Go is only "like C" in the sense of the most superficial syntactic similarities and that it is primitive. It isn't even in the same world as C, it is just another high level garbage collected language that compares poorly with the rest of the languages in that group. Go was not brought to us by the people who made C. It was brought to us by Rob Pike and Russ Cox. Dennis Ritchie gave us C.
Ken Thomson was (and is) also involved with creating Go and he was also one of the writers of Unix. He actually invented the B language which preceeded C, so it was close :-)
I think he wrote a lot of C in Unix and was therefore part of making C popular.
Ken had nearly as little involvement in Go as he had in C, they just asked him stuff. Did he write any of it at all? "From the same people as C" is a factual claim, and it is factually incorrect. Ken absolutely did not bring us C, and it is very sketchy to claim he brought us go. Go is brought to us by the guy who came to bell labs after it was all over and decided to try to pass himself off as "the bell labs guy!" for the rest of eternity.
Just putting this from the official FAQ, for the record.
Robert Griesemer, Rob Pike and Ken Thompson started
sketching the goals for a new language on the white board
on September 21, 2007. Within a few days the goals had
settled into a plan to do something and a fair idea of what
it would be. Design continued part-time in parallel with
unrelated work. By January 2008, Ken had started work on a
compiler with which to explore ideas; it generated C code
as its output. By mid-year the language had become a
full-time project and had settled enough to attempt a
production compiler.
Yeah, that's deliberately selling Ken's involvement as hard as you can without dishonesty, and it still shows he wasn't actually involved in go, just "exploring ideas for a language".
C is not the fastest language. C++ is faster than C. For example, the only way you can hope to match C++ inline template algorithms in C is with a horrific macro scheme.
Yeah it's true that restrict is well supported, mostly because all C++ compilers are also C compilers.
Maybe the biggest reason why C++ is slower in practice is because the programmers tend to feel that using features unique to C++ almost mandatory. Even though technically the pure C program also compiles fine in C++.
It’s as if people make asymptotic progress towards the goal of the ‘ultimate programming language’, the newer innovations which may well have improvements are too marginal to be beneficial, there is too much existing inertia, that is why the more prominent modern ones incorporate interoperability with C often. It would seem more sensible to say as the author of this post seems to propose, that C is the de facto ultimate.
In this thread I saw no mention of Freebasic. It has some good features and is comparable to C. TerraLang is another interesting approach (for system programming languages).
But the fundamental flaw is that in many cases a DSl that compiles to something lower level is the way to Go.
Hmmm. I work on a pretty large scientific computing project written in C++. Complex numbers in C++ are much, much better than they are in C. In general, I'm very happy it isn't in C.
I have no idea why you're being downvoted. Probably by a bunch of people who think they can write C and C++ without writing any security bugs. Which, unless they belong to a vanishingly small group of security experts who write code in an organization with the same discipline, budget and glacial schedule of the team writing software for the Space Shuttle, they can't.
Memory safety can't guarantee security or correctness. If you guys want to have a better discussion, you need to be even handed about the complexity of security. Languages can solve some classes of security problems.
Agreed. But memory safety is really such low-hanging fruit right now. Given that memory bugs still produce massive quantities of severe vulnerabilities, it seems appropriate to hammer on that aspect of languages.
If memory safety ever becomes a practically solved problem (e.g. somebody's running an OpenBSD-style count of how many days since a new memory bug popped up and it gets past, I don't know, hate to be greedy, how about 3?), then I'll be merrily banging away on the other aspects.
except that literally every piece of software you have ever written or used goes through some level of C, and lets face it, most of the C projects out there are actually pretty well tested.
and it's not like higher level languages are significantly better in this regard.
It's not like higher level languages are significantly better? Seriously?
The last time I accidentally executed injected code due to a free error or buggy bounds check in the non-C parts of Python, or Ruby, or Lisp, or Erlang was ... well, never.
And no, the fact that C underlies all these languages isn't a testimony to the adequacy of its security features.
That's so clearly false that it hardly deserves a response. I'm spending more time wondering if you are trolling that why you are wrong. But in case you aren't trolling:
1) A lot of "Python" security vulnerabilities are caused by "C"-problems (overflow errors, etc). [1] and [2] are a couple from this year.
2) You'd expect interpreting python to be more secure than C because the interpreter acts as a filter over input reducing the attack surface, meaning that potentially vulnerable paths are minimised are better tested than if an entire program was in C.
No, because writing a VM constitutes a reduction in attack surface. Implement a bytecode instruction properly once, and it's good everywhere it's used. Implement a correct garbage collector, and you don't have to worry about use-after-free anywhere in your Python code. You still have to worry about it in your C, though, and you have to make sure it's done correctly Every Single Place you do it. And if you think programmers are up to that task, go browse the CVEs for a while.
Look, I know lots of people like you think that C isn't that hard to get secure code from. But if you were correct, we wouldn't be living in the security cesspool of memory bugs we are now.
Technically you are correct: "C is not insecure". In the same way as "English is not offensive"
However programs written in C may or may not be insecure.
Of those programs you would expect a compiler or interpreter, that has been tested in the wild on millions of programs, and so you are more likely to find the security bugs. Also we hope these programs are written by very talented engineers.
On the other hand Joe Enterprise hacking up a greenfield web service for the first time in C ... more likely to have security bugs.
If Joe Enterprise moves from C to Java. Less likely to have security bugs.
Historical accident. People choose C, because nowadays it happens to be everywhere, which eases porting but there are lots of languages that could be used instead, if people valued safety.
Distressingly true. I despair that 20 years from now, absent an utterly wrenching change, we will all still be playing whack-a-mole with a seemingly infinite number of security bugs, many of monumental severity, all because we are unable to move away from our memory unsafe foundations.
And the endless supply of ignorant C/C++ programmers (of which I am not suggesting Damien is a member) who profess to management that they can easily write secure code does not help.
I beg your pardon? I've used valgrind for this purpose on large scientific codes running with MPI on supercomputers. And that works fine. The PETSc team at Argonne National Lab, who write perhaps the most used high-performance parallell linear algebra library in existence in C use valgrind for debugging. Surely those count as non-trivial applications?
HPC code is trivially 'easy' to write from this perspective. You mainly 'only' have to worry about memory leaks, dead locks or overflows on valid or accidentally miss-formated data. It's not like an army of world class hackers are pouring over your code looking for vulnerabilities to exploit. Nor that someone will intentionally try to craft input that will crash your application.
"If you ran valgrind (and mudflap) then any error that might be detected by a compiler with range checking will be detected."
Only if you can be sure you have touched all the interesting portions of input-space. Ideally your tests are doing this, but wherever your tests miss aren't covered by valgrind or mudflap but are covered by a change of language. That's a part of what's meant by safe. It's not that it's impossible to write correct code in an unsafe language. It's that it's difficult to impossible to verify that you have written correct code. I'm very willing to include available tools in that calculation, but valgrind and mudflap (while tremendously useful) cannot give guarantees.
Strictly, "... or that you can run your tests on an OS that does, and your code does not behave meaningfully differently between OSes." Either way, it may or may not be a strong assumption, depending on domain...
I think the parent was trying to say "no one has managed to use C safely in a non-trivial application", not "no one has managed to use mudflap or valgind on a nontrivial application".
I didn't say anything about whether either was correct. I simply was trying to help people avoid talking past each other, because the parent seems to be addressing something the grandparent didn't say.
People have unquestionably used Valgrind to find errors, in trivial and nontrivial applications. The question here is whether the result is "safe code". It may be, but the parent does not seem to be speaking to that question, hence my attempt at clarification.
I understood you, I was replying to the thread in general.
> ... the result is "safe code". It may be, but the parent does not seem to be speaking to that question ...
Here we differ. The ability to successfully run valgrind (and presumably remove all errors) does actually speak to the question, and it answers that the result is in fact safe code.
It is not necessary for the compiler to assure safe code, it's perfectly possible to do it with an external tool. And as a result C is a safe language as long as you use both tools.
If we presume "... and remove all errors" (and this can be done reliably), then yes that is speaking directly to the question and you have safe code. I would be surprised if the comment I replied to had intended to be asserting that, though. If that is what they intended, it would have been good for them to be more explicit about what amounts to the strongest claim made in the comment.
He kinda did. He writes that they changed from Erlang to C because of problems in Erlang. He wanted some safety or tools to help him find problems easier (actually problems in the language or libraries they used). They might develop slightly faster now in C than they did before in Erlang but I doubt it and the rewrite took time that probably cost them 2 months of inproductivity compared to not rewriting it. As for running speed they of course want that. Can't remember if they actually thought that low speed was problem with the earlier Couchbase.
Which is filled with security bugs. Consider the conventional sysadmin wisdom that if someone gets a shell on your box, it's as good as owned, due to a no doubt fertile field of privilege escalation vulnerabilities in the kernel.
Don't get me wrong -- the Linux kernel is a magnificent piece of engineering, and an excellent choice for hosting a mainstream server, I run a number of such myself -- but let's not kid ourselves about its security limitations.
Drivers in (everything, possibly Ocaml/Lua/Common Lisp/C++)
Os in everything.
The Linux kernel is not written in C but in a DSL that there is not yet a compiler to generate C. This is the same like Enlightment Object System and GObject.
moreover drivers should be written in a DSl language (Termite-like) that supports concurrency and provided by spec writters as an appendix.
The driver is two parts. The OS adaptation Layer and a Formal description of modus Operandis.
Consider USB, normally the spec provides an implementtion i a DSL that "compiles" to the prose.
One can mechanically check for invariants and properties and
compile to an OS package.
I think it is possible. Packages for example could describe transports
e.g. module BT uses an Ocaml-like interface for transport that can have various implementations like USB transport.
But all could be written in the same DSL.
Of course Kernel developers are allergic to memory management. Rust/Ocaml/D are the anti-histamine.
i'm sorry, but proof by convention or consensus is not a proof. you have an idea of the linux kernel, and it's not entirely unwarranted. but whilst there have obviously been security issues, it would be a mistake to suggest that they are the fault of C.
Let's just say that it's not C's fault. It's just that C is particularly well suited for creating security problems. The base semantics is dangerous, the undefined behaviors are lurking everywhere, and the compilers writers don't care ("it's the user fault if they go in undefined behavior that we put everywhere, and they'd better says thank you that we didn't ring their phone while they were in the bath the last time they did an integer multiplication without checking overflow, because the spec said we could have done it").
Sometimes I think the ebola crisis is created by a GCC developer. "I'm sorry for those people, but I was entitled to it, that guy there dereferenced a wild pointer, I could have cured cancer instead, but no".
Strictly speaking, you are correct. But saying C is not at fault is like finding the trivial solution to a polynomial -- it is correct but not very useful.
No, C is not at fault. The programmers are at fault. But it is prudent to ask, when some of the world's foremost programmers produce an apparently limitless number of CVEs as they do, whether they could use some help from the language. If the answer is yes, we should recognize that such help cannot come from C.
You can attribute fault to the programmer, or you can attribute fault to the language, but for every problem there is a solution that is simple easy and wrong.
>Every time there is a claim of "near C" performance from a higher level language like Java or Haskell, it becomes a sick joke when you see the details. They have to do awkward backflips of syntax, use special knowledge of "smart" compilers and VM internals to get that performance, to the point that the simple expressive nature of the language is lost to strange optimizations that are version specific, and usually only stand up in micro-benchmarks.
I see this kind of statement a lot, and it simply does not hold up to even a cursory look. Where are the awkward backflips of syntax, special compiler or VM knowledge, or version specific optimizations here?
>Critically important to developer efficiency and productivity is the "build, run, debug" cycle.
This is a pretty bizarre claim. Sure, having an equally archaic and primitive process but 10 times slower is obviously worse (C++), but that doesn't mean having an archaic and primitive process is good if it is fast. I rarely ever run my code, much less debug it. Freeing myself from that horrible way of working was one of the greatest things about working in a language with a useful type system.
Haskell is beautiful and mind-bending, but to get close to C in any non-trivial problem, you will end up writing imperative code. We have 4 implementations of a graph algorithm:
a) Idiomatic Haskell takes ~25 minutes
b) Monadic Haskell takes 11 seconds
c) C++ takes 3 seconds
d) C takes 1 seconds (5x less memory than b))
If you look at all the micro-benchmarks, C uses far less memory than Java or Haskell and the memory is the bottleneck in the most cases.
This is the reason why HPC is FORTRAN, C and C++.
Reference: Computational Topology via Functional Programming: A Baseline Analysis
>but to get close to C in any non-trivial problem, you will end up writing imperative code.
I know that's not true because I do it on a regular basis. For problems that require writing imperative code you end up writing imperative code. This is not a problem, writing imperative code in haskell is far nice than writing it in C.
>Monadic Haskell takes 11 seconds
"Monadic Haskell" is meaningless when making performance claims. If you want help fixing your code, you need to post it. And idiomatic haskell uses monads all the time. Is this another case of the common "I am pretending that deliberately doing things the slowest way possible is the definition of idiomatic" trolling?
>C takes 1 seconds
If your C++ is taking 3x as long as your C you are doing something wrong.
Can you give an example of the code you write which is on par with C in terms of speed and is non-trivial? Did you look up the algorithm? (it is perfectly defined recursively)
Arguing what is nicer is subjective.
To clarify, I did not write the code (mine is the C version), the results are from the paper. Read it, beat it and post results.
Often obvious recursive algorithms aren't well-optimized by GHC. This might be the kind of "bend over backwards" coding you're talking about, though.
The way you can escape from this performance trap is to (as usual) write explicit state passing instead of direct recursion. GHC can inline and fuse these away much more nicely and often arrives at a very tight, C-like inner loop.
I'm not sure if I call this bending over backwards though since Haskell style has always been to avoid explicit recursion and this kind of state-passing transform can be hidden behind the same set of combinators you're used to.
I'm not going to go buy a book just so I can spend the time writing code for you to dismiss as "oh that's not idiomatic haskell". Are you saying you don't actually know and are just repeating the claims made by the authors? Do you know haskell?
If you avoid recursion and use explicit state passing and create an API with combinators hiding the state passing plumbing you get very fast Haskell.
I believe many people never get to the "write combinators to hide plumbing" part and assume to get very fast Haskell they have to duplicate the explicit state passing everywhere.
Wow, I just noticed the name and saw that all these empty shitposts are the same person.
>No, he gives reference.
Why are you speaking to what someone else knows? Your desire to see your own words exceeds your ability to string together words in a constructive manner.
>Can you support your claims? Surely the burden on proof is on "Haskell is just as fast".
I made no such claim. Your dishonesty is appalling.
"shitposts", "appaling dishonesty", "desire to see my own words", no "ability to sting words in a constructive manner", etc.
Those were aimed at me.
And these were aimed at at the parent:
"you don't actually know", "are just repeating the claims made by the authors", "do you know haskell?", "trolling", "you are doing something wrong".
Plus condescending responses like "if you want you code fixed" and "I'm not going to buy a book just so I can spend the time writing code for you to dismiss" -- when given a reference by the parent. I only intervened because I didn't like your constant condescending tone.
Are you actually here to discuss, or you just came to insult people? If it's the second, maybe try Reddit?
While Rust is indeed competitive with C here, its likely only number #1 because LLVM is generating better code than gcc for this example. I'd expect clang/C to run at least as fast.
When people compare C with other languages, what they are actually comparing are optimizing implementations of native code compilers for C with 30 years of effort against implementations of other languages. Not always choosing the best ones.
Specially the fact that C is fast because of 30 years of compiler research, tends to the ignored by C zealots.
Lets not forget that 30 years ago C compilers used to suck really bad, and most of us that were coding back then were using Assembly for real men work.
C, Pascal dialects, Modula-2, Algol dialects, PL/I and many other candidates to systems programming were all equally bad in code quality.
But of course, C guys like that new generations think that C was the first systems programming language and its compilers always produced fast code.
Ànd assembler was once an innovation as well. I read a quote by Richard Hamming once, he stated "automatic programming" as it was called, would be a great productivety booster.
One might assume he is using Haskell or similar, and is assuming that because the compiler accepts his code it is therefore correct, of course this is a fallacy. I will use a particular apt quote from Donald Knuth "Beware of bugs in the above code; I have only proved it correct, not tried it."
If correctness can't be demonstrated by mathematical proof, and it can't be demonstrated by comprehensive test suite, how can it be demonstrated? What kind of definition of 'correct' are you using?
Haskell is not quite sufficient to connect mathematical proof to your code. Coq/Agda/Idris are much closer to this idea though and, if you can create a spec in one of those systems then a proof really is sufficient.
You can express "this code performs at most n primitive steps from this list". Theoretically it should be possible to express any performance requirement in that form, but I doubt it's practical to do for e.g. numeric computation. (It's usable for "no page render require more than 4 db roundtrips" or similar though).
I'd bet you could ensure that some kind of tolerance and stability checks were performed and then carry the results around. Ultimately whenever computation relies on dynamic input it's necessary to have a hard-coded "tolerance failure" check.
Theoretically you could embed enough analysis in there to have theories about the actual code you're running, too, but I don't think as many analysts care about theorem proving—they're more of an algebraicist's toy.
That you did. I certainly didn't mean it as a takedown! I just saw it a subtlety worth stressing, and about which I was eager to learn more if there was more to learn :)
Perhaps mathematical proof + comprehensive test suite (including property/fuzz testing) + (time used without bugs showing up * number of people using it)
This does not prevent you from proving the program correct by hand. Regrettably that is rarely done, but could in principle be done regardless of the type system of the language. Of course that would require you to write a specification of the of the features you want to implement, which might be messy depending on your problem domain.
I'm not sure what this has to do with me. Again, I never said you can't prove programs correct. I simply stated how I work: I don't run my code nearly as often as I would when working in C.
Imagine if just compiling your code told you all the things you get from running a good suite of unit tests. And you didn't even have to write the tests, just add a few annotation hints to help the automatic test generator occasionally. That's what a strongly typed language can get you, impossible as it sounds.
"Strong typing" is an ambiguous term, I'm going to need something more specific. As far as I'm aware, "strong typing" is used as a spectrum, in which case C is relatively strong because the compiler will reject parameters that don't match a function signature's data type unless explicitly cast.
Nonetheless, unit tests aren't a panacea. If all you do is just run your unit tests, watch them pass and proclaim "Well, everything works! No need to do any acceptance, integration, system, fuzz or other form of testing! Not even gonna run it, let's just ship!", then that's quite stupid, to put it mildly.
> "Strong typing" is an ambiguous term, I'm going to need something more specific. As far as I'm aware, "strong typing" is used as a spectrum, in which case C is relatively strong because the compiler will reject parameters that don't match a function signature's data type unless explicitly cast.
C has some unfortunate integer conversion rules and a style that often encourages the use of (unsafe) void pointers. Perhaps more importantly, the only way to implement polymorphism is with casting or unions, both of which are very much unsafe.
Still, you can write safe code in C, at least once you write an object system. Use structs rather than typedefs, avoid casts, use only memory-safe things, write wrapper functions to allow you to do allocation and deallocation in a safe way. It's actually possible to reach a similar safety level to something like Haskell - but it will require a lot of tedious hand coding because the language doesn't have the abstraction facilities that would let you write these helpers generically. Eventually you end up greenspunning.
> Nonetheless, unit tests aren't a panacea. If all you do is just run your unit tests, watch them pass and proclaim "Well, everything works! No need to do any acceptance, integration, system, fuzz or other form of testing! Not even gonna run it, let's just ship!", then that's quite stupid, to put it mildly.
Sure. But I think it's reasonable for your day-to-day code loop to be edit / run unit tests, and to do the fancier testing or manual testing maybe a few times a week.
In a language like Haskell (actually I use Scala) you can get to the same point with compilation. It won't just happen without working at it, you need to think about what invariants are important and then encode them into your types. And you wouldn't want to release/deploy without executing the program or running some tests. But you can get to the point where in day-to-day coding an edit/compile cycle is enough.
C is strongly typed, it just has too few types to enable the really useful benefits of static type checking. If typedef actually let you define new types instead of just defining a shorthand for an existing type, it would be a much safer language.
"Actually, the reality of C is much more complex: C has two different type systems. There is one type system (with types such as int, double and even function types) at the level of the program, and a different type system, defined solely by lengths of bitstrings, at the level of execution. … As a result, it isn't even meaningful to talk about soundness for C, because the static types and dynamic type representations simply don't agree."
page 263 "Programming Languages: Application and Interpretation" Shriram Krishnamurthi 2003
That's an outdated version of a bad book. That statement is completely false. Notice how as racket's type system improved, he continued to update the book to be more and more pro-types and removed more and more the deliberate FUD like that?
>There is one type system (with types such as int, double and even function types) at the level of the program
Yes, that is the only type system.
>and a different type system, defined solely by lengths of bitstrings, at the level of execution
That is not a type system. By definition type systems are at the program level. Data classification occurs at the level of execution, and such a system exists in virtually every language whether it has a type system or not.
If you have ever used the phrases “strong typing” or “weak typing”, define them.
That’s what I thought. But thank you for playing.
Indeed, the phrases are not only poorly defined, they are also wrong, because the problem is not with the “strength” of the type checker but rather with the nature of the run-time system that backs them.
Concerning "strong typing", yeah I completely agree. I've wanted to talk about types and programming languages for some time now, but I've had trouble because the word "type" means very different things depending on what programming languages you are familiar with.
I think the thing that people are so excited about for recent type systems is that they have some basis in type theory. So maybe we can call these type systems "mathematically typed" or something along those lines?
I'm not sure if I agree about the highly subjective "power level" claim, but then who's to say.
More specifically, it's quite certain that types are capable of solidly dispatching some kinds of errors and tests a different kind. In other words, they are orthogonally optimized and combine well.
Yes, it does verify behavior... "up to" how well your types describe your program's behavior. That is, you get a free proof that your program does at least what it says it does in its types.
If, for example, I give one of my Haskell functions the type "f: [a] -> a", I know that it a) does not have side effects, and b) must pick some element from the list to hand me back, i.e. it cannot just construct or cast "something" to an 'a' and give me that. (There is a caveat around explicit "unsafeXXX" usage, but that's mostly beside the point.)
Both of those properties constrain behavior and are impossible to prove using unit testing.
It seems my confusion was that 'a' represents a generic in Haskell. Since the parameter is generic the function can't perform any type-specific operations on the value (e.g., creation or modification), and since it doesn't accept a function to apply to the values in the list, the only thing it can do is return one of the values it was given.
Yes, exactly. There's no way to construct a value of an arbitrary type -- outside of non-termination[1] (which doesn't really produce any values) or abuses of unsafeXXX functions. I think I perhaps should have been more explicit in my example. It could also have been specified as
f: forall a . [a] -> a
which would make it more explicit that the function has to work for any given 'a'.
[1] ... which I think I should have actually specified. The function type given does not preclude non-termination in Haskell. (You can specify such things in Idris/Coq/Agda, but the effort of implementing f can be much higher if you have to prove totality.)
If said a implements the Num typeclass (can kind of think of it like an interface) then you can perform Num specific operations such as adding/subtracting or averaging.
It verifies all the behavior that you tell it to. Consider the simple example used to help teach people to use type systems effectively: tic tac toe. You can write an implementation of tic tac toe in which the behavior of the game is type checked. You can even do so in java, although it is certainly more effort than in a more powerful language.
'So what is "strong typing"? This appears to be a meaningless phrase, and people often use it in a nonsensical fashion. To some it seems to mean "The language has a type checker". To others it means "The language is sound" (that is, the type checker and run-time system are related). To most, it just seems to mean, "A language like Pascal, C or Java, related in a way I can't quite make precise". If someone uses this phrase, be sure to ask them to define it for you. (For amusement, watch them squirm.)'
page 263 "Programming Languages: Application and Interpretation" Shriram Krishnamurthi 2003
Is that an opinion you share with the author and you are referencing a really poor book in an attempt at an argument from authority? Strongly typed is well defined, even when a Racket enthusiast dishonestly tries to pretend otherwise in his Racket marketing book. That nonsense was removed from the book was it not?
The term was defined by Barbara Liskov in 1974: "whenever an object is passed from a calling function to a called function, its type must be compatible with the type declared in the called function". Very clear, very simple, no nonsensical or meaningless problems at all.
It's absolutely possible for certain values of "power of your type system", "power of your program's types", and "extensiveness of your unit testing system". A few points to elaborate this.
1. Types absolutely can capture (trivially often) invariants which can never be satisfactorily covered by unit tests. This is the difference between proof and evidence.
2. Unit tests are far easier to write for many kinds of program behavior. Even though you can encode essentially anything you want into sufficiently powerful type systems, their proofs can be too challenging to practically build.
3. Type checking works very differently from testing. It's not red-green-refactor. It's more like "describe your problem in types and then slowly discover the only possible program which is correct". The feedback cycle here is very fast, but sometimes more inhospitable. Major research focus is on making this better, though. I'd say some of the most advanced HCI work is being done in dependently typed proof systems... but it's targeted only at people who understand and use dependently typed proof systems to begin with.
4. Unit tests often duplicate functionality which is trivially and automatically inferred by a type system. This is why, for certain invariants, types are "free" and unit tests are both incomplete and a big violation of DRY.
So, essentially, the unit tests which get written for you automatically are the trivial ones which are inferable properties. A dynlang usually does not have sufficient structure to enable this inference so you have to cover it with unit tests or contracts. More advanced, un-inferable properties can be handled and checked on compilation as well, but typically it takes some effort to build this stuff. Interestingly, though, you usually do it with heavy automation driven by the (partial) inference that the compiler provides—it becomes a conversation between you and the compiler as you develop the program which satisfies the type specification you gave.
I've seen this attitude a couple of times before and I think it's a perfectly valid reaction to have towards these statements because you're correct. It is impossible. However, type systems heavily steeped in type theory get around it being impossible by cheating. For example ...
Consider the halting problem. It's pretty common knowledge that given a program and some input there's no general algorithm that tells you if that program halts when the programming language is turing complete. However, we know that every simply typed lambda calculus program halts if it passes the type checker, so did we defeat the halting problem? No we cheated because simply typed lambda calculus isn't turing complete.
The problem with type theory is that sometimes it excludes good programs that we want to run just because we don't know how to prove that the program is good (or maybe it's even unprovable). So we try to develop new type theories that will allow us to prove more and more good programs are in fact good.
Does quickcheck run at compile time for Haskell? (I've dabbled with Haskell, and I've read about Quickcheck in other languages besides Haskell. In those other languages, Quickcheck definitely does not "happen" at compile time ... does it for Haskell?)
Idunno I write JavaScript primarily and I can go days without running a project. It's a way that I enjoy working. I kind of thought that everyone works this way. When I run my code, I'll usually have a few typos and syntax oversights, but other than that it usually works fine and tests out. Writing in a concise functional style really helps.
To some degree, if you can't write code without running it, it's a sign that you don't really understand the code you are writing.
Ehhh, it's all pretty simple logic. There's nothing different about "non trivial" stuff, other than maybe the opinion that the devs have of themselves.
Exactly what I said. I rarely run my code. When your compiler catches 99% of your errors as soon as you reload your repl, there's far less need to be constantly running your program to see what it is going to do. I know what it is going to do, the code tells me. I only run it when I need to, which is when I am doing something unsafe, when I have made a lot of big changes and want to double check them, or when I need it to run to produce new output for some other program to process.
> They have to do awkward backflips of syntax, use special knowledge of "smart" compilers and VM internals to get that performance, to the point that the simple expressive nature of the language is lost to strange optimizations that are version specific, and usually only stand up in micro-benchmarks.
...Huh, and the author thinks C doesn't? :)
(Well, except the VM part.)
"What Every C Programmer Should Know About Undefined Behavior" springs to mind: I believe it's been discussed several times in HN.
You managed to respond, but didn't manage to actually say anything. If you want to argue the point, then make your argument. Claiming it is "nowhere near idiomatic" despite it being perfectly normal code and doing none of the things he claimed is not an argument.
>Plus they are microbenchmarks (which he also mentions).
Notice how I linked to the web benchmarks too? Your complaint was addressed in the post you are complaining about.
>You managed to respond, but didn't manage to actually say anything. If you want to argue the point, then make your argument. Claiming it is "nowhere near idiomatic" despite it being perfectly normal code and doing none of the things he claimed is not an argument.
Perfectly normal code that treats Java like C.
>Notice how I linked to the web benchmarks too?
This does not negate the fact that you also linked to these microbenchmarks, which was what I addressed.
Plus, the web benchmarks you linked to are also microbenchmarks. They are not measurements of a real life application -- it's mostly getting the framework to print "Hello World", or to do a few database calls, etc.
Again, put forth an argument. If all you can do is make unsubstantiated claims, then you might as well just keep them to yourself. You aren't furthering a discussion.
>This does not negate the fact that you also linked to these microbenchmarks
No, it negates your fallacious point. The claim that it only applies to microbenchmarks. The web benchmarks are not microbenchmarks, therefore it clearly does not only apply to microbenchmarks. Are you being deliberately dishonest?
>Plus, the web benchmarks you linked to are also microbenchmarks
If you consider an entire web server and framework to be a microbenchmark, then you are clearly being intellectually dishonest. This is not the forum for that kind of garbage, take it to reddit.
C++ compile times are, in large part, are due to header files. The language moves all sorts of implementation details like class definitions and private variables and templates and inline functions.
I think he's saying he works on the code for long periods of time before worrying about how running it works out. Then he just tweaks the bits that are off and goes about his business.
Funny, that's exactly how I work with Fortran (2008). And I have Syntastic in Vim nag me every time I save if I made a mistake that the compiler catches. A great advantage over C/C++ is that even on 30 000+ LoC projects split into many files, compiling from clean takes less than 30 seconds. "implicit none", of course.
Nope, what Rapzid said. My code very rarely compiles first try, but when it does I have a strong amount of certainty that it will work. So I only have to run it a few times a day to check on the output, rather than every single time I compile it. Nobody said anything about releasing it without running it.
Nothing. What is horrible is being forced to run it constantly every single time you make a tiny change to see if you messed it up or not. I don't need to do that when I am using haskell, I just have my repl session autmatically :reload when I save a file and it tells me if I broke anything.
To you downvoters, consider that the top-voted comment in an earlier submission (https://news.ycombinator.com/item?id=5037089) agrees with me in principle, if not in form. If nothing else, the endless onslaught of critical security bugs we see throughout the software world suggests that we should be striving mightily to deprecate memory unsafe languages, not writing lovefests about rediscovering the worst of the offenders.
Language wars in a nutshell. However, there is a significant contingent of C practitioners who delude themselves into thinking that they are good at writing secure code, when all the world's evidence militates against them. Hopefully one day "C Considered Harmful" is a meme and we only use C, and its memory-unsafe friends, in hopefully increasingly rare places where absolutely nothing else will do.
We need to be realistic and admit C/C++ is part of the problem. The sooner everyone accepts that, the better.
It almost seems cruel to have a blog post written by someone from more than a month ago on the top of HN on Thanksgiving Day. I can picture the turkey and stuffing slowly tumbling out of Damien's mouth as he learns a blog post he wrote from early 2013 is at the top of HN right now.
And the followup discussed here: https://news.ycombinator.com/item?id=5075370
I repost because I'm becoming more and more frustrated with C, and wondered if others feel the same. Not because it's too low-level, rather because it seems to be trying (with limited success) to move to being a higher level language. There are times when I really do want a "thin layer over assembly", and C seems to have evolved to a place where that is no longer possible. I explain my distress more completely in the comments of Yann's article here: http://fastcompression.blogspot.com/2014/11/portability-woes...