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.