It's kind of both. ()
is mentioned in Chapter 3.2: Data Types. (Confession: I've never actually read the Rust Book because I learned Rust before it existed.)
You can have tuples like (i32, f32, bool)
which is an i32
, an f32
, and a bool
all lumped together, and can make values of this type like so: (42, 3.14, false)
. You can think of them like anonymous struct
s, or fixed-sized arrays where each element can be a different type. You can have tuples of any length; this includes single-element tuples like (i32,)
or even a tuple with zero elements: ()
.
()
is a "unitary" type, meaning it has only one value. Due to tuple syntax, that values ends up being ()
. Also, because it contains no information, it doesn't consume any space in memory. This makes it useful for cases where you need a type or a value, but don't have anything meaningful to put there. If you have a function that doesn't return anything meaningful, it implicitly returns ()
:
// These are effectively the same:
fn returns_nil_1() { print!("Hi!") }
fn returns_nil_2() -> () { print!("Hi!") }
This is also the type of loop bodies, and of blocks without an explicit tail expression.
Correct. I was just worried that you might think you needed the contents of the { ... }
block to evaluate to the value, as opposed to the loop
as a whole.
For example, if you do this:
let result = loop { 42 };
This does not cause the loop
to unconditionally exit with the value 42
; it gives this error:
error[E0308]: mismatched types
--> src/main.rs:2:25
|
2 | let result = loop { 42 };
| ^^ expected `()`, found integer
The loop body needs to result in ()
. break
is jumping out of the loop and forcing the loop { ... }
expression as a whole to evaluate to the value you specify. You can think of it a bit like having a function that repeats forever until you explicitly return
from it.
But because break
can be anywhere in a loop body (like how return
can show up anywhere inside a function body), it doesn't need to be at the end, and it doesn't need to be in a tail expression.