7 ms·
> let x: i32 = 42; I'm sorry but this notation will always make me scream. C is so much simple: int x = 42; not "let", no colon, and no ambiguous "i32 = 42"
by jokoon 7y ago
> let x: i32 = 42;
I'm sorry but this notation will always make me scream. C is so much simple:
int x = 42;
not "let", no colon, and no ambiguous "i32 = 42"
- bschwindHN 7y agoHow about `let x = 42` and let type inference do the work? I used to do a lot of C-style variable declarations in other languages but I'm warmed up to Rust's really fast because most of the time I don't need to explicitly name the type.
- jokoon 7y ago"let" should be removed, and it doesn't change that the type should be placed before, not after with a colon.
- gameswithgo 7y agothen you have to introduce new keywords in other places to remove ambiguity.
- chrismorgan 7y agoWhether a type should come before or after the name is a fairly subjective matter. I believe that after is generally superior, especially when the type is optional. But there’s a very practical reason for requiring the `let` token: it makes parsing very much easier. With `let`, you can keep a LL(1) grammar, because seeing `let` tells you to next parse a pattern, then if there’s a colon a type after that. But if you don’t put something in there, you get a genuinely intractable problem once type grammar is not trivial: sure, `int foo;` is simple and obvious, but what about `A<B, C> d;`? should that be parsed as an expression (respaced, `A < B, C > d;`). Some languages have not resolved this style of parsing ambiguity at all, and figure it out at runtime, based on what else they find (Perl is infamous for this). I think others only kind-of resolve it, by looking at what symbols are present at compile time, to decide what was meant. Others just declare that such ambiguities are parsed one way, and you can rewrite your code (e.g. add parentheses) if you want to mean the other. Still others have resolved it otherwise, by other more subtle syntactic means, so that even if you need arbitrary look-ahead while parsing, there’s not quite any overlap between the two syntaxes (e.g. don’t support commas in this way as a kind of alternative to semicolon within expressions; or use proper matched delimiters like [] or () for generics). Rust chooses to make parsing simple, which benefits humans as well as machines, reducing cognitive requirements in reading code. Furthermore, in Rust what follows `let` is not an identifier or identifier list, but rather a pattern. Imagine the following contrived example: let x = [[0]]; type x = [u32; 1]; let a = 0; let [a]: x = [1]; That falls over completely if you put the type first: `x [a] = [1];`—does that define a new binding a with value 1, or does it set x[0] to [1]? And finally, as I mentioned, the type is optional, and not commonly required, so you end up with something like C++’s `auto` keyword, which is basically `let` but spelled worse (and with worse semantics). The end result is that for Rust specifically, what you desire is quite unsuitable, and what it has works very well—and that its reasons for doing things that way are well worth while considering.
- bschwindHN 7y agolol okay, how about you just stick with C then since you like it so much.
- jrimbault 7y agoI found in "real" rust code I very rarely declare any types other than in function signatures and I keep functions pure, short and to the point. It's so rare to have hardcoded values anyway.
- GordonS 7y agoI just made a similar comment before I read yours, but about C# rather than C, where just like C you can do: `int x = 42;` But since int (32-bit integer) is the default integer type, you can also do: `var x = 42;` If you want to use another type, for example, ulong (unsigned, 64-bit integer), you can do: `ulong x = 42;` Or: `var x = 42ul;` C#'s syntax is not only terser, but seems a lot easier to read to me. With rust's syntax, it's not immediately clear what the value is, and what the type is.
- logicchains 7y agoIt's only clear because you already know what "long" and "int" mean in C#. It's potentially quite confusing for someone coming from a C or C++ background as they mean different things there. i32 and u32 on the other hand are ambiguously 32 bits. In C++ I've actually had code using a value like `42l` that works on Linux but not on Windows, because the sizes of those types aren't fixed.
- GordonS 7y agoA valid point about knowing what the types mean, but even if you don't, it is at least immediately apparent which part is the type, and which part is the value. I've only dabbled with rust, but I came across this very early on, and was baffled by the syntax. After further dabbling, I still can't see it and immediately know what the value is.
- labawi 7y agoAFAIK, syntax is: let variable_name [: type] = value[(i|u|f)bits]; In rust, 123l is written 123i64 or 123i32 (also 123_i32, or 1_2_3i32), depending on what 123l actually means. I don't actually use rust, but it seems very clear and obvious to me (obvious once you know the syntax above).
- GordonS 7y agoMeh, 123_i32 seems like a big improvement to me, but with the others, I just can't immediately grok it - it's having numeric digits as part of the type name that throws me. I realise of course that not everyone will feel the same.
- fasterthanlime 7y agoHow do you feel about C's function pointer syntax?
- jstimpfle 7y agoTo be pedantic, there is no (specialized) function pointer syntax. The syntax to declare function pointers is just general declaration syntax, which in turn is basically regular expression syntax. How to declare a function pointer is hard to grok when not being introduced to declaring variables in a principled way. But it makes sense and is not too clunky if you're only declaring a function pointer every now and then.
- fasterthanlime 7y agoExactly! C is internally consistent in that, for example, the star in: int *a; is part of the variable declaration, not the type. And the function pointer syntax derives from that. (I know you know, just providing context). However, just because it's consistent doesn't mean it's easy to remember or use. Just repeating "it's easy!" doesn't make it true. Others have brought up that cdecl exists, which illustrates this pretty well. It seems you've worked with this syntax for long enough, and it fits your way of thinking well enough, that it's not an issue at all for you! But there's ample evidence of a lot of people struggling with it, which should be sufficient to deem it "not easy".
- jstimpfle 7y agoI can understand why newer languages have moved away from this style of declaration syntax, and moving away has brought merits such as becoming more intuitive to understand to novices, as well as better support for tooling / IDEs, including parsing performance gains. On the other hand, nothing is quite so easy to read and write for me as the terse C declaration syntax (granted I don't declare a lot of pointers-to-functions-returning-pointers-to-functions).
- gameswithgo 7y agoThe C approach makes the compiler much more complex, and introduces extra typing in other language constructs. (like parens around if statements) This is why many newer languages do something more like the Rust way. Overall it is simpler for programmer and compiler.
- dmit 7y agoPretty much. The mere existence of cdecl(1) says a lot about the simplicity of C's type declaration syntax.
- jstimpfle 7y agoIt's unfortunate that the simple (and easy) underlying principles are not well known. See my other comment. The bigger reason why recent languages have different declaration syntax is to avoid the need to carry a symbol table during parsing, and to avoid the need to parse all files serially instead of independently. Because to recognize a declaration the parser has to know which words correspond to types in the current scope.
- jstimpfle 7y agoI don't think parentheses around if-conditions are related to C variable declarations (if that's what you were saying), and I think it's fair to say that C's syntactical terseness is unmatched. The parentheses are required to separate the condition from the following conditional statement.
- Sharlin 7y agoYou mean, like when declaring an array: int[4] arr; // oops, doesn’t compile Or a function pointer: int (*)(int) fptr; // oops, doesn’t compile So much for the simplicity of C declaration syntax.
- jstimpfle 7y agoOne can make the argument that the C declaration syntax is simple, because the rule to make a declaration is to simply follow a type name by an expression where the declared variable is used. The fact that you write "int[4] arr" shows that you don't know how it works (which is not a criticism; it's just not well known how it works). The correct way is to write int arr[4]; and to interpret it as "arr[4] is an int" (which is only a slight lie because arr[4] is undefined if arr is a 4-element array). How do you declare an array of pointers? Again, you write int *arr[4]; // array of 4 pointers to ints because in C expression syntax, "* arr[4]" means to index into the array first and then to dereference. If that is an int, it means that arr[4] is a pointer to an int, and consequently arr is an array of pointers to ints. If you want a pointer to an array of ints instead, do this int (*arr)[4]; // pointer to an array of 4 ints again, because that's how regular C expressions work. Functions are (mostly) not an exception: int myfunc(int x); int (*myptr)(int x); which is to say that "myfunc(x) is an int" and "(* myptr)(x) is an int", i.e. myptr is a pointer to a function that takes an int and returns an int. Note that in the beginning (i.e. K&R C, pre-1989) the way to declare functions was consistent: Declarations had to be int myfunc(x); i.e. there was no types in the argument lists. The types in the argument list appeared, I believe, after Stroustroup added them to C++ in order to improve type-safety.
- bloomer 7y agohttp://c-faq.com/decl/spiral.anderson.html http://c-faq.com/decl/spiral.anderson.html Is the rule I learned to understand C declarations and while the rule there is described as simple I think the examples even without argument types are actually fairly complex. It also seems telling that no recent language has followed C’s example for declaration style, which is more implicit than explicit.
- fluffything 7y agoC 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.
- kuon 7y agoI worked with C for nearly 20 years, and it is everything but simple. It is complicated, not "low level" as many thinks, full of weird edge cases, compilers will happily compile non standard code, fragile (hard to refactor), full of implicit conversion you didn't expect... Rust has a good design, it still have a few rough edges but the syntax is great and improving.