Syntax
Rust distinguishes items, statements, and expressions.
Control flow statements tend to require block expressions ({ }
).
Also very important are patterns, which are used for variable binding and sum type matching.
Comments are usually //
but /*..*/
is also supported.
The top level of a module may contain only items.
In particular, let
bindings are not permitted outside code blocks.
Generally, a redundant trailing ,
is allowed at the end of lists
(of values, arguments, etc.).
But ;
is very significant and is usually either required, or forbidden.
Attributes
Rust code is frequently littered with #[attributes]
.
These are placed before the item or expression they apply to.
The semantics are very varied.
New attributes can be defined as procedural macros in libraries.
Notable is #[derive(...)]
which invokes a macro
to autogenerate code based on a data structure type.
Many Rust libraries provide extremely useful derive macros
for structs and enums.
The syntax #![attribute]
applies the attribute to
the thing the attribute is placed inside.
Typically it is found only at the top of a whole module or crate.
Attributes are used for many important purposes:
- Conditional compilation
#[cfg(..)]
; - Denoting functions whose value should be checked
(and types which should not be simply discarded):
#[must_use]
; - Suppressing warnings locally
#[allow(dead_code)]
or for a whole crate (at the toplevel)#![allow(dead_code)]
; - Enabling unstable features on Nightly
#![feature(exit_status_error)]
; - Marking functions as tests
#[test]
; - Request (hint) inlining
#[inline]
. - Control a type's memory layout
#[repr(...)]
. - Specify where to find the source for a module
#[path="foo.rs"] mod bar;
.
Items
fn function(arg0: T, arg1: U) -> ReturnValue { ... }
| |
type TypeAlias = OtherType; | Type alias, structural equality |
pub struct Counter { counter: u64 } | Nominal type equality |
trait Trait { fn trait_method(self); }
| |
const FORTY_TWO: u32 = 42;
| |
static TABLE: [u8; 256] = { 0x32, 0x26, 0o11, entries... };
| |
impl Type { ... }
| |
impl Trait for Type { ... }
| |
mod some_module; |
Causes some_module.rs to be read
|
mod some_module { ... } | Module source is right here |
Rust really hates mutable globals. See under Safety.
Expressions
Most of the usual infix and assignment operators are available. Control flow "statements" are generally expressions:
{ stmt0; stmt1; } |
With semicolon, has type ()
|
{ stmt0; stmt1 } |
No semicolon, has type of stmt1
|
if condition { statements... } |
Can only have type ()
|
if condition { value } else { other_value } |
No ? : , use this
|
if let pattern = value { ... } [else ...] | Pattern binding condition |
match value { pat0 [ if cond0 ] => expr0, ... } | See Types and Patterns |
'label: loop { ... } |
'label: is optional of course
|
'label: while condition { }
| |
'label: while let pattern = expr { }
| |
'label: for loopvar in something_iterable { ... }
|
return v |
At end of function, it is idiomatic to just write v
|
break value; |
loop only; specifies value of loop expr
|
break 'label value; |
break value with named loop; Rust 1.65, Nov 2022
|
continue; continue 'label; break; break 'label;
|
function(arg0,arg1)
| |
receiver.method(arg0,arg1,arg2) | See on Methods |
|arg0, arg1, ...| expression | Closures |
|arg0: Type0, arg1: Type1, ...| -> ReturnType expression
|
fallible? | See in Error handling |
*value |
Deref , see in Traits, methods
|
value as OtherType | Type conversion (safe but maybe lossy, see in Safety) |
Counter { counter: 42 } | Constructor ("struct literal") |
collection[index] | Usually panics if not found, eg array bounds check |
thing.field | Field of a struct with named fields |
tuple.0; tuple.1; | Fields of tuple or tuple struct |
start..end; start..=end |
End-exclusive and -inclusive Range
|
Note the odd semicolon rule, which determines the type of block expressions.
Missing return type on a fn
item means ()
;
missing return type on a closure means _
;
Other statements
let
introduces a binding.
let pattern = value; | Irrefutable patterns |
let pattern = value else { diverges... }; | Refutable (Rust 1.65, Nov 2022) |
place = value; | Assignment to a mutable variable or location |
pattern = value; | Destructuring assignment (Rust 1.59, Feb 2022) |
Variable names may be reused by rebinding; this is often considered idiomatic.
In a block, you can define any other kind of item, which will have local scope.
Identifiers and scopes
Names are
paths
like scope::scope::ident
.
Here scope
can be a module, type or trait,
or an external library ("crate"),
or special values like crate
, self
, super
.
Each Rust module
(file, or mod { }
within a file)
has its own namespace.
Other names are imported using use path::to::thing;
.
use
can also
refer to other crates (i.e. your Cargo.toml
dependencies).
Items can be renamed during import using as
.
Rust has strong conventions about identifier case and spelling, which the compiler will warn you about violating:
snake_case
: Variables, functions and modules.StudlyCaps
: Types (including enum variant names and traits).SCREAMING_SNAKE_CASE
: Constants and global variables.
-
is not valid in identifier names in Rust source code.
In other places in the Rust world,
you may see names in kebab-case
.
Many items (including functions, types, fields of product types, etc.)
can be public (pub
) or private to the module (the default),
or have more subtle visibility.
_
can often be written when an identifier is expected.
For a type or lifetime, it asks the compiler to infer.
For a binding, it ignores the value.