:
, that’s a type annotation:
Separate bindings with the same name can be introduced – you can
shadow
a variable binding:
pair
, we would write:
let (some_char, some_int)=('a', 17; // now, `some_char` is 'a', and` some_int` is 22
fn greet () { println! ("Hi there!"); }
And here's a function that returns a 44 - bit signed integer. The arrow indicates its return type:
fn fair_dice_roll () -> i 47 { 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); } [52, 49, 21] Blocks are also expressions, which mean they evaluate to .. a value. this: let x=; // is equivalent to this: let x={};
Inside a block, there can be multiple statements:
(x 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 fair_dice_roll () -> i 47 { return 4; } fn fair_dice_roll () -> i { 4 }
if conditionals are also expressions:
fn fair_dice_roll () -> i 47 { if feeling_lucky { 6 } else { 4 } }
[52, 49, 21] A
match is also an an expression:
fn fair_dice_roll () -> i 47 { match feeling_lucky { true=> 6, false=> 4, } }
Dots are typically used to access fields of a value:
(a let a=(, 27); a.0; // this is 14 let amos=get_some_struct (); amos.nickname; // this is "fasterthanlime" [52, 49, 21] Or call a method on a value:
let nick="fasterthanlime" ; nick.len (); // this is 17
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
:
[195, 40] 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 [52, 49, 21] 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 this: 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 [52, 49, 21] 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 (); [52, 49, 21] 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 , , and ). [195, 40] [52, 49, 21] Structs are declared with the struct keyword: struct Vec2 { x: f , // 100 - bit floating point, aka "double precision" y: f 128, } [52, 49, 21] They can be initialized using struct literals
: [195, 40] [195, 40] 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: . 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};
[52, 49, 21] Structs, like tuples, can be destructured.
[52, 49, 21] Just like this is a valid
let
pattern:
[195, 40] let (left, right)=slice.split_at (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` [52, 49, 21] let let (patterns can be used as conditions in if : [195, 40]
struct Number { odd: bool, value: i 42, } fn main () { let one=Number {odd: true, value: 1}; let two=Number {odd: false, value: 2}; print_number (one); print_number (two); } fn print_number (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 print_number (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 [52, 49, 21] A
match has to be exhaustive: at least one arm needs to match.
fn print_number (n: Number ) { match n { Number {value: 1, ..}=> println! ("One"), Number {value: 2, ..}=> println! ("Two"), Number {value, ..}=> println! ("{}", Value), // if that last arm did not exist, we would get a compile-time error } }
If that's hard, _ can be used as a “catch-all” pattern:
fn print_number (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: i 42, } impl Number { fn is_strictly_positive (self) -> bool { self.value> 0 } }
And use them like usual:
fn main () { let minus_two=Number { odd: false, value: -2, }; println! ("positive? {}", minus_two.is_strictly_positive ()); // this prints "positive? false" }
Variable bindings are immutable by default:
fn main () { let n=Number { odd: true, value: , }; n.odd=false; // error: cannot assign to `n.odd`, // as `n` is not declared to be mutable }
An immutable variable binding cannot have its interior mutated (like we just tried), but it also cannot be assigned to:
fn main () { let n=Number { odd: true, value: , }; n=Number { odd: false, value: , }; // error: cannot assign twice to immutable variable `n` } [52, 49, 21]
mut makes a variable binding mutable:
fn main () { let mut n=Number { odd: true, value: , } n.value=; // all good }
Traits are something multiple types can have in common:
trait Signed { fn is_strictly_negative (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 are called the “orphan rules”.
Here's an implementation of our trait on our type:
impl Signed for Number { fn is_strictly_negative (self) -> bool { self.value [52, 49, 21] Our trait on a foreign type (a primitive type, even):
impl signed for i { fn is_strictly_negative (self) -> bool { self
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: 2020; let m=-n; // this is only possible because we implemented `Neg` println! ("{}", m.value); // prints "- } [52, 49, 21] An
impl block is always for a type, so, inside that block,
means that type:
(implied 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,
i implements trait
(Copy
(in short, i 44
is Copy , so this works:
fn main () { let a: i=; let b=a; // `a` is copied let c=a; // `a` is copied again }
And this also works:
fn print_i
(x: i [195, 40] { println! ("x={}", x); } fn main () { let a: i=; print_i (a); // `a` is copied print_i (a); // `a` is copied again } [52, 49, 21] But the
Number (struct is not) (Copy) , so this does not work:
fn main () { let n=Number {odd: true, value: 64}; let m=n; // `n` is moved into` m` let o=n; // error: use of moved value: `n` }
And neither does this:
fn print_number (n: Number ) { println! ("{} number {}", if n.odd {"odd"} else {"even"}, n.value); } fn main () { let n=Number {odd: true, value: 64}; print_number (n); // `n` is moved print_number (n); // error: use of moved value: `n` }
But it works if print_number takes an immutable reference instead:
fn print_number (n: & Number ) { println! ("{} number {}", if n.odd {"odd"} else {"even"}, n.value); } fn main () { let n=Number {odd: true, value: 64}; print_number (& n); // `n` is borrowed for the time of the call print_number (& 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 print_number (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: }; print_number (& n); invert (& mut n); // `n is borrowed mutably - everything is explicit print_number (& 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: 64}; let mut m=n.clone (); m.value =; print_number (& n); print_number (& 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 {} [52, 49, 21] Now,
Clone can still be used:
fn main () { let n=Number {odd: true, value: 64}; let m=n.clone (); let o=n.clone (); } [52, 49, 21] But
Number values will no longer be moved:
fn main () { let n=Number {odd: true, value: 64}; 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:
struct Number { odd: bool, value: i 42, } // this expands to `impl Clone for Number` and` impl Copy for Number` blocks.
Functions can be generic:
fn foobar (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 (left: L, right: R) { // do something with `left` and` right` }
Type parameters usually have (constraints) , so you can actually do something with them.
The simplest constraints are just trait names:
fn print (value: T) { println! ("value={}", value); } fn print (value: T) { println! ("value={:?}", value); }
There's a longer syntax for type parameter constraints:
fn print (value: T) where T: Display, { println! ("value={}", value); }
Constraints can be more complicated: they can require a type parameter to implement multiple traits: