In Rust, and other languages actually as well, I made use of a lot of variables. This because it made the code a bit more self-documenting.
E.g:
let x = some_func();
some_other_func(x);
But since I'm getting code reviews now I got the feedback that I should use as less variables as possible, and write code as inline as much as possible if the variable is not used in multiple places.
So
some_other_func(some_func());
I understand and accept this feedback, because it makes the code look cleaner. But also makes it less self-documenting.
I also have the feeling that Rust kind of teached me to use a lot of variables because of the ownership and borrow system. Does someone else have an opinion on this?
It's true that the borrow checker sometimes forces you to store values in variables, and that's unique to Rust.
You shouldn't be bothered by it, it's normal to have this kind of problems when working with multiple programming languages; me and my colleagues sometimes show this "lag" when switching languages such as using the match statement in the wrong order when switching between Scala and Rust (it works both ways), and using the let keyword in JavaScript when const was the right choice.
Again, I wouldn't be bothered. Just accept that it's a quirk of working with Rust, and it's normal.
On the contrary I feel like Rust pushed me more towards an expression style "variableless" idiom, making functions only one expression, or only combining expressions. But I was already on that functional journey before coming to Rust, having done a bit of ocaml etc.
Personally, I think this is terrible advice. It's like saying you should avoid writing functions as much as possible, unless they are called multiple times. For me, variables act as a kind of mental checkpoint in understanding code. Instead of "here is a giant expression I have to pick apart and understand in one go", I have a collection of simpler expressions that can be understood in isolation and have convenient names. Human brains like having names for things.
Sure, you can take it too far, but that's true of basically any subjective choice you make while programming. And while Rust sometimes forces you to add bindings where you would not normally need them, I don't think it overly encourages it in general. I sometimes pack an entire expression block into the condition of a branch, just because I want to explicitly "contain" some calculation, making it clear that it only matters in this one spot.
But, hey, if that's the house style, then your hands are kinda tied.
Honestly this this generally the kind of pr feedback that makes me cringe. Not because with or without variables is better, but because it's not about why you're adding or removing the variables, something that should be specific to each instance.
For example, "the variable name foo is redundant with the function get_next_foo() you're getting it from, so you can shorten the function with no loss in clarity here", vs "extracting this long argument out to a variable would allow the formatter to make the call a single line, so you can shorten the function with no lack of clarity" both get the same goal with completely opposite approaches (and shortening functions is a surprisingly useful rubric for code quality, though obviously easy to abuse if codified)
From my experience, I am creating more variables and using block expressions a lot more. Not really a problem, just the headache of coming up with names.
Oh boy. That is review feed back that I would often have heated resistance to. There are two rules being laid down here:
Thous shalt not introduce redundant variables.
Thou shalt stuff as much code into a single line/statement as possible.
Both of which I strongly object to.
Firstly this almost certainly has no effect on the generated code. That x in your example will be optimised away. Also there it is likely some_func() gets inlined so there isn't even a call. You can see this by looking at the generated code in Godbolt: Compiler Explorer Your code will be short and fast either way. Even if that optimisation does not happen it likely has no practical impact on size/speed.
That means these review suggestions can only be aesthetic/stylistic preferences. Or some argument about "readability". Which of course everyone has different opinions on.
Personally I find it helps to try and get the code to document itself. As such I think it is useful to give meaningful names to things. That might mean spitting some knotty piece of code into its own function or if it is only required once naming some intermediate variable. Taking your example we see that none of the names tell us anything useful. But what if some_func() does something meaningful, like say measuring boiler temperature. We can improve readability by saying so:
let boiler_temperature = some_func();
some_other_func(x);
Of course it would be better if we renamed some_func()to indicate what it does:
let boiler_temperature = boiler_temperature();
some_other_func(boiler_temperature);
But then we might start to think the boiler_temperature variable is redundant and get:
some_other_func(boiler_temperature());
That may not be possible though. What if some_func() was not in your control, a library function say?
As for rule 2) personally I find scanning and parsing long lines a lot harder work than having things laid out step by step line by line. Long lines make my visual cortex lose its horizontal hold (anyone remember what that is?). That's why books have short lines and newspapers narrow columns. But that is just me.
Bottom line is that rather than hard and fast rules these should be decided on a case by case basis. If a reviewer has a different personal preference to the author they had better put up a good case for it because there is no practical difference. Isn't that called "bikesheding".
You don't need to add that explicitly because of Goodhart's law: any rule, absolutely any, when codified as explicit goal and taken as absolute can be [ab]used to produce absolutely cringeworthy code.
I used to think that there are some exceptions… but developers are creative, and they become especially creative when they are forced to obey the rule they hate.
It depends. These two code snippets are completely equivalent:
{
let result_of_foo = foo();
bar(result_of_foo);
}
{
bar(foo());
}
In the second case, there still needs to be memory (in the stack) for storing the result_of_foo; we just haven't given it a name.
However, it is possible for using a variable to cause a value to be dropped later than it otherwise would. This can only happen when the value is used by reference, so it is not moved out of the variable by its usage.
{
let some_data = vec![0; 1000];
use_data(&some_data);
// ...lots more code that doesn't use some_data...
} // some_data is dropped here, at end of scope
{
use_data(&vec![0; 1000]);
// vec is dropped ^ HERE, at the end of the statement
// ...lots more code that doesn't use some_data...
}
In this case, it can be useful to avoid a variable — or create a {} block that narrows the scope of the variable. However:
This sort of thing only affects the peak memory usage of your program. The same amount of allocations are made; you're only changing whether or not they overlap in time. It might be completely insignificant, if this isn't the part of your program that uses the most memory.
This only matters for types which implement Drop or contain something which does.[1] For types which don't, dropping has no visible side-effects, and therefore the compiler can do it as soon as the value is no longer in use.
PHP is a very different beast. You can (jokingly) think of a compiler as a very efficient compression algorithm for your source code, variables used once won't really "exists" at the end of compilation, whitespace is ignored.
In PHP/Python/JS/etc every binding has to be materialized, interpreted and point to a dynamic object somewhere in memory, whitespace will be read one character at a time then ignored. Difficult to compare.
Sure, but "short functions" are particularly easy to abuse as a goal, especially as it's easy to convince yourself you're "making the code more self documenting" by breaking it up into three line functions, or the like.
While you could abuse a similar rule like "lines should be less than 100 characters", say by packing everything horizontally until you need to wrap, it's a lot harder to convince yourself or others that it's doing anything good.
No. A variable like the x in the OP's example perhaps only exists for the fleeting moment the function it is in is running. It is something in stack memory that disappears when the function returns.
But as I said above, perhaps it never exists in memory at all. The compiler can generate code that passes that value around in a processor register.
Or, even better, the compiler inlines all the code of the function into the caller. Then no function call or return is required.
I'm strongly on the side "less variables == better code", so I'm intrigued that the popular opinion here seems to go the other way.
Whenever this discussion comes up, I'm reminded of that scene in the Jason Bourne movies where Bourne is proud that he managed to talk himself into the flat of an old lady (as an exercise in spycraft), but then his coach scolds him because he lied his way into the flat and so if he ever were to meet this lady again Bourne might have to go to great lengths to uphold all the lies. To me, introducing variables (or any identifier including functions, for that matter) is exactly the same: variables are super easy to add, but once you've added them you are committed to maintaining them exactly as is for as long as your code lives. There are tons of ways how this can later come to bite you:
You originally started off with let file = "/path/to/file" but later change your mind and would like file to be a std::fs::File or maybe even its content as a string. So you update the definition, but now you have to very careful to update all uses correspondingly or else bad things might happen.
You want to copy-paste some section of your code to somewhere else (yes, copy-pasting is generally bad practice, but it happens, especially during early dev work). If you have introduced local variables, then you must be careful to also copy-paste all the local variables, else the copy-pasting might produce unintended results.
Admittedly, these issues are probably less serious in Rust because the compiler would usually stop you from shooting your foot, but in more dynamic languages the above can seriously hurt you. So maybe the answer to your question is not that Rust "taught" you to use local variables, but it allowed you to get away with it? In any case, I of course also fully agree with the other popular opinion here, which is that whether or not to use local variables is strongly dependent on the specific context and to some extent taste, so I'm not denying that occasionally there are benefits to introducing local variables also.
Can anyone give a concrete example for this? I can't think of anything off the top of my head...
I'm scratching my head wondering why you say that?
I don't see why variables would be cast in stone any more than statements, functions, or anything else that gets added/deleted/moved/ as code develops and is refactored.
Yup. Nice reeason to not use dynamic languages, isn't it?
Rust maximises profits from lots of local variables thus making them joy to use. If I'm forced to use dynamically-typed languages I sometimes try to avoid using as many variables precisely for the reasons you stated.
You read code much more often than write (except if you are perl user), this code should be generally optimized for reading.
That's why statically typed languages are usually better.
But yes, some code is not read often and only executed once. In that case introducing nice variable names is an obvious overkill and even dynamic languages may be useful.
I'm sorry but aren't you getting the logic backwards here? Use local variables give you advantages, and the drawbacks you listed are from using dynamic languages! How is this "Rust allowed you to get away with it"? It's more like not punishing should-be-good behavior. Unless by "it", you meant dynamic languages instead local variables, then, ofc.
In Rust, I would consider both of these advantages of using variables, because the compiler tells you when you've got it wrong; I can see how they'd be disadvantages in a dynamically checked language, since they both cause issues in the absence of a compiler that checks the entire program, not just the parts that are executed.
The first one boils down to "if I've deeply baked in the assumption that file is the pathname, then refactoring it out is hard", which is true whether or not file is a variable, or inline with the function call. However, if you've put the literal "/path/to/file" throughout your program, you now have to search for all uses of "/path/to/file" and confirm that it's still meant to be a path; if you have a variable file, you can rename it to file_path, add let file_handle = std::fs::File::open(file)?; (or let file_content = std::fs::read(file)? as appropriate), and rely on the compiler telling you about all the places that used file (which no longer exists). Once you've fixed up all uses of file to be file_path, file_handle or file_contents as appropriate, the compiler stops complaining, and you're able to use something like rust-analyzer's rename to restore the name file.
The second boils down to "if I copy-paste an incomplete chunk of code, then it won't do what it would do if it was a complete chunk". Again, though, Rust helps here, because if you've not got a variable of the right name and type, the compiler will complain, and you're in a position to go back and work out what the variable should have been.