A half-hour to learn Rust (original) (raw)
Jan 27, 2020 27 min #rust
👋 This page was last updated ~5 years ago. Just so you know.
NOW RECRUITING
bearcove is recruiting a video editor for fasterthanlime.
Applicants need:
- a mac
- good english
- great internet
- willingness to learn a specific DaVinci Resolve-based workflow
Is this you? Or do you know someone who is? check out the complete job listing here
In order to increase fluency in a programming language, one has to read a lot of it.
But how can you read a lot of it if you don’t know what it means?
In this article, instead of focusing on one or two concepts, I’ll try to go through as many Rust snippets as I can, and explain what the keywords and symbols they contain mean.
Ready? Go!
Variable bindings The let keyword
let
introduces a variable binding:
_let_ x_;_ _// declare "x"_x = _42_ _;_ _// assign 42 to "x"_
This can also be written as a single line:
_let_ x = _42_ _;_
You can specify the variable’s type explicitly with :
, that’s a type annotation:
_let_ x_:_ _i32_ _;_ _// `i32` is a signed 32-bit integer_x = _42_ _;_ _// there's i8, i16, i32, i64, i128_ _// also u8, u16, u32, u64, u128 for unsigned_
This can also be written as a single line:
_let_ x_:_ _i32_ = _42_ _;_
If you declare a name and initialize it later, the compiler will prevent you from using it before it’s initialized.
_let_ x_;_ _foobar_ _(_x_)_ _;_ _// error: borrow of possibly-uninitialized variable: `x`_x = _42_ _;_
However, doing this is completely fine:
_let_ x_;_x = _42_ _;_ _foobar_ _(_x_)_ _;_ _// the type of `x` will be inferred from here_
The underscore _
is a special name - or rather, a “lack of name”. It basically means to throw away something:
_// this does *nothing* because 42 is a constant_ _let_ _ = _42_ _;_ _// this calls `getthing` but throws away its result_ _let_ _ = _getthing_ _(_ _)_ _;_
Names that start with an underscore are regular names, it’s just that the compiler won’t warn about them being unused:
_// we may use `x` eventually, but our code is a work-in-progress_ _// and we just wanted to get rid of a compiler warning for now._ _let_ _x = _42_ _;_
Separate bindings with the same name can be introduced - you can _shadow_a variable binding:
_let_ x = _13_ _;_ _let_ x = x + _3_ _;_ _// using `x` after that line only refers to the second `x`,_ _//_ _// although the first `x` still exists (it'll be dropped_ _// when going out of scope), you can no longer refer to it._
Rust has tuples, which you can think of as “fixed-length collections of values of different types”.
_let_ pair = _(_ _'a'_ _,_ _17_ _)_ _;_pair_._ _0_ _;_ _// this is 'a'_pair_._ _1_ _;_ _// this is 17_
If we really wanted to annotate the type of pair
, we would write:
_let_ pair_:_ _(_ _char_ _,_ _i32_ _)_ = _(_ _'a'_ _,_ _17_ _)_ _;_
Tuples can be destructured when doing an assignment, which means they’re broken down into their individual fields:
_let_ _(_some_char_,_ some_int_)_ = _(_ _'a'_ _,_ _17_ _)_ _;_ _// now, `somechar` is 'a', and `someint` is 17_
This is especially useful when a function returns a tuple:
_let_ _(_left_,_ right_)_ = slice_._ _splitat_ _(_middle_)_ _;_
Of course, when destructuring a tuple, _
can be used to throw away part of it:
_let_ _(__ _,_ right_)_ = slice_._ _splitat_ _(_middle_)_ _;_
The semi-colon marks the end of a statement:
_let_ x = _3_ _;_ _let_ y = _5_ _;_ _let_ z = y + x_;_
Which means statements can span multiple lines:
_let_ x = _vec_ _!_ _[_ _1_ _,_ _2_ _,_ _3_ _,_ _4_ _,_ _5_ _,_ _6_ _,_ _7_ _,_ _8_ _]_ _._ _iter_ _(_ _)_ _._ _map_ _(_|x| x + _3_ _)_ _._ _fold_ _(_ _0_ _,_ |x_,_ y| x + y_)_ _;_
(We’ll go over what those actually mean later).
fn
declares a function.
Here’s a void function:
_fn_ _greet_ _(_ _)_ _{_ _println_ _!_ _(_ _"Hi there!"_ _)_ _;_ _}_
And here’s a function that returns a 32-bit signed integer. The arrow indicates its return type:
_fn_ _fairdiceroll_ _(_ _)_ -> _i32_ _{_ _4_ _}_
A pair of brackets declares a block, which has its own scope:
_// This prints "in", then "out"_ _fn_ _main_ _(_ _)_ _{_ _let_ x = _"out"_ _;_ _{_ _// this is a different `x`_ _let_ x = _"in"_ _;_ _println_ _!_ _(_ _"{}"_ _,_ x_)_ _;_ _}_ _println_ _!_ _(_ _"{}"_ _,_ x_)_ _;_ _}_
Blocks are also expressions, which mean they evaluate to a value.
_// this:_ _let_ x = _42_ _;_ _// is equivalent to this:_ _let_ x = _{_ _42_ _}_ _;_
Inside a block, there can be multiple statements:
_let_ x = _{_ _let_ y = _1_ _;_ _// first statement_ _let_ z = _2_ _;_ _// second statement_ y + z _// this is the *tail* - what the whole block will evaluate to_ _}_ _;_
And that’s why “omitting the semicolon at the end of a function” is the same as returning, ie. these are equivalent:
_fn_ _fairdiceroll_ _(_ _)_ -> _i32_ _{_ _return_ _4_ _;_ _}_ _fn_ _fairdiceroll_ _(_ _)_ -> _i32_ _{_ _4_ _}_
if
conditionals are also expressions:
_fn_ _fairdiceroll_ _(_ _)_ -> _i32_ _{_ _if_ feeling_lucky _{_ _6_ _}_ _else_ _{_ _4_ _}_ _}_
A match
is also an expression:
_fn_ _fairdiceroll_ _(_ _)_ -> _i32_ _{_ _match_ feeling_lucky _{_ _true_ => _6_ _,_ _false_ => _4_ _,_ _}_ _}_
Field access and method calling
Dots are typically used to access fields of a value:
_let_ a = _(_ _10_ _,_ _20_ _)_ _;_a_._ _0_ _;_ _// this is 10_ _let_ amos = _getsomestruct_ _(_ _)_ _;_amos_._ _nickname_ _;_ _// this is "fasterthanlime"_
Or call a method on a value:
_let_ nick = _"fasterthanlime"_ _;_nick_._ _len_ _(_ _)_ _;_ _// this is 14_
The double-colon, ::
, is similar but it operates on namespaces.
In this example, std
is a crate (~ a library), cmp
is a module(~ a source file), and min
is a function:
_let_ least = std_::_cmp_::_ _min_ _(_ _3_ _,_ _8_ _)_ _;_ _// this is 3_
use
directives can be used to “bring in scope” names from other namespace:
_use_ std_::_cmp_::_min_;_ _let_ least = _min_ _(_ _7_ _,_ _1_ _)_ _;_ _// this is 1_
Within use
directives, curly brackets have another meaning: they’re “globs”. If we want to import both min
and max
, we can do any of these:
_// this works:_ _use_ std_::_cmp_::_min_;_ _use_ std_::_cmp_::_max_;_ _// this also works:_ _use_ std_::_cmp_::_ _{_min_,_ max_}_ _;_ _// this also works!_ _use_ std_::_ _{_cmp_::_min_,_ cmp_::_max_}_ _;_
A wildcard (*
) lets you import every symbol from a namespace:
_// this brings `min` and `max` in scope, and many other things_ _use_ std_::_cmp_::_ _*_ _;_
Types are namespaces too, and methods can be called as regular functions:
_let_ x = _"amos"_ _._ _len_ _(_ _)_ _;_ _// this is 4_ _let_ x = str_::_ _len_ _(_ _"amos"_ _)_ _;_ _// this is also 4_
str
is a primitive type, but many non-primitive types are also in scope by default.
_// `Vec` is a regular struct, not a primitive type_ _let_ v = _Vec_ _::_ _new_ _(_ _)_ _;_ _// this is exactly the same code, but with the *full* path to `Vec`_ _let_ v = std_::_vec_::_ _Vec_ _::_ _new_ _(_ _)_ _;_
This works because Rust inserts this at the beginning of every module:
_use_ std_::_prelude_::_v1_::_ _*_ _;_
(Which in turns re-exports a lot of symbols, like Vec
, String
, Option
and Result
).
Structs are declared with the struct
keyword:
_struct_ _Vec2_ _{_ _x_ _:_ _f64_ _,_ _// 64-bit floating point, aka "double precision"_ _y_ _:_ _f64_ _,_ _}_
They can be initialized using struct literals:
_let_ v1 = _Vec2_ _{_ _x_ _:_ _1.0_ _,_ _y_ _:_ _3.0_ _}_ _;_ _let_ v2 = _Vec2_ _{_ _y_ _:_ _2.0_ _,_ _x_ _:_ _4.0_ _}_ _;_ _// the order does not matter, only the names do_
There is a shortcut for initializing the rest of the fields from another struct:
_let_ v3 = _Vec2_ _{_ _x_ _:_ _14.0_ _,_ ..v2_}_ _;_
This is called “struct update syntax”, can only happen in last position, and cannot be followed by a comma.
Note that the rest of the fields can mean all the fields:
_let_ v4 = _Vec2_ _{_ ..v3 _}_ _;_
Structs, like tuples, can be destructured.
Just like this is a valid let
pattern:
_let_ _(_left_,_ right_)_ = slice_._ _splitat_ _(_middle_)_ _;_
So is this:
_let_ v = _Vec2_ _{_ _x_ _:_ _3.0_ _,_ _y_ _:_ _6.0_ _}_ _;_ _let_ _Vec2_ _{_ x_,_ y _}_ = v_;_ _// `x` is now 3.0, `y` is now `6.0`_
And this:
_let_ _Vec2_ _{_ x_,_ .. _}_ = v_;_ _// this throws away `v.y`_
Patterns and destructuring Destructuring with if let
let
patterns can be used as conditions in if
:
_struct_ _Number_ _{_ _odd_ _:_ _bool_ _,_ _value_ _:_ _i32_ _,_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ one = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _1_ _}_ _;_ _let_ two = _Number_ _{_ _odd_ _:_ _false_ _,_ _value_ _:_ _2_ _}_ _;_ _printnumber_ _(_one_)_ _;_ _printnumber_ _(_two_)_ _;_ _}_ _fn_ _printnumber_ _(_ _n_ _:_ _Number_ _)_ _{_ _if_ _let_ _Number_ _{_ _odd_ _:_ _true_ _,_ value _}_ = n _{_ _println_ _!_ _(_ _"Odd number: {}"_ _,_ value_)_ _;_ _}_ _else_ _if_ _let_ _Number_ _{_ _odd_ _:_ _false_ _,_ value _}_ = n _{_ _println_ _!_ _(_ _"Even number: {}"_ _,_ value_)_ _;_ _}_ _}_ _// this prints:_ _// Odd number: 1_ _// Even number: 2_
match
arms are also patterns, just like if let
:
_fn_ _printnumber_ _(_ _n_ _:_ _Number_ _)_ _{_ _match_ n _{_ _Number_ _{_ _odd_ _:_ _true_ _,_ value _}_ => _println_ _!_ _(_ _"Odd number: {}"_ _,_ value_)_ _,_ _Number_ _{_ _odd_ _:_ _false_ _,_ value _}_ => _println_ _!_ _(_ _"Even number: {}"_ _,_ value_)_ _,_ _}_ _}_ _// this prints the same as before_
A match
has to be exhaustive: at least one arm needs to match.
_fn_ _printnumber_ _(_ _n_ _:_ _Number_ _)_ _{_ _match_ n _{_ _Number_ _{_ _value_ _:_ _1_ _,_ .. _}_ => _println_ _!_ _(_ _"One"_ _)_ _,_ _Number_ _{_ _value_ _:_ _2_ _,_ .. _}_ => _println_ _!_ _(_ _"Two"_ _)_ _,_ _Number_ _{_ value_,_ .. _}_ => _println_ _!_ _(_ _"{}"_ _,_ value_)_ _,_ _// if that last arm didn't exist, we would get a compile-time error_ _}_ _}_
If that’s hard, _
can be used as a “catch-all” pattern:
_fn_ _printnumber_ _(_ _n_ _:_ _Number_ _)_ _{_ _match_ n_._ _value_ _{_ _1_ => _println_ _!_ _(_ _"One"_ _)_ _,_ _2_ => _println_ _!_ _(_ _"Two"_ _)_ _,_ _ => _println_ _!_ _(_ _"{}"_ _,_ n_._ _value_ _)_ _,_ _}_ _}_
You can declare methods on your own types:
_struct_ _Number_ _{_ _odd_ _:_ _bool_ _,_ _value_ _:_ _i32_ _,_ _}_ _impl_ _Number_ _{_ _fn_ _isstrictlypositive_ _(_ _self_ _)_ -> _bool_ _{_ _self_ _._ _value_ > _0_ _}_ _}_
And use them like usual:
_fn_ _main_ _(_ _)_ _{_ _let_ minus_two = _Number_ _{_ _odd_ _:_ _false_ _,_ _value_ _:_ -_2_ _,_ _}_ _;_ _println_ _!_ _(_ _"positive? {}"_ _,_ minus_two_._ _isstrictlypositive_ _(_ _)_ _)_ _;_ _// this prints "positive? false"_ _}_
Variable bindings are immutable by default, which means their interior can’t be mutated:
_fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _17_ _,_ _}_ _;_ n_._ _odd_ = _false_ _;_ _// error: cannot assign to `n.odd`,_ _// as `n` is not declared to be mutable_ _}_
And also that they cannot be assigned to:
_fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _17_ _,_ _}_ _;_ n = _Number_ _{_ _odd_ _:_ _false_ _,_ _value_ _:_ _22_ _,_ _}_ _;_ _// error: cannot assign twice to immutable variable `n`_ _}_
mut
makes a variable binding mutable:
_fn_ _main_ _(_ _)_ _{_ _let_ _mut_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _17_ _,_ _}_ n_._ _value_ = _19_ _;_ _// all good_ _}_
Traits are something multiple types can have in common:
_trait_ _Signed_ _{_ _fn_ _isstrictlynegative_ _(_ _self_ _)_ -> _bool_ _;_ _}_
You can implement:
- one of your traits on anyone’s type
- anyone’s trait on one of your types
- but not a foreign trait on a foreign type
These are called the “orphan rules”.
Here’s an implementation of our trait on our type:
_impl_ _Signed_ _for_ _Number_ _{_ _fn_ _isstrictlynegative_ _(_ _self_ _)_ -> _bool_ _{_ _self_ _._ _value_ < _0_ _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _false_ _,_ _value_ _:_ -_44_ _}_ _;_ _println_ _!_ _(_ _"{}"_ _,_ n_._ _isstrictlynegative_ _(_ _)_ _)_ _;_ _// prints "true"_ _}_
Our trait on a foreign type (a primitive type, even):
_impl_ _Signed_ _for_ _i32_ _{_ _fn_ _isstrictlynegative_ _(_ _self_ _)_ -> _bool_ _{_ _self_ < _0_ _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ n_:_ _i32_ = -_44_ _;_ _println_ _!_ _(_ _"{}"_ _,_ n_._ _isstrictlynegative_ _(_ _)_ _)_ _;_ _// prints "true"_ _}_
A foreign trait on our type:
_// the `Neg` trait is used to overload `-`, the_ _// unary minus operator._ _impl_ std_::_ops_::_ _Neg_ _for_ _Number_ _{_ _type_ _Output_ = _Number_ _;_ _fn_ _neg_ _(_ _self_ _)_ -> _Number_ _{_ _Number_ _{_ _value_ _:_ -_self_ _._ _value_ _,_ _odd_ _:_ _self_ _._ _odd_ _,_ _}_ _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _987_ _}_ _;_ _let_ m = -n_;_ _// this is only possible because we implemented `Neg`_ _println_ _!_ _(_ _"{}"_ _,_ m_._ _value_ _)_ _;_ _// prints "-987"_ _}_
An impl
block is always for a type, so, inside that block, Self
means that type:
_impl_ std_::_ops_::_ _Neg_ _for_ _Number_ _{_ _type_ _Output_ = _Self_ _;_ _fn_ _neg_ _(_ _self_ _)_ -> _Self_ _{_ _Self_ _{_ _value_ _:_ -_self_ _._ _value_ _,_ _odd_ _:_ _self_ _._ _odd_ _,_ _}_ _}_ _}_
Some traits are markers - they don’t say that a type implements some methods, they say that certain things can be done with a type.
For example, i32
implements trait Copy
(in short, i32
is Copy
), so this works:
_fn_ _main_ _(_ _)_ _{_ _let_ a_:_ _i32_ = _15_ _;_ _let_ b = a_;_ _// `a` is copied_ _let_ c = a_;_ _// `a` is copied again_ _}_
And this also works:
_fn_ _printi32_ _(_ _x_ _:_ _i32_ _)_ _{_ _println_ _!_ _(_ _"x = {}"_ _,_ x_)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ a_:_ _i32_ = _15_ _;_ _printi32_ _(_a_)_ _;_ _// `a` is copied_ _printi32_ _(_a_)_ _;_ _// `a` is copied again_ _}_
But the Number
struct is not Copy
, so this doesn’t work:
_fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _51_ _}_ _;_ _let_ m = n_;_ _// `n` is moved into `m`_ _let_ o = n_;_ _// error: use of moved value: `n`_ _}_
And neither does this:
_fn_ _printnumber_ _(_ _n_ _:_ _Number_ _)_ _{_ _println_ _!_ _(_ _"{} number {}"_ _,_ _if_ n_._ _odd_ _{_ _"odd"_ _}_ _else_ _{_ _"even"_ _}_ _,_ n_._ _value_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _51_ _}_ _;_ _printnumber_ _(_n_)_ _;_ _// `n` is moved_ _printnumber_ _(_n_)_ _;_ _// error: use of moved value: `n`_ _}_
But it works if print_number
takes an immutable reference instead:
_fn_ _printnumber_ _(_ _n_ _:_ _&_ _Number_ _)_ _{_ _println_ _!_ _(_ _"{} number {}"_ _,_ _if_ n_._ _odd_ _{_ _"odd"_ _}_ _else_ _{_ _"even"_ _}_ _,_ n_._ _value_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _51_ _}_ _;_ _printnumber_ _(_ _&_n_)_ _;_ _// `n` is borrowed for the time of the call_ _printnumber_ _(_ _&_n_)_ _;_ _// `n` is borrowed again_ _}_
It also works if a function takes a mutable reference - but only if our variable binding is also mut
.
_fn_ _invert_ _(_ _n_ _:_ _&_ _mut_ _Number_ _)_ _{_ n_._ _value_ = -n_._ _value_ _;_ _}_ _fn_ _printnumber_ _(_ _n_ _:_ _&_ _Number_ _)_ _{_ _println_ _!_ _(_ _"{} number {}"_ _,_ _if_ n_._ _odd_ _{_ _"odd"_ _}_ _else_ _{_ _"even"_ _}_ _,_ n_._ _value_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _// this time, `n` is mutable_ _let_ _mut_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _51_ _}_ _;_ _printnumber_ _(_ _&_n_)_ _;_ _invert_ _(_ _&_ _mut_ n_)_ _;_ _// `n is borrowed mutably - everything is explicit_ _printnumber_ _(_ _&_n_)_ _;_ _}_
Trait methods can also take self
by reference or mutable reference:
_impl_ std_::_clone_::_ _Clone_ _for_ _Number_ _{_ _fn_ _clone_ _(_ _&_ _self_ _)_ -> _Self_ _{_ _Self_ _{_ .._*_ _self_ _}_ _}_ _}_
When invoking trait methods, the receiver is borrowed implicitly:
_fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _51_ _}_ _;_ _let_ _mut_ m = n_._ _clone_ _(_ _)_ _;_ m_._ _value_ += _100_ _;_ _printnumber_ _(_ _&_n_)_ _;_ _printnumber_ _(_ _&_m_)_ _;_ _}_
To highlight this: these are equivalent:
_let_ m = n_._ _clone_ _(_ _)_ _;_ _let_ m = std_::_clone_::_ _Clone_ _::_ _clone_ _(_ _&_n_)_ _;_
Marker traits like Copy
have no methods:
_// note: `Copy` requires that `Clone` is implemented too_ _impl_ std_::_clone_::_ _Clone_ _for_ _Number_ _{_ _fn_ _clone_ _(_ _&_ _self_ _)_ -> _Self_ _{_ _Self_ _{_ .._*_ _self_ _}_ _}_ _}_ _impl_ std_::_marker_::_ _Copy_ _for_ _Number_ _{_ _}_
Now, Clone
can still be used:
_fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _51_ _}_ _;_ _let_ m = n_._ _clone_ _(_ _)_ _;_ _let_ o = n_._ _clone_ _(_ _)_ _;_ _}_
But Number
values will no longer be moved:
_fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _odd_ _:_ _true_ _,_ _value_ _:_ _51_ _}_ _;_ _let_ m = n_;_ _// `m` is a copy of `n`_ _let_ o = n_;_ _// same. `n` is neither moved nor borrowed._ _}_
Some traits are so common, they can be implemented automatically by using the derive
attribute:
_#[derive(Clone, Copy) ]_ _struct_ _Number_ _{_ _odd_ _:_ _bool_ _,_ _value_ _:_ _i32_ _,_ _}_ _// this expands to `impl Clone for Number` and `impl Copy for Number` blocks._
Functions can be generic:
_fn_ _foobar_ _<_ _T_ _>_ _(_ _arg_ _:_ _T_ _)_ _{_ _// do something with `arg`_ _}_
They can have multiple type parameters, which can then be used in the function’s declaration and its body, instead of concrete types:
_fn_ _foobar_ _<_ _L_ _,_ _R_ _>_ _(_ _left_ _:_ _L_ _,_ _right_ _:_ _R_ _)_ _{_ _// do something with `left` and `right`_ _}_
Type parameter constraints (trait bounds)
Type parameters usually have constraints, so you can actually do something with them.
The simplest constraints are just trait names:
_fn_ _print_ _<_ _T_ _:_ _Display_ _>_ _(_ _value_ _:_ _T_ _)_ _{_ _println_ _!_ _(_ _"value = {}"_ _,_ value_)_ _;_ _}_ _fn_ _print_ _<_ _T_ _:_ _Debug_ _>_ _(_ _value_ _:_ _T_ _)_ _{_ _println_ _!_ _(_ _"value = {:?}"_ _,_ value_)_ _;_ _}_
There’s a longer syntax for type parameter constraints:
_fn_ _print_ _<_ _T_ _>_ _(_ _value_ _:_ _T_ _)_ _where_ _T_ _:_ _Display_ _,_ _{_ _println_ _!_ _(_ _"value = {}"_ _,_ value_)_ _;_ _}_
Constraints can be more complicated: they can require a type parameter to implement multiple traits:
_use_ std_::_fmt_::_Debug_;_ _fn_ _compare_ _<_ _T_ _>_ _(_ _left_ _:_ _T_ _,_ _right_ _:_ _T_ _)_ _where_ _T_ _:_ _Debug_ + _PartialEq_ _,_ _{_ _println_ _!_ _(_ _"{:?} {} {:?}"_ _,_ left_,_ _if_ left == right _{_ _"=="_ _}_ _else_ _{_ _"!="_ _}_ _,_ right_)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _compare_ _(_ _"tea"_ _,_ _"coffee"_ _)_ _;_ _// prints: "tea" != "coffee"_ _}_
Generic functions can be thought of as namespaces, containing an infinity of functions with different concrete types.
Same as with crates, and modules, and types, generic functions can be “explored” (navigated?) using ::
_fn_ _main_ _(_ _)_ _{_ _use_ std_::_any_::_type_name_;_ _println_ _!_ _(_ _"{}"_ _,_ _typename_ _::_ _<_ _i32_ _>_ _(_ _)_ _)_ _;_ _// prints "i32"_ _println_ _!_ _(_ _"{}"_ _,_ _typename_ _::_ _<_ _(_ _f64_ _,_ _char_ _)_ _>_ _(_ _)_ _)_ _;_ _// prints "(f64, char)"_ _}_
This is lovingly called turbofish syntax, because::<>
looks like a fish.
Structs can be generic too:
_struct_ _Pair_ _<_ _T_ _>_ _{_ _a_ _:_ _T_ _,_ _b_ _:_ _T_ _,_ _}_ _fn_ _printtypename_ _<_ _T_ _>_ _(_ __val_ _:_ _&_ _T_ _)_ _{_ _println_ _!_ _(_ _"{}"_ _,_ std_::_any_::_ _typename_ _::_ _<_ _T_ _>_ _(_ _)_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ p1 = _Pair_ _{_ _a_ _:_ _3_ _,_ _b_ _:_ _9_ _}_ _;_ _let_ p2 = _Pair_ _{_ _a_ _:_ _true_ _,_ _b_ _:_ _false_ _}_ _;_ _printtypename_ _(_ _&_p1_)_ _;_ _// prints "Pair<i32>"_ _printtypename_ _(_ _&_p2_)_ _;_ _// prints "Pair<bool>"_ _}_
The standard library type Vec
(~ a heap-allocated array), is generic:
_fn_ _main_ _(_ _)_ _{_ _let_ _mut_ v1 = _Vec_ _::_ _new_ _(_ _)_ _;_ v1_._ _push_ _(_ _1_ _)_ _;_ _let_ _mut_ v2 = _Vec_ _::_ _new_ _(_ _)_ _;_ v2_._ _push_ _(_ _false_ _)_ _;_ _printtypename_ _(_ _&_v1_)_ _;_ _// prints "Vec<i32>"_ _printtypename_ _(_ _&_v2_)_ _;_ _// prints "Vec<bool>"_ _}_
Speaking of Vec
, it comes with a macro that gives more or less “vec literals”:
_fn_ _main_ _(_ _)_ _{_ _let_ v1 = _vec_ _!_ _[_ _1_ _,_ _2_ _,_ _3_ _]_ _;_ _let_ v2 = _vec_ _!_ _[_ _true_ _,_ _false_ _,_ _true_ _]_ _;_ _printtypename_ _(_ _&_v1_)_ _;_ _// prints "Vec<i32>"_ _printtypename_ _(_ _&_v2_)_ _;_ _// prints "Vec<bool>"_ _}_
All of name!()
, name![]
or name!{}
invoke a macro. Macros just expand to regular code.
In fact, println
is a macro:
_fn_ _main_ _(_ _)_ _{_ _println_ _!_ _(_ _"{}"_ _,_ _"Hello there!"_ _)_ _;_ _}_
This expands to something that has the same effect as:
_fn_ _main_ _(_ _)_ _{_ _use_ std_::_io_::_ _{_ _self_ _,_ Write_}_ _;_ io_::_ _stdout_ _(_ _)_ _._ _lock_ _(_ _)_ _._ _writeall_ _(_ _b"Hello there!\n"_ _)_ _._ _unwrap_ _(_ _)_ _;_ _}_
panic
is also a macro. It violently stops execution with an error message, and the file name / line number of the error, if enabled:
_fn_ _main_ _(_ _)_ _{_ _panic_ _!_ _(_ _"This panics"_ _)_ _;_ _}_ _// output: thread 'main' panicked at 'This panics', src/main.rs:3:5_
Some methods also panic. For example, the Option
type can contain something, or it can contain nothing. If .unwrap()
is called on it, and it contains nothing, it panics:
_fn_ _main_ _(_ _)_ _{_ _let_ o1_:_ _Option_ _<_ _i32_ _>_ = _Some_ _(_ _128_ _)_ _;_ o1_._ _unwrap_ _(_ _)_ _;_ _// this is fine_ _let_ o2_:_ _Option_ _<_ _i32_ _>_ = None_;_ o2_._ _unwrap_ _(_ _)_ _;_ _// this panics!_ _}_ _// output: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:378:21_
Option
is not a struct - it’s an enum
, with two variants.
_enum_ _Option_ _<_ _T_ _>_ _{_ None_,_ Some_(_ _T_ _)_ _,_ _}_ _impl_ _<_ _T_ _>_ _Option_ _<_ _T_ _>_ _{_ _fn_ _unwrap_ _(_ _self_ _)_ -> _T_ _{_ _// enums variants can be used in patterns:_ _match_ _self_ _{_ _Self_ _::_Some_(_t_)_ => t_,_ _Self_ _::_None => _panic_ _!_ _(_ _".unwrap() called on a None option"_ _)_ _,_ _}_ _}_ _}_ _use_ _self_ _::_Option_::_ _{_None_,_ Some_}_ _;_ _fn_ _main_ _(_ _)_ _{_ _let_ o1_:_ _Option_ _<_ _i32_ _>_ = _Some_ _(_ _128_ _)_ _;_ o1_._ _unwrap_ _(_ _)_ _;_ _// this is fine_ _let_ o2_:_ _Option_ _<_ _i32_ _>_ = None_;_ o2_._ _unwrap_ _(_ _)_ _;_ _// this panics!_ _}_ _// output: thread 'main' panicked at '.unwrap() called on a None option', src/main.rs:11:27_
Result
is also an enum, it can either contain something, or an error:
_enum_ _Result_ _<_ _T_ _,_ _E_ _>_ _{_ Ok_(_ _T_ _)_ _,_ Err_(_ _E_ _)_ _,_ _}_
It also panics when unwrapped and containing an error.
Variables bindings have a “lifetime”:
_fn_ _main_ _(_ _)_ _{_ _// `x` doesn't exist yet_ _{_ _let_ x = _42_ _;_ _// `x` starts existing_ _println_ _!_ _(_ _"x = {}"_ _,_ x_)_ _;_ _// `x` stops existing_ _}_ _// `x` no longer exists_ _}_
Similarly, references have a lifetime:
_fn_ _main_ _(_ _)_ _{_ _// `x` doesn't exist yet_ _{_ _let_ x = _42_ _;_ _// `x` starts existing_ _let_ x_ref = _&_x_;_ _// `xref` starts existing - it borrows `x`_ _println_ _!_ _(_ _"xref = {}"_ _,_ x_ref_)_ _;_ _// `xref` stops existing_ _// `x` stops existing_ _}_ _// `x` no longer exists_ _}_
The lifetime of a reference cannot exceed the lifetime of the variable binding it borrows:
_fn_ _main_ _(_ _)_ _{_ _let_ x_ref = _{_ _let_ x = _42_ _;_ _&_x_}_ _;_ _println_ _!_ _(_ _"xref = {}"_ _,_ x_ref_)_ _;_ _// error: `x` does not live long enough_ _}_
Borrowing rules (one or more immutable borrows XOR one mutable borrow)
A variable binding can be immutably borrowed multiple times:
_fn_ _main_ _(_ _)_ _{_ _let_ x = _42_ _;_ _let_ x_ref1 = _&_x_;_ _let_ x_ref2 = _&_x_;_ _let_ x_ref3 = _&_x_;_ _println_ _!_ _(_ _"{} {} {}"_ _,_ x_ref1_,_ x_ref2_,_ x_ref3_)_ _;_ _}_
While borrowed, a variable binding cannot be mutated:
_fn_ _main_ _(_ _)_ _{_ _let_ _mut_ x = _42_ _;_ _let_ x_ref = _&_x_;_ x = _13_ _;_ _println_ _!_ _(_ _"xref = {}"_ _,_ x_ref_)_ _;_ _// error: cannot assign to `x` because it is borrowed_ _}_
While immutably borrowed, a variable cannot be mutably borrowed:
_fn_ _main_ _(_ _)_ _{_ _let_ _mut_ x = _42_ _;_ _let_ x_ref1 = _&_x_;_ _let_ x_ref2 = _&_ _mut_ x_;_ _// error: cannot borrow `x` as mutable because it is also borrowed as immutable_ _println_ _!_ _(_ _"xref1 = {}"_ _,_ x_ref1_)_ _;_ _}_
Functions generic over lifetimes
References in function arguments also have lifetimes:
_fn_ _print_ _(_ _x_ _:_ _&_ _i32_ _)_ _{_ _// `x` is borrowed (from the outside) for the_ _// entire time this function is called._ _}_
Functions with reference arguments can be called with borrows that have different lifetimes, so:
- All functions that take references are generic
- Lifetimes are generic parameters
Lifetimes’ names start with a single quote, '
:
_// elided (non-named) lifetimes:_ _fn_ _print_ _(_ _x_ _:_ _&_ _i32_ _)_ _{_ _}_ _// named lifetimes:_ _fn_ _print_ _<_ _'_ _a_ _>_ _(_ _x_ _:_ _&_ _'_ _a_ _i32_ _)_ _{_ _}_
This allows returning references whose lifetime depend on the lifetime of the arguments:
_struct_ _Number_ _{_ _value_ _:_ _i32_ _,_ _}_ _fn_ _numbervalue_ _<_ _'_ _a_ _>_ _(_ _num_ _:_ _&_ _'_ _a_ _Number_ _)_ -> _&_ _'_ _a_ _i32_ _{_ _&_num_._ _value_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ n = _Number_ _{_ _value_ _:_ _47_ _}_ _;_ _let_ v = _numbervalue_ _(_ _&_n_)_ _;_ _// `v` borrows `n` (immutably), thus: `v` cannot outlive `n`._ _// While `v` exists, `n` cannot be mutably borrowed, mutated, moved, etc._ _}_
When there is a single input lifetime, it doesn’t need to be named, and everything has the same lifetime, so the two functions below are equivalent:
_fn_ _numbervalue_ _<_ _'_ _a_ _>_ _(_ _num_ _:_ _&_ _'_ _a_ _Number_ _)_ -> _&_ _'_ _a_ _i32_ _{_ _&_num_._ _value_ _}_ _fn_ _numbervalue_ _(_ _num_ _:_ _&_ _Number_ _)_ -> _&_ _i32_ _{_ _&_num_._ _value_ _}_
Structs generic over lifetimes
Structs can also be generic over lifetimes, which allows them to hold references:
_struct_ _NumRef_ _<_ _'_ _a_ _>_ _{_ _x_ _:_ _&_ _'_ _a_ _i32_ _,_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ x_:_ _i32_ = _99_ _;_ _let_ x_ref = _NumRef_ _{_ _x_ _:_ _&_x _}_ _;_ _// `xref` cannot outlive `x`, etc._ _}_
The same code, but with an additional function:
_struct_ _NumRef_ _<_ _'_ _a_ _>_ _{_ _x_ _:_ _&_ _'_ _a_ _i32_ _,_ _}_ _fn_ _asnumref_ _<_ _'_ _a_ _>_ _(_ _x_ _:_ _&_ _'_ _a_ _i32_ _)_ -> _NumRef_ _<_ _'_ _a_ _>_ _{_ _NumRef_ _{_ _x_ _:_ _&_x _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ x_:_ _i32_ = _99_ _;_ _let_ x_ref = _asnumref_ _(_ _&_x_)_ _;_ _// `xref` cannot outlive `x`, etc._ _}_
The same code, but with “elided” lifetimes:
_struct_ _NumRef_ _<_ _'_ _a_ _>_ _{_ _x_ _:_ _&_ _'_ _a_ _i32_ _,_ _}_ _fn_ _asnumref_ _(_ _x_ _:_ _&_ _i32_ _)_ -> _NumRef_ _<_ _'_ ___ _>_ _{_ _NumRef_ _{_ _x_ _:_ _&_x _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ x_:_ _i32_ = _99_ _;_ _let_ x_ref = _asnumref_ _(_ _&_x_)_ _;_ _// `xref` cannot outlive `x`, etc._ _}_
Implementations generic over lifetimes
impl
blocks can be generic over lifetimes too:
_impl_ _<_ _'_ _a_ _>_ _NumRef_ _<_ _'_ _a_ _>_ _{_ _fn_ _asi32ref_ _(_ _&_ _'_ _a_ _self_ _)_ -> _&_ _'_ _a_ _i32_ _{_ _self_ _._ _x_ _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ x_:_ _i32_ = _99_ _;_ _let_ x_num_ref = _NumRef_ _{_ _x_ _:_ _&_x _}_ _;_ _let_ x_i32_ref = x_num_ref_._ _asi32ref_ _(_ _)_ _;_ _// neither ref can outlive `x`_ _}_
But you can do elision (“to elide”) there too:
_impl_ _<_ _'_ _a_ _>_ _NumRef_ _<_ _'_ _a_ _>_ _{_ _fn_ _asi32ref_ _(_ _&_ _self_ _)_ -> _&_ _i32_ _{_ _self_ _._ _x_ _}_ _}_
You can elide even harder, if you never need the name:
_impl_ _NumRef_ _<_ _'_ ___ _>_ _{_ _fn_ _asi32ref_ _(_ _&_ _self_ _)_ -> _&_ _i32_ _{_ _self_ _._ _x_ _}_ _}_
There is a special lifetime, named 'static
, which is valid for the entire program’s lifetime.
String literals are 'static
:
_struct_ _Person_ _{_ _name_ _:_ _&_ _'_ _static_ _str_ _,_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ p = _Person_ _{_ _name_ _:_ _"fasterthanlime"_ _,_ _}_ _;_ _}_
But references to a String
are not static:
_struct_ _Person_ _{_ _name_ _:_ _&_ _'_ _static_ _str_ _,_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ name = _format_ _!_ _(_ _"fasterthan{}"_ _,_ _"lime"_ _)_ _;_ _let_ p = _Person_ _{_ _name_ _:_ _&_name _}_ _;_ _// error: `name` does not live long enough_ _}_
In that last example, the local name
is not a &'static str
, it’s aString
. It’s been allocated dynamically, and it will be freed. Its lifetime is less than the whole program (even though it happens to be in main
).
To store a non-'static
string in Person
, it needs to either:
A) Be generic over a lifetime:
_struct_ _Person_ _<_ _'_ _a_ _>_ _{_ _name_ _:_ _&_ _'_ _a_ _str_ _,_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ name = _format_ _!_ _(_ _"fasterthan{}"_ _,_ _"lime"_ _)_ _;_ _let_ p = _Person_ _{_ _name_ _:_ _&_name _}_ _;_ _// `p` cannot outlive `name`_ _}_
or
B) Take ownership of the string
_struct_ _Person_ _{_ _name_ _:_ _String_ _,_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ name = _format_ _!_ _(_ _"fasterthan{}"_ _,_ _"lime"_ _)_ _;_ _let_ p = _Person_ _{_ _name_ _:_ name _}_ _;_ _// `name` was moved into `p`, their lifetimes are no longer tied._ _}_
Struct literal assignment shorthand
Speaking of: in a struct literal, when a field is set to a variable binding of the same name:
_let_ p = _Person_ _{_ _name_ _:_ name _}_ _;_
It can be shortened like this:
_let_ p = _Person_ _{_ name _}_ _;_
Tools like clippy will suggest making those changes, and even apply the fix programmatically if you let it.
Owned types vs reference types
For many types in Rust, there are owned and non-owned variants:
- Strings:
String
is owned,&str
is a reference. - Paths:
PathBuf
is owned,&Path
is a reference. - Collections:
Vec<T>
is owned,&[T]
is a reference. Slices
Rust has slices - they’re a reference to multiple contiguous elements.
You can borrow a slice of a vector, for example:
_fn_ _main_ _(_ _)_ _{_ _let_ v = _vec_ _!_ _[_ _1_ _,_ _2_ _,_ _3_ _,_ _4_ _,_ _5_ _]_ _;_ _let_ v2 = _&_v_[_ _2_.._4_ _]_ _;_ _println_ _!_ _(_ _"v2 = {:?}"_ _,_ v2_)_ _;_ _}_ _// output:_ _// v2 = [3, 4]_
The above is not magical. The indexing operator (foo[index]
) is overloaded with the Index
and IndexMut
traits.
The ..
syntax is just range literals. Ranges are just a few structs defined in the standard library.
They can be open-ended, and their rightmost bound can be inclusive, if it’s preceded by =
.
_fn_ _main_ _(_ _)_ _{_ _// 0 or greater_ _println_ _!_ _(_ _"{:?}"_ _,_ _(_ _0_.._)_ _._ _contains_ _(_ _&_ _100_ _)_ _)_ _;_ _// true_ _// strictly less than 20_ _println_ _!_ _(_ _"{:?}"_ _,_ _(_.._20_ _)_ _._ _contains_ _(_ _&_ _20_ _)_ _)_ _;_ _// false_ _// 20 or less than 20_ _println_ _!_ _(_ _"{:?}"_ _,_ _(_..=_20_ _)_ _._ _contains_ _(_ _&_ _20_ _)_ _)_ _;_ _// true_ _// only 3, 4, 5_ _println_ _!_ _(_ _"{:?}"_ _,_ _(_ _3_.._6_ _)_ _._ _contains_ _(_ _&_ _4_ _)_ _)_ _;_ _// true_ _}_
Borrowing rules apply to slices.
_fn_ _tail_ _(_ _s_ _:_ _&_ _[_ _u8_ _]_ _)_ -> _&_ _[_ _u8_ _]_ _{_ _&_s_[_ _1_.._]_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ x = _&_ _[_ _1_ _,_ _2_ _,_ _3_ _,_ _4_ _,_ _5_ _]_ _;_ _let_ y = _tail_ _(_x_)_ _;_ _println_ _!_ _(_ _"y = {:?}"_ _,_ y_)_ _;_ _}_
This is the same as:
_fn_ _tail_ _<_ _'_ _a_ _>_ _(_ _s_ _:_ _&_ _'_ _a_ _[_ _u8_ _]_ _)_ -> _&_ _'_ _a_ _[_ _u8_ _]_ _{_ _&_s_[_ _1_.._]_ _}_
This is legal:
_fn_ _main_ _(_ _)_ _{_ _let_ y = _{_ _let_ x = _&_ _[_ _1_ _,_ _2_ _,_ _3_ _,_ _4_ _,_ _5_ _]_ _;_ _tail_ _(_x_)_ _}_ _;_ _println_ _!_ _(_ _"y = {:?}"_ _,_ y_)_ _;_ _}_
…but only because [1, 2, 3, 4, 5]
is a 'static
array.
So, this is illegal:
_fn_ _main_ _(_ _)_ _{_ _let_ y = _{_ _let_ v = _vec_ _!_ _[_ _1_ _,_ _2_ _,_ _3_ _,_ _4_ _,_ _5_ _]_ _;_ _tail_ _(_ _&_v_)_ _// error: `v` does not live long enough_ _}_ _;_ _println_ _!_ _(_ _"y = {:?}"_ _,_ y_)_ _;_ _}_
…because a vector is heap-allocated, and it has a non-'static
lifetime.
&str
values are really slices.
_fn_ _fileext_ _(_ _name_ _:_ _&_ _str_ _)_ -> _Option_ _<_ _&_ _str_ _>_ _{_ _// this does not create a new string - it returns_ _// a slice of the argument._ name_._ _split_ _(_ _"."_ _)_ _._ _last_ _(_ _)_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ name = _"Read me. Or don't.txt"_ _;_ _if_ _let_ Some_(_ext_)_ = _fileext_ _(_name_)_ _{_ _println_ _!_ _(_ _"file extension: {}"_ _,_ ext_)_ _;_ _}_ _else_ _{_ _println_ _!_ _(_ _"no file extension"_ _)_ _;_ _}_ _}_
…so the borrow rules apply here too:
_fn_ _main_ _(_ _)_ _{_ _let_ ext = _{_ _let_ name = _String_ _::_ _from_ _(_ _"Read me. Or don't.txt"_ _)_ _;_ _fileext_ _(_ _&_name_)_ _._ _unwrapor_ _(_ _""_ _)_ _// error: `name` does not live long enough_ _}_ _;_ _println_ _!_ _(_ _"extension: {:?}"_ _,_ ext_)_ _;_ _}_
Fallible functions (Result<T, E>)
Functions that can fail typically return a Result
:
_fn_ _main_ _(_ _)_ _{_ _let_ s = std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _240_ _,_ _159_ _,_ _141_ _,_ _137_ _]_ _)_ _;_ _println_ _!_ _(_ _"{:?}"_ _,_ s_)_ _;_ _// prints: Ok("🍉")_ _let_ s = std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _195_ _,_ _40_ _]_ _)_ _;_ _println_ _!_ _(_ _"{:?}"_ _,_ s_)_ _;_ _// prints: Err(Utf8Error { validupto: 0, errorlen: Some(1) })_ _}_
If you want to panic in case of failure, you can .unwrap()
:
_fn_ _main_ _(_ _)_ _{_ _let_ s = std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _240_ _,_ _159_ _,_ _141_ _,_ _137_ _]_ _)_ _._ _unwrap_ _(_ _)_ _;_ _println_ _!_ _(_ _"{:?}"_ _,_ s_)_ _;_ _// prints: "🍉"_ _let_ s = std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _195_ _,_ _40_ _]_ _)_ _._ _unwrap_ _(_ _)_ _;_ _// prints: thread 'main' panicked at 'called `Result::unwrap()`_ _// on an `Err` value: Utf8Error { validupto: 0, errorlen: Some(1) }',_ _// src/libcore/result.rs:1165:5_ _}_
Or .expect()
, for a custom message:
_fn_ _main_ _(_ _)_ _{_ _let_ s = std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _195_ _,_ _40_ _]_ _)_ _._ _expect_ _(_ _"valid utf-8"_ _)_ _;_ _// prints: thread 'main' panicked at 'valid utf-8: Utf8Error_ _// { validupto: 0, errorlen: Some(1) }', src/libcore/result.rs:1165:5_ _}_
Or, you can match
:
_fn_ _main_ _(_ _)_ _{_ _match_ std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _240_ _,_ _159_ _,_ _141_ _,_ _137_ _]_ _)_ _{_ Ok_(_s_)_ => _println_ _!_ _(_ _"{}"_ _,_ s_)_ _,_ Err_(_e_)_ => _panic_ _!_ _(_e_)_ _,_ _}_ _// prints 🍉_ _}_
Or you can if let
:
_fn_ _main_ _(_ _)_ _{_ _if_ _let_ Ok_(_s_)_ = std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _240_ _,_ _159_ _,_ _141_ _,_ _137_ _]_ _)_ _{_ _println_ _!_ _(_ _"{}"_ _,_ s_)_ _;_ _}_ _// prints 🍉_ _}_
Or you can bubble up the error:
_fn_ _main_ _(_ _)_ -> _Result_ _<_ _(_ _)_ _,_ std_::_str_::_ _Utf8Error_ _>_ _{_ _match_ std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _240_ _,_ _159_ _,_ _141_ _,_ _137_ _]_ _)_ _{_ Ok_(_s_)_ => _println_ _!_ _(_ _"{}"_ _,_ s_)_ _,_ Err_(_e_)_ => _return_ _Err_ _(_e_)_ _,_ _}_ _Ok_ _(_ _(_ _)_ _)_ _}_
Or you can use ?
to do it the concise way:
_fn_ _main_ _(_ _)_ -> _Result_ _<_ _(_ _)_ _,_ std_::_str_::_ _Utf8Error_ _>_ _{_ _let_ s = std_::_str_::_ _fromutf8_ _(_ _&_ _[_ _240_ _,_ _159_ _,_ _141_ _,_ _137_ _]_ _)_?_;_ _println_ _!_ _(_ _"{}"_ _,_ s_)_ _;_ _Ok_ _(_ _(_ _)_ _)_ _}_
The *
operator can be used to dereference, but you don’t need to do that to access fields or call methods:
_struct_ _Point_ _{_ _x_ _:_ _f64_ _,_ _y_ _:_ _f64_ _,_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ p = _Point_ _{_ _x_ _:_ _1.0_ _,_ _y_ _:_ _3.0_ _}_ _;_ _let_ p_ref = _&_p_;_ _println_ _!_ _(_ _"({}, {})"_ _,_ p_ref_._ _x_ _,_ p_ref_._ _y_ _)_ _;_ _}_ _// prints `(1, 3)`_
And you can only do it if the type is Copy
:
_struct_ _Point_ _{_ _x_ _:_ _f64_ _,_ _y_ _:_ _f64_ _,_ _}_ _fn_ _negate_ _(_ _p_ _:_ _Point_ _)_ -> _Point_ _{_ _Point_ _{_ _x_ _:_ -p_._ _x_ _,_ _y_ _:_ -p_._ _y_ _,_ _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ p = _Point_ _{_ _x_ _:_ _1.0_ _,_ _y_ _:_ _3.0_ _}_ _;_ _let_ p_ref = _&_p_;_ _negate_ _(_ _*_p_ref_)_ _;_ _// error: cannot move out of `*pref` which is behind a shared reference_ _}_
_// now `Point` is `Copy`_ _#[derive(Clone, Copy) ]_ _struct_ _Point_ _{_ _x_ _:_ _f64_ _,_ _y_ _:_ _f64_ _,_ _}_ _fn_ _negate_ _(_ _p_ _:_ _Point_ _)_ -> _Point_ _{_ _Point_ _{_ _x_ _:_ -p_._ _x_ _,_ _y_ _:_ -p_._ _y_ _,_ _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ p = _Point_ _{_ _x_ _:_ _1.0_ _,_ _y_ _:_ _3.0_ _}_ _;_ _let_ p_ref = _&_p_;_ _negate_ _(_ _*_p_ref_)_ _;_ _// ...and now this works_ _}_
Closures are just functions of type Fn
, FnMut
or FnOnce
with some captured context.
Their parameters are a comma-separated list of names within a pair of pipes (|
). They don’t need curly braces, unless you want to have multiple statements.
_fn_ _foreachplanet_ _<_ _F_ _>_ _(_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _Fn_ _(_ _&_ _'_ _static_ _str_ _)_ _{_ _f_ _(_ _"Earth"_ _)_ _;_ _f_ _(_ _"Mars"_ _)_ _;_ _f_ _(_ _"Jupiter"_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _foreachplanet_ _(_|planet| _println_ _!_ _(_ _"Hello, {}"_ _,_ planet_)_ _)_ _;_ _}_ _// prints:_ _// Hello, Earth_ _// Hello, Mars_ _// Hello, Jupiter_
The borrow rules apply to them too:
_fn_ _foreachplanet_ _<_ _F_ _>_ _(_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _Fn_ _(_ _&_ _'_ _static_ _str_ _)_ _{_ _f_ _(_ _"Earth"_ _)_ _;_ _f_ _(_ _"Mars"_ _)_ _;_ _f_ _(_ _"Jupiter"_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ greeting = _String_ _::_ _from_ _(_ _"Good to see you"_ _)_ _;_ _foreachplanet_ _(_|planet| _println_ _!_ _(_ _"{}, {}"_ _,_ greeting_,_ planet_)_ _)_ _;_ _// our closure borrows `greeting`, so it cannot outlive it_ _}_
For example, this would not work:
_fn_ _foreachplanet_ _<_ _F_ _>_ _(_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _Fn_ _(_ _&_ _'_ _static_ _str_ _)_ + _'_ _static_ _// `F` must now have "'static" lifetime_ _{_ _f_ _(_ _"Earth"_ _)_ _;_ _f_ _(_ _"Mars"_ _)_ _;_ _f_ _(_ _"Jupiter"_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ greeting = _String_ _::_ _from_ _(_ _"Good to see you"_ _)_ _;_ _foreachplanet_ _(_|planet| _println_ _!_ _(_ _"{}, {}"_ _,_ greeting_,_ planet_)_ _)_ _;_ _// error: closure may outlive the current function, but it borrows_ _// `greeting`, which is owned by the current function_ _}_
But this would:
_fn_ _main_ _(_ _)_ _{_ _let_ greeting = _String_ _::_ _from_ _(_ _"You're doing great"_ _)_ _;_ _foreachplanet_ _(_ _move_ |planet| _println_ _!_ _(_ _"{}, {}"_ _,_ greeting_,_ planet_)_ _)_ _;_ _// `greeting` is no longer borrowed, it is *moved* into_ _// the closure._ _}_
An FnMut
needs to be mutably borrowed to be called, so it can only be called once at a time.
This is legal:
_fn_ _foobar_ _<_ _F_ _>_ _(_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _Fn_ _(_ _i32_ _)_ -> _i32_ _{_ _println_ _!_ _(_ _"{}"_ _,_ _f_ _(_ _f_ _(_ _2_ _)_ _)_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _foobar_ _(_|x| x _*_ _2_ _)_ _;_ _}_ _// output: 8_
This isn’t:
_fn_ _foobar_ _<_ _F_ _>_ _(_ _mut_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _FnMut_ _(_ _i32_ _)_ -> _i32_ _{_ _println_ _!_ _(_ _"{}"_ _,_ _f_ _(_ _f_ _(_ _2_ _)_ _)_ _)_ _;_ _// error: cannot borrow `f` as mutable more than once at a time_ _}_ _fn_ _main_ _(_ _)_ _{_ _foobar_ _(_|x| x _*_ _2_ _)_ _;_ _}_
This is legal again:
_fn_ _foobar_ _<_ _F_ _>_ _(_ _mut_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _FnMut_ _(_ _i32_ _)_ -> _i32_ _{_ _let_ tmp = _f_ _(_ _2_ _)_ _;_ _println_ _!_ _(_ _"{}"_ _,_ _f_ _(_tmp_)_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _foobar_ _(_|x| x _*_ _2_ _)_ _;_ _}_ _// output: 8_
FnMut
exists because some closures mutably borrow local variables:
_fn_ _foobar_ _<_ _F_ _>_ _(_ _mut_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _FnMut_ _(_ _i32_ _)_ -> _i32_ _{_ _let_ tmp = _f_ _(_ _2_ _)_ _;_ _println_ _!_ _(_ _"{}"_ _,_ _f_ _(_tmp_)_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ _mut_ acc = _2_ _;_ _foobar_ _(_|x| _{_ acc += _1_ _;_ x _*_ acc_}_ _)_ _;_ _}_ _// output: 24_
Those closures cannot be passed to functions expecting Fn
:
_fn_ _foobar_ _<_ _F_ _>_ _(_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _Fn_ _(_ _i32_ _)_ -> _i32_ _{_ _println_ _!_ _(_ _"{}"_ _,_ _f_ _(_ _f_ _(_ _2_ _)_ _)_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ _mut_ acc = _2_ _;_ _foobar_ _(_|x| _{_ acc += _1_ _;_ _// error: cannot assign to `acc`, as it is a_ _// captured variable in a `Fn` closure._ _// the compiler suggests "changing foobar_ _// to accept closures that implement `FnMut`"_ x _*_ acc_}_ _)_ _;_ _}_
FnOnce
closures can only be called once. They exist because some closure move out variables that have been moved when captured:
_fn_ _foobar_ _<_ _F_ _>_ _(_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _FnOnce_ _(_ _)_ -> _String_ _{_ _println_ _!_ _(_ _"{}"_ _,_ _f_ _(_ _)_ _)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ s = _String_ _::_ _from_ _(_ _"alright"_ _)_ _;_ _foobar_ _(_ _move_ || s_)_ _;_ _// `s` was moved into our closure, and our_ _// closures moves it to the caller by returning_ _// it. Remember that `String` is not `Copy`._ _}_
This is enforced naturally, as FnOnce
closures need to be _moved_in order to be called.
So, for example, this is illegal:
_fn_ _foobar_ _<_ _F_ _>_ _(_ _f_ _:_ _F_ _)_ _where_ _F_ _:_ _FnOnce_ _(_ _)_ -> _String_ _{_ _println_ _!_ _(_ _"{}"_ _,_ _f_ _(_ _)_ _)_ _;_ _println_ _!_ _(_ _"{}"_ _,_ _f_ _(_ _)_ _)_ _;_ _// error: use of moved value: `f`_ _}_
And, if you need convincing that our closure does move s
, this is illegal too:
_fn_ _main_ _(_ _)_ _{_ _let_ s = _String_ _::_ _from_ _(_ _"alright"_ _)_ _;_ _foobar_ _(_ _move_ || s_)_ _;_ _foobar_ _(_ _move_ || s_)_ _;_ _// use of moved value: `s`_ _}_
But this is fine:
_fn_ _main_ _(_ _)_ _{_ _let_ s = _String_ _::_ _from_ _(_ _"alright"_ _)_ _;_ _foobar_ _(_|| s_._ _clone_ _(_ _)_ _)_ _;_ _foobar_ _(_|| s_._ _clone_ _(_ _)_ _)_ _;_ _}_
Here’s a closure with two arguments:
_fn_ _foobar_ _<_ _F_ _>_ _(_ _x_ _:_ _i32_ _,_ _y_ _:_ _i32_ _,_ _isgreater_ _:_ _F_ _)_ _where_ _F_ _:_ _Fn_ _(_ _i32_ _,_ _i32_ _)_ -> _bool_ _{_ _let_ _(_greater_,_ smaller_)_ = _if_ _isgreater_ _(_x_,_ y_)_ _{_ _(_x_,_ y_)_ _}_ _else_ _{_ _(_y_,_ x_)_ _}_ _;_ _println_ _!_ _(_ _"{} is greater than {}"_ _,_ greater_,_ smaller_)_ _;_ _}_ _fn_ _main_ _(_ _)_ _{_ _foobar_ _(_ _32_ _,_ _64_ _,_ |x_,_ y| x > y_)_ _;_ _}_
Here’s a closure ignoring both its arguments:
_fn_ _main_ _(_ _)_ _{_ _foobar_ _(_ _32_ _,_ _64_ _,_ |_ _,_ _| _panic_ _!_ _(_ _"Comparing is futile!"_ _)_ _)_ _;_ _}_
Here’s a slightly worrying closure:
_fn_ _countdown_ _<_ _F_ _>_ _(_ _count_ _:_ _usize_ _,_ _tick_ _:_ _F_ _)_ _where_ _F_ _:_ _Fn_ _(_ _usize_ _)_ _{_ _for_ i _in_ _(_ _1_..=count_)_ _._ _rev_ _(_ _)_ _{_ _tick_ _(_i_)_ _;_ _}_ _}_ _fn_ _main_ _(_ _)_ _{_ _countdown_ _(_ _3_ _,_ |i| _println_ _!_ _(_ _"tick {}..."_ _,_ i_)_ _)_ _;_ _}_ _// output:_ _// tick 3..._ _// tick 2..._ _// tick 1..._
And here’s a toilet closure:
_fn_ _main_ _(_ _)_ _{_ _countdown_ _(_ _3_ _,_ |_| _(_ _)_ _)_ _;_ _}_
It’s called that because |_| ()
looks like a toilet.
Anything that is iterable can be used in a for in
loop.
We’ve just seen a range being used, but it also works with a Vec
:
_fn_ _main_ _(_ _)_ _{_ _for_ i _in_ _vec_ _!_ _[_ _52_ _,_ _49_ _,_ _21_ _]_ _{_ _println_ _!_ _(_ _"I like the number {}"_ _,_ i_)_ _;_ _}_ _}_
Or a slice:
_fn_ _main_ _(_ _)_ _{_ _for_ i _in_ _&_ _[_ _52_ _,_ _49_ _,_ _21_ _]_ _{_ _println_ _!_ _(_ _"I like the number {}"_ _,_ i_)_ _;_ _}_ _}_ _// output:_ _// I like the number 52_ _// I like the number 49_ _// I like the number 21_
Or an actual iterator:
_fn_ _main_ _(_ _)_ _{_ _// note: `&str` also has a `.bytes()` iterator._ _// Rust's `char` type is a "Unicode scalar value"_ _for_ c _in_ _"rust"_ _._ _chars_ _(_ _)_ _{_ _println_ _!_ _(_ _"Give me a {}"_ _,_ c_)_ _;_ _}_ _}_ _// output:_ _// Give me a r_ _// Give me a u_ _// Give me a s_ _// Give me a t_
Even if the iterator items are filtered and mapped and flattened:
_fn_ _main_ _(_ _)_ _{_ _for_ c _in_ _"SuRPRISE INbOUND"_ _._ _chars_ _(_ _)_ _._ _filter_ _(_|c| c_._ _islowercase_ _(_ _)_ _)_ _._ _flatmap_ _(_|c| c_._ _touppercase_ _(_ _)_ _)_ _{_ _print_ _!_ _(_ _"{}"_ _,_ c_)_ _;_ _}_ _println_ _!_ _(_ _)_ _;_ _}_ _// output: UB_
You can return a closure from a function:
_fn_ _maketester_ _(_ _answer_ _:_ _String_ _)_ -> _impl_ _Fn_ _(_ _&_ _str_ _)_ -> _bool_ _{_ _move_ |challenge| _{_ challenge == answer_}_ _}_ _fn_ _main_ _(_ _)_ _{_ _// you can use `.into()` to perform conversions_ _// between various types, here `&'static str` and `String`_ _let_ test = _maketester_ _(_ _"hunter2"_ _._ _into_ _(_ _)_ _)_ _;_ _println_ _!_ _(_ _"{}"_ _,_ _test_ _(_ _"******"_ _)_ _)_ _;_ _println_ _!_ _(_ _"{}"_ _,_ _test_ _(_ _"hunter2"_ _)_ _)_ _;_ _}_
You can even move a reference to some of a function’s arguments, into a closure it returns:
_fn_ _maketester_ _<_ _'_ _a_ _>_ _(_ _answer_ _:_ _&_ _'_ _a_ _str_ _)_ -> _impl_ _Fn_ _(_ _&_ _str_ _)_ -> _bool_ + _'_ _a_ _{_ _move_ |challenge| _{_ challenge == answer_}_ _}_ _fn_ _main_ _(_ _)_ _{_ _let_ test = _maketester_ _(_ _"hunter2"_ _)_ _;_ _println_ _!_ _(_ _"{}"_ _,_ _test_ _(_ _"*******"_ _)_ _)_ _;_ _println_ _!_ _(_ _"{}"_ _,_ _test_ _(_ _"hunter2"_ _)_ _)_ _;_ _}_ _// output:_ _// false_ _// true_
Or, with elided lifetimes:
_fn_ _maketester_ _(_ _answer_ _:_ _&_ _str_ _)_ -> _impl_ _Fn_ _(_ _&_ _str_ _)_ -> _bool_ + _'_ ___ _{_ _move_ |challenge| _{_ challenge == answer_}_ _}_
And with that, we hit the 30-minute estimated reading time mark, and you should be able to read most of the Rust code you find online.
Writing Rust is a very different experience from reading Rust. On one hand, you’re not reading the solution to a problem, you’re actually solving it. On the other hand, the Rust compiler helps out a lot.
The Rust compiler has high-quality diagnostics (which include suggestions) for all the mistakes featured in this article.
And when there’s a hint missing, the compiler team is not afraid to add it.
For more Rust material, you may want to check out:
I also blog about Rust and post a lot about Rust onMastodon andTwitter a lot, so if you liked this article, you know what to do!
Have fun!
(JavaScript is required to see this. Or maybe my stuff broke)
NOW RECRUITING
bearcove is recruiting a video editor for fasterthanlime.
Applicants need:
- a mac
- good english
- great internet
- willingness to learn a specific DaVinci Resolve-based workflow
Is this you? Or do you know someone who is? check out the complete job listing here
Here's another article just for you:
My ideal Rust workflow
Writing Rust is pretty neat. But you know what’s even neater? Continuously testing Rust, releasing Rust, and eventually, shipping Rust to production. And for that, we want more than plug-in for a code editor.
We want… a workflow.
Why I specifically care about this
This gets pretty long, so if all you want is the advice, feel free to jump to it directly.