I am a newbie programmer, and this is my first post about my Rust programming experience. Even though I have been involved in the Rust community for a while, I am not familiar with programming in Rust. I do want to have Rust in my toolkit though, so I decided to get hands on and write some code.
As I started, I quickly learned some techniques to create more readable code. I take notes and comment my code to remember better, and help others learn from my experience. Meanwhile, it’s quite an efficient way to prepare documentation for this project in the future.
In this post, I use my toy blockchain codebase as examples. You will see the code I originally wrote and the final, improved code.
Naming Expressions
In this piece of code, I put the parameters’ values directly. I thought it was convenient because of fewer lines of code.
let new_block = Block::new("01232123", "something")?;
But it’s not obvious to readers what the parameters mean. They are just strings. Readers don’t know the purpose of the parameters.
To make this code more clear, we give each parameter a name, showing its meaning. Now the code looks like below:
let prev_hash = "000001232123";
let block_data = "something";
let new_block = Block::new(prev_hash, block_data)?;
Having every parameter on its own line with its own name
makes the intent more clear.
Now we know that the Block::new
function
requires parameters for prev_hash
and block_data
.
Pretty straightforward, isn’t it?
Variable Shadowing
Variable shadowing, or name shadowing, happens when a variable becomes inaccessible because a new variable with the same name has been defined in the same scope.
There are different opinions on name shadowing. Some think it’s confusing, some think it makes code clean to reuse the same name for intermediate values. I don’t have enough experience to judge it yet, but I am pretty happy with name shadowing in this example. Take a look at variable shadowing examples in multiple languages if you want to dig more.
In the example below, I defined each variable for one-time use (pay attention to the code comments):
impl Block {
pub fn new(prev_hash: String, block_data: String) -> Result<Block> {
let new_timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_millis();
// Define `new_hash`
let mut new_hash = Sha256::new();
new_hash.input(&serialize(
&(&block_data, &new_timestamp)
)?);
// Define `hash_result`
// for taking value of `new_hash`'s output.
let hash_result = new_hash.result_str();
// `new_hash` is no longer useful
let block = Block {
timestamp: new_timestamp,
prev_block_hash: prev_hash,
hash: hash_result,
block_data: block_data,
};
Ok(block)
}
}
Then we use the variable shadowing and the code turns out to be this:
impl Block {
pub fn new(prev_hash: String, block_data: String) -> Result<Block> {
let new_timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_millis();
// Define `new_hash`
let mut new_hash = Sha256::new();
new_hash.input(&serialize(
&(&block_data, &new_timestamp)
)?);
// Redefine `new_hash` --
// we no longer need the previous `new_hash`
let new_hash = new_hash.result_str();
let block = Block {
timestamp: new_timestamp,
prev_block_hash: prev_hash,
hash: new_hash,
block_data: block_data,
};
Ok(block)
}
}
We shadow variable new_hash
by redefining it for a new value,
with a different type.
The new new_hash
points to another memory location where
the .result_str()
output stored.
This might not be the strongest example of where to use variable shadowing. Rust book explains better in Scope and Shadowing and gives another example with variables in different scopes.
Field Init Shorthand
Rust has special struct initialization syntax that allows Using the Field Init Shorthand when Variables and Fields Have the Same Name.
We can make the previous code even better:
impl Block {
// Give parameters the same names as `Block` fields
pub fn new(prev_block_hash: String, block_data: String) -> Result<Block> {
// Define `timestamp` variable
// with the same name as `Block` field `timestamp`
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.as_millis();
// Define `hash` variable
// with the same name as `Block` field `hash`
let mut hash = Sha256::new();
hash.input(&serialize(
&(&block_data, ×tamp)
)?);
let hash = hash.result_str();
// At the same time,
// our block init can be much cleaner
let block = Block {
hash,
timestamp,
prev_block_hash,
block_data,
};
Ok(block)
}
}
We give variables and parameters the same names
as the Block
struct fields in order to avoid repetition.
Now the Block
init code looks a bit cleaner.
Doesn’t it look cool?