I'm usually using return foo;
at the end of a function because it I find this easier to understand. But I'm not sure if this is really a good idea because the "normal" return expression is the one without a semicolon. Should I use the return without the semicolon or not (for example for better performance)?
Example:
//My typical functions
pub fn user_exist(&self, key: &str) -> Result<(), ApiError> {
let mut cache: PooledConnection<Client> = connection()?;
cache.exists(key)?;
return Ok(());
}
// Is it better to use this?
pub fn user_exist(&self, key: &str) -> Result<(), ApiError> {
let mut cache: PooledConnection<Client> = connection()?;
cache.exists(key)?;
Ok(())
}
The compiler doesn't make a distinction between the two, they are completely equivalent. Consider the following two examples:
pub fn square1(x: usize) -> usize {
x*x
}
pub fn square2(x: usize) -> usize {
return x*x;
}
Looking at the assembly in the godbolt compiler explorer, we get identical code:
example::square1:
mov rax, rdi
imul rax, rdi
ret
example::square2:
mov rax, rdi
imul rax, rdi
ret
From a style point of view, it is convention to not use return statements unless doing an early return, see this question for more details.
edit: @Chayim Friedman brought up a good point that looking at the assembly isn't hard proof they are equivalent. Although again not hard proof, looking at the MIR for these two functions gives further evidence the compiler is treating the two returns the same:
fn square1(_1: usize) -> usize {
debug x => _1; // in scope 0 at src/lib.rs:1:16: 1:17
let mut _0: usize; // return place in scope 0 at src/lib.rs:1:29: 1:34
let mut _2: usize; // in scope 0 at src/lib.rs:2:5: 2:6
let mut _3: usize; // in scope 0 at src/lib.rs:2:7: 2:8
let mut _4: (usize, bool); // in scope 0 at src/lib.rs:2:5: 2:8
bb0: {
_2 = _1; // scope 0 at src/lib.rs:2:5: 2:6
_3 = _1; // scope 0 at src/lib.rs:2:7: 2:8
_4 = CheckedMul(_2, _3); // scope 0 at src/lib.rs:2:5: 2:8
assert(!move (_4.1: bool), "attempt to compute `{} * {}`, which would overflow", move _2, move _3) -> bb1; // scope 0 at src/lib.rs:2:5: 2:8
}
bb1: {
_0 = move (_4.0: usize); // scope 0 at src/lib.rs:2:5: 2:8
return; // scope 0 at src/lib.rs:3:2: 3:2
}
}
fn square2(_1: usize) -> usize {
debug x => _1; // in scope 0 at src/lib.rs:5:16: 5:17
let mut _0: usize; // return place in scope 0 at src/lib.rs:5:29: 5:34
let mut _2: usize; // in scope 0 at src/lib.rs:6:12: 6:13
let mut _3: usize; // in scope 0 at src/lib.rs:6:14: 6:15
let mut _4: (usize, bool); // in scope 0 at src/lib.rs:6:12: 6:15
bb0: {
_2 = _1; // scope 0 at src/lib.rs:6:12: 6:13
_3 = _1; // scope 0 at src/lib.rs:6:14: 6:15
_4 = CheckedMul(_2, _3); // scope 0 at src/lib.rs:6:12: 6:15
assert(!move (_4.1: bool), "attempt to compute `{} * {}`, which would overflow", move _2, move _3) -> bb1; // scope 0 at src/lib.rs:6:12: 6:15
}
bb1: {
_0 = move (_4.0: usize); // scope 0 at src/lib.rs:6:12: 6:15
return; // scope 0 at src/lib.rs:7:2: 7:2
}
}