A Rust function with no explicit return type is equivalent to one whose return type is explicitly marked () (pronounced "unit"). This is the equivalent to void in the C++-family of languages. So
fn example() {
loop {}
}
is equivalent to
fn example() -> () {
loop {}
}
It effectively "ignores" the return value. So if we have a function bar() which returns i32, then
fn foo() -> i32 {
bar()
}
is a function returning the result of bar, while
fn foo() {
bar()
}
is a function which calls bar and then throws away its return value.
! (pronounced "never") is a bit special in Rust. A value of type ! can be assigned to a variable of any type. On the other hand, () is an ordinary type. So if we called the ! version of our example function in, say, one arm of a match statement, then it would happily coexist with whatever the return type of the match is.
match foo() {
Some(x) => 0,
None => example(),
}
example will agree with this match, since it will loop forever (or panic) and never return. But the () version of example will fail to compile, since () and i32 are not the same type.
In summary, if you can explicitly mark a function as returning !, then you should do so. A return type of ! is always better than one of (), since the former can be converted to the latter.