4 ms·
C is the opposite of simple. If you want to create a binding in C, you need to learn multiple, unnecessarily complex rules. Sure, creating a binding to an int i
by fluffything 7y ago
C is the opposite of simple. If you want to create a binding in C, you need to learn multiple, unnecessarily complex rules. Sure, creating a binding to an int is easy:
int foo = 42;
but doing the same thing for pointers to arrays or function pointers is not:
void(foo)(int) = bar; // function pointer
int (foo)[N] = baz; // pointer to array
OTOH in Rust you just need to learn one rule: bindings are created with the grammar "PATTERN [: TYPE]" ([] means the ":TYPE" is optional.
That's the only rule you need to know: (1) it works consistently everywhere in the language (let, match, for, if-let, function arguments, while-let...), (2) it lets you create bindings, and (3) it gives you pattern matching and destructuring for free. For example,
let x: i32 = 42;
let y: fn(i32) = foo;
let z: \const [i32] = bar;
but also:
struct Entity { id: i32, vel: (f32, f32) }
let e: Entity; // given
let Entity { vel .. } = e; // vel points to e.vel
let Entity { vel: (v_x, ..), .. } = e; // v_x points to e.vel.x;
// Works in function arguments:
fn foo(Entitiy { vel, .. }: Entity) -> (f32, f32) { vel }
// Works in for loops:
for Entitiy { id, .. } in entities() {
// use the id of the entity
}
match entity {
Entity { vel: (v_x, ..), .. } if v_x > 0 => {
// do something if the entity.vel.x > 0
}
_ => /* do something else otherwise */
}
etc.
That's IMO the definition of efficiency: one simple rule, that has no exceptions, works everywhere, and lets you do a lot.
What C does of having multiple different incompatible rules, some for doing simple things like "int a = 42;" and some for doing complex things like "void (foo)(int) = bar" is not simple. It's just a pain. It means that professional C programmers need to look up rules for things they don't use as often, which is why Stackoverflow is full with questions about "How do I assign a function pointer to a variable in C?", "How do I pass a function pointer as a function argument in C?", etc. Having to learn multiple rules to do the same thing just sucks, and is one of the main reasons I love using Rust: when I learn something, I learn it only once, and things I learn later just reinforce that I learned it in the right way.
- jstimpfle 7y agoBut that's just not true. There is a single principled way for C declarations, and it's as easy as "a binding is a type name followed by an expression and a semicolon". And while small inconcistencies have been introduced over time, it was entirely consistent when C was conceived. The problem is just that the simple rule how to read declarations is not well-known (I don't understand why). See my other comments.
- fluffything 7y ago> "a binding is a type name followed by an expression and a semicolon". I find this hard to grock. Do you have a link to the actual grammar and production rules that apply to all cases ? When I see: int a = 3; I don't see a "type name followed by an expression and a semicolon", but rather the grammar "TYPE_NAME NAME = EXPR;". However, that's not correct, since TYPE_NAME cannot be any type (e.g. a function pointer won't work). When looking at void (*foo)(int) = expr; int (*foo)[N] = expr; or at how the keywords "struct" and "union" are part of the type name in some contexts, but not others, I see quite different grammar rules. I've tried to find literature about this, since I hack on a toy C parser every now and then, and those could simplify it, without much luck. I've never thought of these as "CDECL = EXPR;" or similar, since that does not work either (e.g. a function declaration would be "void foo(int);" but that's not exactly the same as "void (*foo)(int) = expr;").
- jstimpfle 7y ago> Do you have a link to the actual grammar and production rules that apply to all cases ? A google search returns that Annex A in the ISO standard has something like a grammar. Here is a link to an unoffical version of the standard: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf . However, this is not the right place to look for if you just want to understand the simple principle behind declarations, because the purity of declaration syntax has been considerably diluted in the last decades. So, only look in the standard if you are in good psychological health, and need to implement a production-grade C compiler. Also, note that grammars are overrated and tend to make things more complex than they really are. They are often too theoretical of a construct to be applicable, and that is certainly true for a language like C. Instead, I recommend you to read the idea from the horse's mouth, here is Dennis Ritchie talking about it: https://www.bell-labs.com/usr/dmr/www/chist.html https://www.bell-labs.com/usr/dmr/www/chist.html > I don't see a "type name followed by an expression and a semicolon", but rather the grammar "TYPE_NAME NAME = EXPR;". Let's ignore the optional equal sign + initializer expression, and just focus on declarations without initializers. The syntax is (as I said) "TYPE_NAME EXPR;" where EXPR is an expression that makes use of the newly-declared variable. > (e.g. a function pointer won't work) Not sure what you mean by "function pointer", but I'm pretty sure it's not a type name in the way I mean it. Here is how to look at your examples: void (*foo)(int) = expr; ^^^^^^ (optional) initializer ^^^^^^^^^^^ expression (originally it was (*foo)(x), but as I said nowadays there are type specifiers in the list which is a little inconsistent.) ^^^^ type name int (*foo)[N] = expr; ^^^^^^ (optional) initializer ^^^^^^^^ expression ^^^ type name Basically, the first example says "(* foo)(int) is a void", so you can conclude that foo is a pointer to a function that takes an int and returns a void. The second example says "(* foo)[N] is an int" so you can conclude that foo is a pointer to an array of N ints.